547 lines
14 KiB
C
547 lines
14 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>
|
|
|
|
#if LOGLEVEL <= ll_info
|
|
/* ************************************************************************** */
|
|
/* Logging */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* getcmdstring:
|
|
* Get command code string (useful for logging).
|
|
*
|
|
* @arg code command code
|
|
* @return the string
|
|
*/
|
|
|
|
static const char *getcmdstring(unsigned int code)
|
|
{
|
|
static const char *commands[] = {
|
|
/* system commands */
|
|
"restart the equipment",
|
|
"get device info",
|
|
"set link settings",
|
|
NULL, NULL, NULL,
|
|
"set link timeout",
|
|
"OS Update verification #1",
|
|
"OS Update verification #2",
|
|
"OS Update verification #3",
|
|
"OS Update verification #4",
|
|
NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
|
|
/* MCS commands */
|
|
"create directory on mcs",
|
|
"delete directory on mcs",
|
|
"rename directory on mcs",
|
|
"change working directory on mcs",
|
|
"request file transfer from mcs",
|
|
"transfer file on mcs",
|
|
"delete file on mcs",
|
|
"rename file on mcs",
|
|
"copy file on mcs",
|
|
"request all files transfer on mcs",
|
|
"reset mcs",
|
|
"request mcs capacity",
|
|
"transmit mcs capacity",
|
|
"request all mcs file info",
|
|
"transfer all mcs file info",
|
|
"request RAM image",
|
|
"transfer RAM image",
|
|
"request setup entry",
|
|
"transfer setup entry",
|
|
"request all setup entry",
|
|
NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
|
|
/* Flash commands */
|
|
"create directory on flash",
|
|
"delete directory on flash",
|
|
"rename directory on flash",
|
|
"change working directory on flash",
|
|
"request flash file",
|
|
"transfer flash file",
|
|
"delete file on flash",
|
|
"rename file on flash",
|
|
"copy file on flash",
|
|
"request all flash file",
|
|
"reset flash",
|
|
"capacity transmit request",
|
|
"capacity transmit",
|
|
"request flash file info",
|
|
"transfer flash file info",
|
|
"request ROM image",
|
|
"transfer ROM image",
|
|
"optimize flash filesystem",
|
|
|
|
/* OS Update */
|
|
"request CASIOWIN entry",
|
|
"send CASIOWIN entry",
|
|
"request boot code",
|
|
"send boot code",
|
|
"upload and run"
|
|
};
|
|
|
|
/* return the string */
|
|
if (code > 0x56 || !commands[code])
|
|
return ("<unknown>");
|
|
return (commands[code]);
|
|
}
|
|
|
|
/**
|
|
* geterrstring:
|
|
* Get error string (useful for logging).
|
|
*
|
|
* @arg code error code
|
|
* @return the string
|
|
*/
|
|
|
|
static const char *geterrstring(p7_seven_error_t code)
|
|
{
|
|
static const char *strings[] = {
|
|
"should resend",
|
|
"file exists, overwrite request",
|
|
"will not overwrite",
|
|
"overwrite impossible",
|
|
"memory is full"
|
|
};
|
|
|
|
if (code > 0x04) return ("<unknown error>");
|
|
return (strings[code]);
|
|
}
|
|
|
|
/**
|
|
* gettermstring:
|
|
* Get termination cause string (useful for logging).
|
|
*
|
|
* @arg code cause code
|
|
* @return the string
|
|
*/
|
|
|
|
static const char *gettermstring(p7_seven_term_t code)
|
|
{
|
|
static const char *strings[] = {
|
|
"default",
|
|
"user requested",
|
|
"timeouts",
|
|
"on overwrite request"
|
|
};
|
|
|
|
if (code > 0x03) return ("<unknown termination reason>");
|
|
return (strings[code]);
|
|
}
|
|
|
|
#endif
|
|
/* ************************************************************************** */
|
|
/* Receiving utilities */
|
|
/* ************************************************************************** */
|
|
#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);
|
|
}
|
|
|
|
/* ************************************************************************** */
|
|
/* Main receiving functions */
|
|
/* ************************************************************************** */
|
|
/**
|
|
* p7_seven_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_seven_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_seven_type_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
|
|
|
|
/* FIXME: check size according to format? */
|
|
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);
|
|
}
|
|
|
|
/* check if we should skip */
|
|
if (!response.pictype || image_size > MAX_VRAM_SIZE) {
|
|
p7_skip(&handle->_stream, image_size); /* FIXME: checksum? */
|
|
return (p7_error_unknown);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* check the sum */
|
|
if ((~handle->_flags & p7_intflag_nocheck)) {
|
|
COMPLETE_PACKET(2)
|
|
|
|
/* calculate the checksums */
|
|
p7uint_t csum = p7_checksum(buffer, received);
|
|
p7uint_t csum_ex = p7_getascii(&buffer[received - 2], 2);
|
|
|
|
/* check them */
|
|
if (csum != csum_ex) {
|
|
log_error("Checksum problem: expected 0x%02" PRIXP7INT ", "
|
|
"got 0x%02" PRIXP7INT, csum_ex, csum);
|
|
return (p7_error_checksum);
|
|
}
|
|
}
|
|
handle->_flags &= ~p7_intflag_nocheck;
|
|
|
|
/* log */
|
|
log_info("received the following [screen] packet (%" PRIuSIZE "o) :",
|
|
received);
|
|
logm_info(buffer, received);
|
|
|
|
/* 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);
|
|
|
|
/* check data size */
|
|
if (data_size > MAX_ENCDFLD_SIZE) {
|
|
int err = p7_skip(&handle->_stream, data_size + 2);
|
|
if (err) return (err);
|
|
return (p7_error_checksum);
|
|
}
|
|
|
|
/* 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_seven_type_cmd
|
|
&& subtype == p7_seven_cmdosu_upandrun);
|
|
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_seven_type_cmd:
|
|
log_info("packet was interpreted as a command one");
|
|
response.code = subtype;
|
|
log_info("command is '%s' (0x%02" PRIxP7SHORT ")",
|
|
getcmdstring(subtype), subtype);
|
|
if (is_extended
|
|
&& p7_seven_decode_command(handle, &buffer[8], data_size))
|
|
return (p7_error_checksum);
|
|
break;
|
|
|
|
/* - for data - */
|
|
case p7_seven_type_data:
|
|
log_info("packet was interpreted as a data one");
|
|
p7_seven_decode_data(handle, &buffer[8], data_size);
|
|
break;
|
|
|
|
/* - for roleswap - */
|
|
case p7_seven_type_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_seven_type_check:
|
|
log_info("packet was interpreted as a check one");
|
|
response.initial = !subtype;
|
|
break;
|
|
|
|
/* - for ack - */
|
|
case p7_seven_type_ack:
|
|
log_info("packet was interpreted as an ack one");
|
|
response.ow = (subtype == 0x01);
|
|
response.extended = (subtype == 0x02);
|
|
if (response.extended
|
|
&& p7_seven_decode_ack(handle, &buffer[8], data_size))
|
|
return (p7_error_checksum);
|
|
if (subtype == 0x03)
|
|
handle->_flags |= p7_intflag_terminated;
|
|
break;
|
|
|
|
/* - for error - */
|
|
case p7_seven_type_error:
|
|
log_info("packet was interpreted as an error one");
|
|
log_info("error is '%s' (0x%02" PRIxP7SHORT ")",
|
|
geterrstring(subtype), subtype);
|
|
response.code = subtype;
|
|
if (subtype == p7_seven_err_fullmem)
|
|
handle->_flags |= p7_intflag_terminated;
|
|
break;
|
|
|
|
/* - for termination - */
|
|
case p7_seven_type_end:
|
|
log_info("packet was interpreted as a terminate one");
|
|
log_info("termination cause is '%s' (0x%02" PRIxP7SHORT ")",
|
|
gettermstring(subtype), subtype);
|
|
response.code = subtype;
|
|
if (subtype == p7_seven_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_seven_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_seven_recv(p7_handle_t *handle, int checksum)
|
|
{
|
|
/* make checks */
|
|
chk_handle(handle);
|
|
|
|
/* main receiving loop */
|
|
int tries = 2;
|
|
int wasresend = 0, err;
|
|
do {
|
|
/* get packet */
|
|
err = p7_seven_recv_main(handle);
|
|
|
|
/* if there was a fail */
|
|
switch (err) {
|
|
/* no error :D */
|
|
case p7_noerror:
|
|
tries = 2;
|
|
break;
|
|
|
|
/* 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_seven_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, check tries and send resend error */
|
|
tries--;
|
|
err = p7_seven_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);
|
|
}
|