2023-03-03 00:29:00 +01:00
|
|
|
//---------------------------------------------------------------------------//
|
|
|
|
// ==>/[_]\ fxlink: A community communication tool for CASIO calculators. //
|
|
|
|
// |:::| Made by Lephe' as part of the fxSDK. //
|
|
|
|
// \___/ License: MIT <https://opensource.org/licenses/MIT> //
|
|
|
|
//---------------------------------------------------------------------------//
|
2021-05-25 20:46:27 +02:00
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
#include <fxlink/protocol.h>
|
|
|
|
#include <fxlink/logging.h>
|
2021-05-25 20:46:27 +02:00
|
|
|
#include <stdlib.h>
|
2023-03-03 00:29:00 +01:00
|
|
|
#include <string.h>
|
|
|
|
#include <ctype.h>
|
2023-03-12 20:55:18 +01:00
|
|
|
#include <assert.h>
|
2023-03-03 00:29:00 +01:00
|
|
|
|
|
|
|
bool fxlink_message_is_apptype(struct fxlink_message const *msg,
|
|
|
|
char const *application, char const *type)
|
|
|
|
{
|
|
|
|
return memchr(application, '\0', 17) != NULL
|
|
|
|
&& memchr(type, '\0', 17) != NULL
|
|
|
|
&& !strncmp(msg->application, application, 16)
|
|
|
|
&& !strncmp(msg->type, type, 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
void fxlink_message_free(struct fxlink_message *message, bool free_data)
|
|
|
|
{
|
|
|
|
if(free_data)
|
|
|
|
free(message->data);
|
|
|
|
free(message);
|
|
|
|
}
|
2021-05-25 20:46:27 +02:00
|
|
|
|
|
|
|
//---
|
2023-03-03 00:29:00 +01:00
|
|
|
// Image decoding
|
2021-05-25 20:46:27 +02:00
|
|
|
//---
|
|
|
|
|
|
|
|
static int img_bytes_per_row(int format, int width)
|
|
|
|
{
|
2023-03-03 00:29:00 +01:00
|
|
|
if(format == FXLINK_MESSAGE_IMAGE_RGB565)
|
|
|
|
return 2 * width;
|
|
|
|
if(format == FXLINK_MESSAGE_IMAGE_MONO)
|
|
|
|
return (width + 7) >> 3;
|
|
|
|
if(format == FXLINK_MESSAGE_IMAGE_GRAY)
|
|
|
|
return 2 * ((width + 7) >> 3);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fxlink_message_image_raw *decode_rgb565(void const *pixels,
|
|
|
|
int size, struct fxlink_message_image_raw *raw)
|
|
|
|
{
|
|
|
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_RGB565, raw->width);
|
|
|
|
|
|
|
|
for(int y = 0; y < raw->height; y++) {
|
|
|
|
void const *row = pixels + y * bpr;
|
|
|
|
|
|
|
|
for(int x = 0; x < raw->width; x++) {
|
|
|
|
/* Don't read past the read buffer if the image is incomplete */
|
|
|
|
void const *input = row + 2 * x;
|
|
|
|
uint16_t color = 0;
|
|
|
|
if(input - pixels + 2 <= size) color = *(uint16_t *)input;
|
|
|
|
|
|
|
|
color = ((color & 0xff00) >> 8) | ((color & 0x00ff) << 8);
|
|
|
|
|
|
|
|
raw->data[y][3*x+0] = (color >> 11) << 3;
|
|
|
|
raw->data[y][3*x+1] = ((color >> 5) & 0x3f) << 2;
|
|
|
|
raw->data[y][3*x+2] = (color & 0x1f) << 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fxlink_message_image_raw *decode_mono(void const *pixels,
|
|
|
|
int size, struct fxlink_message_image_raw *raw)
|
|
|
|
{
|
|
|
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_MONO, raw->width);
|
|
|
|
|
|
|
|
for(int y = 0; y < raw->height; y++) {
|
|
|
|
void const *row = pixels + y * bpr;
|
|
|
|
|
|
|
|
for(int x = 0; x < raw->width; x++) {
|
|
|
|
/* Don't read past the read buffer if the image is incomplete */
|
|
|
|
void const *input = row + (x >> 3);
|
|
|
|
int byte = 0;
|
|
|
|
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
|
|
|
int color = (byte & (0x80 >> (x & 7))) ? 0 : 255;
|
|
|
|
|
|
|
|
raw->data[y][3*x+0] = color;
|
|
|
|
raw->data[y][3*x+1] = color;
|
|
|
|
raw->data[y][3*x+2] = color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct fxlink_message_image_raw *decode_gray(void const *pixels,
|
|
|
|
int size, struct fxlink_message_image_raw *raw)
|
|
|
|
{
|
|
|
|
int bpr = img_bytes_per_row(FXLINK_MESSAGE_IMAGE_MONO, raw->width);
|
|
|
|
|
|
|
|
for(int k = 0; k < 2 * raw->height; k++) {
|
|
|
|
void const *row = pixels + k * bpr;
|
|
|
|
int y = k % raw->height;
|
|
|
|
|
|
|
|
for(int x = 0; x < raw->width; x++) {
|
|
|
|
/* Don't read past the read buffer if the image is incomplete */
|
|
|
|
void const *input = row + (x >> 3);
|
|
|
|
int byte = 0;
|
|
|
|
if(input - pixels + 1 <= size) byte = *(uint8_t *)input;
|
|
|
|
|
|
|
|
int color = (byte & (0x80 >> (x & 7)));
|
|
|
|
/* Everything is inverted */
|
|
|
|
if(!color) color = (k >= raw->height) ? 0xaa : 0x55;
|
|
|
|
else color = 0x00;
|
|
|
|
|
|
|
|
raw->data[y][3*x+0] += color;
|
|
|
|
raw->data[y][3*x+1] += color;
|
|
|
|
raw->data[y][3*x+2] += color;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return raw;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fxlink_message_image_raw *fxlink_message_image_decode(
|
|
|
|
struct fxlink_message const *msg)
|
|
|
|
{
|
|
|
|
if(!fxlink_message_is_fxlink_image(msg)
|
|
|
|
&& !fxlink_message_is_fxlink_video(msg))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
struct fxlink_message_image_header *img = msg->data;
|
|
|
|
void *pixels = msg->data + sizeof *img;
|
|
|
|
int pixels_size = msg->size - sizeof *img;
|
|
|
|
|
|
|
|
/* Determine the expected data size */
|
|
|
|
int bytes_per_row = img_bytes_per_row(img->pixel_format, img->width);
|
|
|
|
int expected_size = img->height * bytes_per_row;
|
|
|
|
|
|
|
|
if(pixels_size < expected_size)
|
|
|
|
wlog("image has %d bytes but needs %d, it will be incomplete\n",
|
|
|
|
pixels_size, expected_size);
|
|
|
|
if(pixels_size > expected_size)
|
|
|
|
wlog("image has %d bytes but only needs %d, dropping extra\n",
|
|
|
|
pixels_size, expected_size);
|
|
|
|
|
|
|
|
/* Allocate memory for the decoded pixels */
|
|
|
|
struct fxlink_message_image_raw *raw = malloc(sizeof *raw);
|
|
|
|
uint8_t **data = calloc(img->height, sizeof(uint8_t *));
|
|
|
|
if(!raw || !data)
|
|
|
|
goto alloc_error;
|
|
|
|
for(size_t i = 0; i < img->height; i++) {
|
|
|
|
data[i] = calloc(img->width, 3);
|
|
|
|
if(!data[i])
|
|
|
|
goto alloc_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
raw->width = img->width;
|
|
|
|
raw->height = img->height;
|
|
|
|
raw->data = data;
|
|
|
|
|
|
|
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_RGB565)
|
|
|
|
return decode_rgb565(pixels, pixels_size, raw);
|
|
|
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_MONO)
|
|
|
|
return decode_mono(pixels, pixels_size, raw);
|
|
|
|
if(img->pixel_format == FXLINK_MESSAGE_IMAGE_GRAY)
|
|
|
|
return decode_gray(pixels, pixels_size, raw);
|
|
|
|
|
|
|
|
elog("unknown pixel format: %d\n", img->pixel_format);
|
|
|
|
fxlink_message_image_raw_free(raw);
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
alloc_error:
|
|
|
|
elog("cannot allocate memory for decoded image: %m\n");
|
|
|
|
fxlink_message_image_raw_free(raw);
|
|
|
|
return NULL;
|
2021-05-25 20:46:27 +02:00
|
|
|
}
|
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
void fxlink_message_image_raw_free(struct fxlink_message_image_raw *raw)
|
2021-05-25 20:46:27 +02:00
|
|
|
{
|
2023-03-03 00:29:00 +01:00
|
|
|
if(raw->data){
|
|
|
|
for(int i = 0; i < raw->height; i++)
|
|
|
|
free(raw->data[i]);
|
|
|
|
}
|
|
|
|
free(raw->data);
|
|
|
|
free(raw);
|
|
|
|
}
|
2021-05-25 20:46:27 +02:00
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
//---
|
|
|
|
// Tools for crafting and receiving messages
|
|
|
|
//---
|
2021-05-25 20:46:27 +02:00
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
/* Check whether a header is valid. It's important for this function to be as
|
|
|
|
strict as possible so that picking up communication in the wrong spot
|
|
|
|
doesn't get out of control by recognizing messages everywhere. */
|
|
|
|
static bool header_valid(struct fxlink_message const *msg)
|
|
|
|
{
|
|
|
|
if(msg->version == 0x00000100) {
|
|
|
|
/* Transfer size larger than 16 MB: impossible for so many reasons */
|
|
|
|
if(msg->transfer_size > 0xffffff)
|
|
|
|
return false;
|
|
|
|
/* Non-printable characters in application/type names */
|
|
|
|
for(size_t i = 0; i < sizeof msg->application; i++) {
|
|
|
|
if(msg->application[i] && !isprint(msg->application[i]))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for(size_t i = 0; i < sizeof msg->type; i++) {
|
|
|
|
if(msg->type[i] && !isprint(msg->type[i]))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fxlink_transfer *fxlink_transfer_make_IN(void *data, int size)
|
|
|
|
{
|
|
|
|
int header_size = FXLINK_MESSAGE_HEADER_SIZE;
|
|
|
|
|
|
|
|
if(size < header_size) {
|
|
|
|
elog("cannot read message: header too short (%d < %zu bytes)\n",
|
|
|
|
size, header_size);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct fxlink_transfer *tr = calloc(1, sizeof *tr);
|
|
|
|
if(!tr) {
|
|
|
|
elog("cannot allocate transfer structure: %m\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(&tr->msg, data, header_size);
|
|
|
|
if(!header_valid(&tr->msg)) {
|
|
|
|
elog("cannot read message: invalid header (%d bytes dropped)\n",
|
|
|
|
size);
|
|
|
|
free(tr);
|
|
|
|
return NULL;
|
|
|
|
}
|
2021-05-25 20:46:27 +02:00
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
tr->msg.data = malloc(tr->msg.size);
|
|
|
|
tr->direction = FXLINK_TRANSFER_IN;
|
|
|
|
tr->processed_size = 0;
|
2023-03-12 20:55:18 +01:00
|
|
|
tr->own_data = true;
|
2021-05-25 20:46:27 +02:00
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
if(!tr->msg.data) {
|
|
|
|
elog("cannot allocate buffer for %d bytes\n", tr->msg.size);
|
|
|
|
free(tr);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fxlink_transfer_receive(tr, data + header_size, size - header_size);
|
|
|
|
return tr;
|
2021-05-25 20:46:27 +02:00
|
|
|
}
|
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
struct fxlink_message *fxlink_transfer_finish_IN(struct fxlink_transfer *tr)
|
2021-05-25 20:46:27 +02:00
|
|
|
{
|
2023-03-03 00:29:00 +01:00
|
|
|
if(tr->direction != FXLINK_TRANSFER_IN || !fxlink_transfer_complete(tr))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
struct fxlink_message *msg = malloc(sizeof *msg);
|
|
|
|
if(!msg) {
|
|
|
|
elog("cannot allocate message structure: %m\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Shallow copy the malloc()'d data pointer */
|
2023-03-12 20:55:18 +01:00
|
|
|
assert(tr->own_data && "Can't shallow copy data if we don't own it!");
|
2023-03-03 00:29:00 +01:00
|
|
|
memcpy(msg, &tr->msg, sizeof *msg);
|
|
|
|
free(tr);
|
|
|
|
return msg;
|
2021-05-25 20:46:27 +02:00
|
|
|
}
|
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
void fxlink_transfer_receive(struct fxlink_transfer *tr, void *data, int size)
|
2021-05-25 20:46:27 +02:00
|
|
|
{
|
2023-03-03 00:29:00 +01:00
|
|
|
int remaining_size = tr->msg.size - tr->processed_size;
|
|
|
|
if(size > remaining_size) {
|
|
|
|
elog("message too long, dropping %d bytes\n", size - remaining_size);
|
|
|
|
size = remaining_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(tr->msg.data + tr->processed_size, data, size);
|
|
|
|
tr->processed_size += size;
|
2021-05-25 20:46:27 +02:00
|
|
|
}
|
|
|
|
|
2023-03-04 18:07:30 +01:00
|
|
|
struct fxlink_transfer *fxlink_transfer_make_OUT(char const *application,
|
2023-03-12 20:55:18 +01:00
|
|
|
char const *type, void const *data, int size, bool own_data)
|
2023-03-04 18:07:30 +01:00
|
|
|
{
|
|
|
|
struct fxlink_transfer *tr = calloc(1, sizeof *tr);
|
|
|
|
if(!tr)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
tr->msg.version = 0x00000100;
|
|
|
|
tr->msg.size = size;
|
|
|
|
tr->msg.transfer_size = 0;
|
|
|
|
strncpy(tr->msg.application, application, 16);
|
|
|
|
strncpy(tr->msg.type, type, 16);
|
|
|
|
tr->msg.data = (void *)data;
|
|
|
|
tr->direction = FXLINK_TRANSFER_OUT;
|
|
|
|
tr->processed_size = -1;
|
2023-03-12 20:55:18 +01:00
|
|
|
tr->own_data = own_data;
|
2023-03-04 18:07:30 +01:00
|
|
|
return tr;
|
|
|
|
}
|
|
|
|
|
2023-03-03 00:29:00 +01:00
|
|
|
bool fxlink_transfer_complete(struct fxlink_transfer const *tr)
|
2021-05-25 20:46:27 +02:00
|
|
|
{
|
2023-03-04 18:07:30 +01:00
|
|
|
return tr->processed_size >= (int)tr->msg.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
void fxlink_transfer_free(struct fxlink_transfer *tr)
|
|
|
|
{
|
2023-03-12 20:55:18 +01:00
|
|
|
if(tr->own_data)
|
2023-03-04 18:07:30 +01:00
|
|
|
free(tr->msg.data);
|
|
|
|
free(tr);
|
2021-05-25 20:46:27 +02:00
|
|
|
}
|