AmendHub

Download:

jcs

/

subtext

/

amendments

/

490

nomodem: Introduce NoModem, an alternative file transfer protocol

Instead of using ZModem over an already-reliable connection, NoModem
just sends plain binary data with no encoding or checksumming.

jcs made amendment 490 11 months ago
--- nomodem.c Thu Apr 27 00:18:09 2023 +++ nomodem.c Thu Apr 27 00:18:09 2023 @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2023 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <string.h> +#include "nomodem.h" + +struct nomodem_session * +nomodem_send(struct session *s, FILE *fp, char *filename) +{ + struct nomodem_session *ns; + + if (!fp) + return NULL; + + ns = xmalloczero(sizeof(struct nomodem_session)); + if (ns == NULL) + return NULL; + + ns->mode = NOMODEM_MODE_SENDING; + ns->session = s; + ns->file = fp; + strlcpy(ns->file_name, filename, sizeof(ns->file_name)); + + fseek(ns->file, 0, SEEK_END); + ns->file_size = ftell(ns->file); + fseek(ns->file, 0, SEEK_SET); + + /* initiator */ + session_printf(ns->session, "%c%c%c%c%c%c", + (char)NOMODEM_START0, (char)NOMODEM_START1, + (char)NOMODEM_START2, (char)NOMODEM_START3, + (char)NOMODEM_VERSION, (char)NOMODEM_CMD_SEND); + + /* file name size (byte), file name */ + session_printf(ns->session, "%c%s", + (unsigned char)strlen(ns->file_name), ns->file_name); + + /* 32-bit file size (big-endian) */ + session_printf(ns->session, "%c%c%c%c", + (unsigned char)((ns->file_size >> 24) & 0xff), + (unsigned char)((ns->file_size >> 16) & 0xff), + (unsigned char)((ns->file_size >> 8) & 0xff), + (unsigned char)(ns->file_size & 0xff)); + session_flush(ns->session); + + ns->last_input = Time; + ns->need_header_ack = true; + + return ns; +} + +struct nomodem_session * +nomodem_receive(struct session *s, char *path) +{ + struct nomodem_session *ns; + + if (path == NULL) + return NULL; + + ns = xmalloczero(sizeof(struct nomodem_session)); + if (ns == NULL) + return NULL; + + ns->mode = NOMODEM_MODE_RECEIVING; + ns->session = s; + ns->file_path = xstrdup(path); + if (ns->file_path == NULL) { + xfree(&ns); + return NULL; + } + + ns->file = fopen(ns->file_path, "wb+"); + if (ns->file == NULL) { + session_printf(ns->session, "[nomodem] Failed opening %s for writing", + ns->file_path); + xfree(&ns->file_path); + xfree(&ns); + return NULL; + } + + /* initiator */ + session_printf(ns->session, "%c%c%c%c%c%c", + (char)NOMODEM_START0, (char)NOMODEM_START1, + (char)NOMODEM_START2, (char)NOMODEM_START3, + (char)NOMODEM_VERSION, (char)NOMODEM_CMD_RECEIVE); + session_flush(ns->session); + + ns->last_input = Time; + ns->need_header = true; + + return ns; +} +bool +nomodem_timed_out(struct nomodem_session *ns) +{ + if ((Time - ns->last_input) > NOMODEM_TIMEOUT) + return true; + + return false; +} + +unsigned short +nomodem_get_char(struct nomodem_session *ns) +{ + unsigned char b; + + while (session_get_char(ns->session, &b) == 0) { + if (ns->session->ending || nomodem_timed_out(ns)) + return -1; + } + + ns->last_input = Time; + + return (short)b; +} + +bool +nomodem_continue(struct nomodem_session *ns) +{ + size_t size, rsize, pos; + short resp; + unsigned short file_name_size, file_name_pos; + unsigned char b; + + switch (ns->mode) { + case NOMODEM_MODE_SENDING: + if (ns->need_header_ack) { + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size = ((unsigned long)(resp & 0xff)) << 24; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= ((unsigned long)(resp & 0xff)) << 16; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= ((unsigned long)(resp & 0xff)) << 8; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= (unsigned long)(resp & 0xff); + + if (size != ns->file_size) { + session_logf(ns->session, + "[nomodem] Header ack size %ld != file size %ld, " + "canceling", size, ns->file_size); + return false; + } + + ns->need_header_ack = false; + } + + while (ns->session->obuflen > 0) { + session_flush(ns->session); + if (ns->session->ending) + return false; + if (nomodem_timed_out(ns)) + return false; + } + + pos = ftell(ns->file); + if (feof(ns->file)) + return false; + size = fread(ns->session->obuf + 4, 1, + sizeof(ns->session->obuf) - 4, ns->file); + if (size == 0) + return false; + + ns->session->obuf[0] = (size >> 24) & 0xff; + ns->session->obuf[1] = (size >> 16) & 0xff; + ns->session->obuf[2] = (size >> 8) & 0xff; + ns->session->obuf[3] = size & 0xff; + + ns->session->obuflen = size + 4; + session_flush(ns->session); + + /* wait for ack */ + resp = nomodem_get_char(ns); + + if (resp != NOMODEM_ACK) { + session_logf(ns->session, "[nomodem] Failed ack at chunk " + "%ld/%ld: %d", pos, ns->file_size, resp); + return false; + } + + if (feof(ns->file)) + return false; + break; + case NOMODEM_MODE_RECEIVING: + if (ns->need_header) { + file_name_size = nomodem_get_char(ns); + if (file_name_size < 0) + return false; + + /* get filename */ + file_name_pos = 0; + while (file_name_size != 0) { + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + + if (file_name_pos < sizeof(ns->file_name) - 1) { + ns->file_name[file_name_pos++] = (char)resp; + ns->file_name[file_name_pos] = 0; + } + file_name_size--; + } + + /* get file size */ + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + ns->file_size = ((unsigned long)(resp & 0xff)) << 24; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + ns->file_size |= ((unsigned long)(resp & 0xff)) << 16; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + ns->file_size |= ((unsigned long)(resp & 0xff)) << 8; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + ns->file_size |= (unsigned long)(resp & 0xff); + + /* ack */ + session_printf(ns->session, "%c%c%c%c", + (char)((ns->file_size >> 24) & 0xff), + (char)((ns->file_size >> 16) & 0xff), + (char)((ns->file_size >> 8) & 0xff), + (char)(ns->file_size & 0xff)); + + ns->need_header = false; + } + + pos = ftell(ns->file); + if (pos >= ns->file_size) + return false; + + /* get chunk size */ + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size = ((unsigned long)(resp & 0xff)) << 24; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= ((unsigned long)(resp & 0xff)) << 16; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= ((unsigned long)(resp & 0xff)) << 8; + + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + size |= (unsigned long)(resp & 0xff); + + /* TODO: read chunks directly from ibuf */ + for (rsize = 0; rsize < size; rsize++) { + resp = nomodem_get_char(ns); + if (resp < 0) + return false; + b = resp; + if (fwrite(&b, 1, 1, ns->file) != 1) { + session_logf(ns->session, "[nomodem] Failed writing to %s", + ns->file_path); + return false; + } + } + + session_printf(ns->session, "%c", (char)NOMODEM_ACK); + session_flush(ns->session); + + pos = ftell(ns->file); + if (pos >= ns->file_size) + return false; + } + + return true; +} + +void +nomodem_finish(struct nomodem_session **nsp) +{ + struct nomodem_session *ns = (struct nomodem_session *)*nsp; + + if (ns->file_path != NULL) + xfree(&ns->file_path); + + if (ns->file) + fclose(ns->file); + + xfree(nsp); +} --- nomodem.h Wed Apr 26 21:24:47 2023 +++ nomodem.h Wed Apr 26 21:24:47 2023 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __NOMODEM_H__ +#define __NOMODEM_H__ + +#include "session.h" +#include "util.h" + +/* handshake sequence */ +#define NOMODEM_START0 0x01 +#define NOMODEM_START1 0x00 +#define NOMODEM_START2 0x01 +#define NOMODEM_START3 0x00 + +/* then a version */ +#define NOMODEM_VERSION 0x01 + +/* then a command */ +#define NOMODEM_CMD_SEND 0x01 /* we are sending you a file */ +#define NOMODEM_CMD_RECEIVE 0x02 /* please upload a file to us */ + +#define NOMODEM_ACK 0x01 +#define NOMODEM_CANCEL 0x03 /* ^C */ + +#define NOMODEM_TIMEOUT 20 + +enum { + NOMODEM_MODE_SENDING, + NOMODEM_MODE_RECEIVING +}; + +struct nomodem_session { + struct session *session; + + short mode; + FILE *file; + unsigned long file_size; + char file_name[64]; + char *file_path; + bool need_header; + bool need_header_ack; + unsigned long last_input; +}; + +struct nomodem_session * nomodem_send(struct session *s, FILE *fp, + char *filename); +struct nomodem_session * nomodem_receive(struct session *s, char *path); +bool nomodem_timed_out(struct nomodem_session *ns); +bool nomodem_continue(struct nomodem_session *ns); +void nomodem_finish(struct nomodem_session **nsp); +unsigned short nomodem_get_char(struct nomodem_session *ns); + +#endif