383 lines
10 KiB
C
383 lines
10 KiB
C
/* *****************************************************************************
|
|
* protocol/seven/recv.c -- receive and decode the packet.
|
|
* Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
|
*
|
|
* This file is part of libp7.
|
|
* libp7 is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 3.0 of the License,
|
|
* or (at your option) any later version.
|
|
*
|
|
* libp7 is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
* See the GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with libp7; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* These functions are about receiving and decoding a packet.
|
|
* It is also about managing checksum problems and timeouts.
|
|
* ************************************************************************** */
|
|
#include <libp7/internals.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#pragma pack(1)
|
|
/* TYPZ1/2 subheader */
|
|
struct typz_subheader {
|
|
/* ASCII-hex size */
|
|
uint8_t size[6];
|
|
|
|
/* dimensions */
|
|
uint8_t height[4];
|
|
uint8_t width[4];
|
|
|
|
/* one: we are number one but it is in ascii (always "1") */
|
|
uint8_t one;
|
|
|
|
/* encoding algorithm:
|
|
* - RC2: 16-bit mode;
|
|
* - RC3: 3-bit mode (1 nib./pxl, red-green-blue-trash)
|
|
* - RM2: 2-bit mode? */
|
|
uint8_t enc[3];
|
|
};
|
|
#pragma pack()
|
|
|
|
/**
|
|
* complete_packet:
|
|
* Complete the packet with N bytes.
|
|
*
|
|
* Uses stream buffering to achieve this.
|
|
*
|
|
* @arg handle the libp7 handle.
|
|
* @arg buf the buffer.
|
|
* @arg recv pointer to currently received bytes.
|
|
* @arg with complete with this number of byte.
|
|
* @return the error (0 if ok)
|
|
*/
|
|
|
|
static int complete_packet(p7_handle_t *handle,
|
|
unsigned char *buf, size_t *recv, size_t with)
|
|
{
|
|
int err = p7_read(&handle->_stream, &buf[*recv], with);
|
|
*recv += with;
|
|
return (err);
|
|
}
|
|
|
|
/**
|
|
* p7_recv_main:
|
|
* Receive a packet.
|
|
*
|
|
* This is the main receiving function. It receives, parses, and returns
|
|
* if everything has been successfully done or not.
|
|
* It uses buffered I/O to read progressively all of the packet.
|
|
*
|
|
* @arg handle the libp7 handler
|
|
* @return the error (0 if ok)
|
|
*/
|
|
|
|
#define buffer handle->_recv_buffer
|
|
#define COMPLETE_PACKET(N) { \
|
|
int COMP_PACKET_err; \
|
|
if ((COMP_PACKET_err = complete_packet(handle, \
|
|
buffer, &received, (N)))) \
|
|
return (COMP_PACKET_err); \
|
|
}
|
|
|
|
static int p7_recv_main(p7_handle_t *handle)
|
|
{
|
|
/* prepare reception */
|
|
size_t received = 0;
|
|
|
|
/* log packet */
|
|
log_info("receiving packet...");
|
|
|
|
/* get first three bytes, check if is CAL */
|
|
do {
|
|
COMPLETE_PACKET(3)
|
|
if (memcmp(buffer, "CAL", 3))
|
|
break;
|
|
|
|
/* get the CAL value */
|
|
log_info("is the magic CAL!");
|
|
COMPLETE_PACKET(4) /* TODO: check if is 00D0, and what is it? */
|
|
log_info("CAL value is %.4s", &buffer[3]);
|
|
handle->_flags |= p7_intflag_nocheck;
|
|
|
|
/* good as new! */
|
|
received = 0;
|
|
log_info("Restart receiving the packet.");
|
|
} while (1);
|
|
|
|
/* adjust */
|
|
if (handle->_flags & p7_intflag_adjscreen) {
|
|
/* adjust. */
|
|
log_info("Adjusting: looking for the screen header.");
|
|
log_info("Current buffer:");
|
|
logm_info(buffer, 3);
|
|
while (memcmp(buffer, "\x0BTY", 3)) {
|
|
buffer[0] = buffer[1];
|
|
buffer[1] = buffer[2];
|
|
received = 2;
|
|
COMPLETE_PACKET(1)
|
|
|
|
log_info("Current buffer:");
|
|
logm_info(buffer, 3);
|
|
}
|
|
}
|
|
|
|
/* get the type */
|
|
response.type = buffer[0];
|
|
|
|
/* image has a particular format starting from here, look for it now! */
|
|
if (response.type == p7_pt_ohp) {
|
|
log_info("Johnson, this is a screen.");
|
|
COMPLETE_PACKET(3)
|
|
|
|
/* get image by its type */
|
|
unsigned int image_size = 0;
|
|
if (!memcmp(&buffer[1], "TYP01", 5)) {
|
|
log_info("This is normal VRAM, we know it !");
|
|
response.pictype = p7_pictype_1bit;
|
|
response.width = 128;
|
|
response.height = 64;
|
|
image_size = 1024;
|
|
} else if (!memcmp(&buffer[1], "TYPZ1", 5)
|
|
|| !memcmp(&buffer[1], "TYPZ2", 5)) {
|
|
log_info("Prizm VRAM!");
|
|
COMPLETE_PACKET(sizeof(struct typz_subheader))
|
|
struct typz_subheader *s = (void*)&buffer[6];
|
|
response.width = p7_getascii(s->width, 4);
|
|
response.height = p7_getascii(s->height, 4);
|
|
|
|
/* check the type */
|
|
response.pictype = 0;
|
|
if (!memcmp(s->enc, "RC2", 3))
|
|
response.pictype = p7_pictype_16bit;
|
|
else if (!memcmp(s->enc, "RC3", 3))
|
|
response.pictype = p7_pictype_3bit;
|
|
else if (!memcmp(s->enc, "RM2", 3))
|
|
response.pictype = p7_pictype_2bit;
|
|
#if LOGLEVEL <= ll_error
|
|
else log_error("Unknown encoding: %.3s", s->enc);
|
|
#endif
|
|
|
|
image_size = p7_getascii(s->size, 6);
|
|
log_info("Image size: %uB", image_size);
|
|
handle->_flags |= p7_intflag_nocheck;
|
|
} else {
|
|
log_error("Unknown picture encoding: %.5s", &buffer[1]);
|
|
return (p7_error_checksum);
|
|
}
|
|
|
|
/* complete packet */
|
|
size_t hdsize = received;
|
|
log_info("Get screen content (%uo)", image_size);
|
|
COMPLETE_PACKET(image_size)
|
|
memcpy(&response.vram, &buffer[hdsize], image_size);
|
|
|
|
/* log */
|
|
unsigned int packet_size = hdsize + image_size;
|
|
log_info("received the following [screen] packet (%uo) :", packet_size);
|
|
logm_info(buffer, packet_size);
|
|
|
|
/* check the sum */
|
|
if ((~handle->_flags & p7_intflag_nocheck)) {
|
|
COMPLETE_PACKET(2)
|
|
if (p7_checksum(buffer, packet_size)
|
|
!= p7_getascii(&buffer[packet_size], 2))
|
|
return (p7_error_checksum);
|
|
}
|
|
handle->_flags &= ~p7_intflag_nocheck;
|
|
|
|
/* and return the packet */
|
|
return (0);
|
|
}
|
|
|
|
/* subtype and extended */
|
|
COMPLETE_PACKET(1)
|
|
p7ushort_t subtype = p7_getascii(&buffer[1], 2);
|
|
|
|
/* - extended (beginning) - */
|
|
p7ushort_t data_size = 0;
|
|
int is_extended = (buffer[3] == '1');
|
|
if (is_extended) {
|
|
/* get data size */
|
|
COMPLETE_PACKET(4)
|
|
data_size = p7_getascii(&buffer[4], 4);
|
|
|
|
/* get data */
|
|
COMPLETE_PACKET(data_size + 2)
|
|
} else
|
|
COMPLETE_PACKET(2)
|
|
|
|
/* check if we should read a binary zero */
|
|
int is_binary_zero = (response.type == p7_pt_cmd
|
|
&& subtype == 0x56);
|
|
if (is_binary_zero) COMPLETE_PACKET(1)
|
|
|
|
/* log */
|
|
p7ushort_t packet_size = 6 + 4 * !!is_extended + data_size;
|
|
log_info("received the following [normal] packet (%" PRIuP7SHORT "o) :",
|
|
packet_size);
|
|
logm_info(buffer, packet_size);
|
|
|
|
/* calculate checksum */
|
|
unsigned int calculated_checksum = p7_checksum(buffer, packet_size);
|
|
unsigned int found_checksum = p7_getascii(&buffer[packet_size - 2], 2);
|
|
if (calculated_checksum != found_checksum)
|
|
return (p7_error_checksum);
|
|
|
|
/* - extended (finish) - */
|
|
if (is_extended)
|
|
data_size = p7_decode(&buffer[8], &buffer[8], data_size);
|
|
|
|
/* get fields out for specific packets */
|
|
switch (response.type) {
|
|
/* - for command - */
|
|
case p7_pt_cmd:
|
|
log_info("packet was interpreted as a command one");
|
|
response.code = subtype;
|
|
log_info("command is '%s' (0x%02" PRIxP7SHORT ")",
|
|
p7_getcmdstring(subtype), subtype);
|
|
if (is_extended
|
|
&& p7_decode_command(handle, &buffer[8], data_size))
|
|
return (p7_error_checksum);
|
|
break;
|
|
|
|
/* - for data - */
|
|
case p7_pt_data:
|
|
log_info("packet was interpreted as a data one");
|
|
p7_decode_data(handle, &buffer[8], data_size);
|
|
break;
|
|
|
|
/* - for roleswap - */
|
|
case p7_pt_roleswp:
|
|
log_info("packet was interpreted as a roleswap one");
|
|
log_info("becoming active again");
|
|
handle->_flags |= p7_intflag_active;
|
|
break;
|
|
|
|
/* - for check - */
|
|
case p7_pt_check:
|
|
log_info("packet was interpreted as a check one");
|
|
response.initial = !subtype;
|
|
break;
|
|
|
|
/* - for ack - */
|
|
case p7_pt_ack:
|
|
log_info("packet was interpreted as an ack one");
|
|
response.ow = (subtype == 0x01);
|
|
response.extended = (subtype == 0x02);
|
|
if (response.extended
|
|
&& p7_decode_ack(handle, &buffer[8], data_size))
|
|
return (p7_error_checksum);
|
|
if (subtype == 0x03)
|
|
handle->_flags |= p7_intflag_terminated;
|
|
break;
|
|
|
|
/* - for error - */
|
|
case p7_pt_error:
|
|
log_info("packet was interpreted as an error one");
|
|
log_info("error is '%s' (0x%02" PRIxP7SHORT ")",
|
|
p7_geterrstring(subtype), subtype);
|
|
response.code = subtype;
|
|
if (subtype == p7_err_fullmem)
|
|
handle->_flags |= p7_intflag_terminated;
|
|
break;
|
|
|
|
/* - for termination - */
|
|
case p7_pt_terminate:
|
|
log_info("packet was interpreted as a terminate one");
|
|
log_info("termination cause is '%s' (0x%02" PRIxP7SHORT ")",
|
|
p7_gettermstring(subtype), subtype);
|
|
response.code = subtype;
|
|
if (subtype == p7_term_user)
|
|
return (p7_error_interrupted);
|
|
break;
|
|
|
|
/* other */
|
|
default:
|
|
log_fatal("bad type received ! type was 0x%02x", response.type);
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* finally, return the packet */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* p7_recv:
|
|
* Receives packet, checks for errors.
|
|
*
|
|
* @arg handle the libp7 handler
|
|
* @arg checksum if 0 and invalid checksum, ignore and receive again
|
|
* @return if it worked
|
|
*/
|
|
|
|
int p7_recv(p7_handle_t *handle, int checksum)
|
|
{
|
|
/* check if handler is initialized */
|
|
if (!handle)
|
|
return (p7_error_uninitialized);
|
|
|
|
/* main receiving loop */
|
|
int tries = 2, err = 0;
|
|
int wasresend = 0;
|
|
do {
|
|
/* get packet */
|
|
err = p7_recv_main(handle);
|
|
|
|
/* if there was a fail */
|
|
if (err) switch (err) {
|
|
/* if calc couldn't be found, terminate communication */
|
|
case p7_error_nocalc:
|
|
tries = 0;
|
|
break;
|
|
|
|
/* if it is a timeout, remove a try */
|
|
case p7_error_timeout:
|
|
tries--;
|
|
|
|
/* send a check */
|
|
err = p7_send_check(handle);
|
|
if (err) return (err);
|
|
|
|
/* set things */
|
|
wasresend = 0;
|
|
err = p7_error_timeout;
|
|
break;
|
|
|
|
/* if it is a bad checksum, ask for resend ; also,
|
|
* reset tries number in case of bad checksum between timeouts */
|
|
case p7_error_checksum:
|
|
/* if we shouldn't bother sending resend error, just
|
|
* transmit checksum error */
|
|
if (!checksum) break;
|
|
|
|
/* if we receive an invalid checksum while we are in packet
|
|
* shifting mode, or if we received an error when we sent a
|
|
* resend error, cut connexion... we can't do anything. */
|
|
if ((handle->_flags & p7_intflag_shifted) || wasresend)
|
|
return (p7_error_irrecoverable);
|
|
|
|
/* otherwise, send resend error */
|
|
err = p7_send_err_resend(handle);
|
|
if (err) return (err);
|
|
|
|
/* set things */
|
|
wasresend = 1;
|
|
err = p7_error_checksum;
|
|
break;
|
|
|
|
/* should not catch bad type & others */
|
|
default:
|
|
return (p7_error_unknown);
|
|
}
|
|
} while (err && tries);
|
|
|
|
/* return the error */
|
|
return (err);
|
|
}
|