/* ***************************************************************************** * protocol/seven/recv.c -- receive and decode the packet. * Copyright (C) 2016-2017 Thomas "Cakeisalie5" Touhey * * 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 . * * These functions are about receiving and decoding a packet. * It is also about managing checksum problems and timeouts. * ************************************************************************** */ #include #include #include #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); }