/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * Hayes AT-compliant modem emulation. * * * * Authors: Cacodemon345 * The DOSBox Team * * Copyright 2024 Cacodemon345 * Copyright 2002-2021 The DOSBox Team */ #include #include #include #include #include #include #include #include #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/device.h> #include <86box/thread.h> #include <86box/fifo.h> #include <86box/fifo8.h> #include <86box/timer.h> #include <86box/serial.h> #include <86box/plat.h> #include <86box/network.h> #include <86box/version.h> #include <86box/plat_unused.h> #include <86box/plat_netsocket.h> #ifdef ENABLE_MODEM_LOG int modem_do_log = ENABLE_MODEM_LOG; static void modem_log(const char *fmt, ...) { va_list ap; if (modem_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define modem_log(fmt, ...) #endif /* From RFC 1055. */ #define END 0300 /* indicates end of packet */ #define ESC 0333 /* indicates byte stuffing */ #define ESC_END 0334 /* ESC ESC_END means END data byte */ #define ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */ typedef enum ResTypes { ResNONE, ResOK, ResERROR, ResCONNECT, ResRING, ResBUSY, ResNODIALTONE, ResNOCARRIER, ResNOANSWER } ResTypes; enum modem_types { MODEM_TYPE_SLIP = 1, MODEM_TYPE_PPP = 2, MODEM_TYPE_TCPIP = 3 }; typedef enum modem_mode_t { MODEM_MODE_COMMAND = 0, MODEM_MODE_DATA = 1 } modem_mode_t; typedef enum modem_slip_stage_t { MODEM_SLIP_STAGE_USERNAME, MODEM_SLIP_STAGE_PASSWORD } modem_slip_stage_t; #define COMMAND_BUFFER_SIZE 512 #define NUMBER_BUFFER_SIZE 128 #define PHONEBOOK_SIZE 200 typedef struct modem_phonebook_entry_t { char phone[NUMBER_BUFFER_SIZE]; char address[NUMBER_BUFFER_SIZE]; } modem_phonebook_entry_t; typedef struct modem_t { uint8_t mac[6]; serial_t *serial; uint32_t baudrate; modem_mode_t mode; uint8_t esc_character_expected; pc_timer_t host_to_serial_timer; pc_timer_t dtr_timer; pc_timer_t cmdpause_timer; uint8_t tx_pkt_ser_line[0x10000]; /* SLIP-encoded. */ uint32_t tx_count; Fifo8 rx_data; /* Data received from the network. */ uint8_t reg[100]; Fifo8 data_pending; /* Data yet to be sent to the host. */ char cmdbuf[COMMAND_BUFFER_SIZE]; char prevcmdbuf[COMMAND_BUFFER_SIZE]; char numberinprogress[NUMBER_BUFFER_SIZE]; char lastnumber[NUMBER_BUFFER_SIZE]; uint32_t cmdpos; uint32_t port; int plusinc, flowcontrol; int in_warmup, dtrmode; int dcdmode; bool connected, ringing; bool echo, numericresponse; bool tcpIpMode, tcpIpConnInProgress; bool cooldown; bool telnet_mode; bool dtrstate; uint32_t tcpIpConnCounter; int doresponse; int cmdpause; int listen_port; int ringtimer; SOCKET serversocket; SOCKET clientsocket; SOCKET waitingclientsocket; struct { bool binary[2]; bool echo[2]; bool supressGA[2]; bool timingMark[2]; bool inIAC; bool recCommand; uint8_t command; } telClient; modem_phonebook_entry_t entries[PHONEBOOK_SIZE]; uint32_t entries_num; netcard_t *card; } modem_t; #define MREG_AUTOANSWER_COUNT 0 #define MREG_RING_COUNT 1 #define MREG_ESCAPE_CHAR 2 #define MREG_CR_CHAR 3 #define MREG_LF_CHAR 4 #define MREG_BACKSPACE_CHAR 5 #define MREG_GUARD_TIME 12 #define MREG_DTR_DELAY 25 static void modem_do_command(modem_t *modem, int repeat); static void modem_accept_incoming_call(modem_t *modem); extern ssize_t local_getline(char **buf, size_t *bufsiz, FILE *fp); // https://stackoverflow.com/a/122974 char * trim(char *str) { size_t len = 0; char *frontp = str; char *endp = NULL; if (str == NULL) { return NULL; } if (str[0] == '\0') { return str; } len = strlen(str); endp = str + len; /* Move the front and back pointers to address the first non-whitespace * characters from each end. */ while (isspace((unsigned char) *frontp)) { ++frontp; } if (endp != frontp) { while (isspace((unsigned char) *(--endp)) && endp != frontp) { } } if (frontp != str && endp == frontp) *str = '\0'; else if (str + len - 1 != endp) *(endp + 1) = '\0'; /* Shift the string so that it starts at str so that if it's dynamically * allocated, we can still free it on the returned pointer. Note the reuse * of endp to mean the front of the string buffer now. */ endp = str; if (frontp != str) { while (*frontp) { *endp++ = *frontp++; } *endp = '\0'; } return str; } static void modem_read_phonebook_file(modem_t *modem, const char *path) { FILE *file = plat_fopen(path, "r"); char *buf = NULL; char *buf2 = NULL; size_t size = 0; if (!file) return; modem->entries_num = 0; modem_log("Modem: Reading phone book file %s...\n", path); while (local_getline(&buf, &size, file) != -1) { modem_phonebook_entry_t entry = { { 0 }, { 0 } }; buf[strcspn(buf, "\r\n")] = '\0'; /* Remove surrounding whitespace from the input line and find the address part. */ buf = trim(buf); buf2 = &buf[strcspn(buf, " \t")]; /* Remove surrounding whitespace and any extra text from the address part, then store it. */ buf2 = trim(buf2); buf2[strcspn(buf2, " \t")] = '\0'; strncpy(entry.address, buf2, sizeof(entry.address) - 1); /* Split the line to get the phone number part, then store it. */ buf2[0] = '\0'; strncpy(entry.phone, buf, sizeof(entry.phone) - 1); if ((entry.phone[0] == '\0') || (entry.address[0] == '\0')) { /* Appears to be a bad line. */ modem_log("Modem: Skipped a bad line\n"); continue; } if (strspn(entry.phone, "01234567890*=,;#+>") != strlen(entry.phone)) { /* Invalid characters. */ pclog("Modem: Invalid character in phone number %s\n", entry.phone); continue; } modem_log("Modem: Mapped phone number %s to address %s\n", entry.phone, entry.address); modem->entries[modem->entries_num++] = entry; if (modem->entries_num >= PHONEBOOK_SIZE) break; } fclose(file); } static void modem_echo(modem_t *modem, uint8_t c) { if (modem->echo && fifo8_num_free(&modem->data_pending)) fifo8_push(&modem->data_pending, c); } static uint32_t modem_scan_number(char **scan) { char c = 0; uint32_t ret = 0; while (1) { c = **scan; if (c == 0) break; if (c >= '0' && c <= '9') { ret *= 10; ret += c - '0'; *scan = *scan + 1; } else break; } return ret; } static uint8_t modem_fetch_character(char **scan) { uint8_t c = **scan; *scan = *scan + 1; return c; } static void modem_speed_changed(void *priv) { modem_t *dev = (modem_t *) priv; if (!dev) return; timer_stop(&dev->host_to_serial_timer); /* FIXME: do something to dev->baudrate */ timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / (double) dev->baudrate) * 9); #if 0 serial_clear_fifo(dev->serial); #endif } static void modem_send_line(modem_t *modem, const char *line) { fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); fifo8_push_all(&modem->data_pending, (uint8_t *) line, strlen(line)); fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); } static void modem_send_number(modem_t *modem, uint32_t val) { fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); fifo8_push(&modem->data_pending, val / 100 + '0'); val = val % 100; fifo8_push(&modem->data_pending, val / 10 + '0'); val = val % 10; fifo8_push(&modem->data_pending, val + '0'); fifo8_push(&modem->data_pending, modem->reg[MREG_CR_CHAR]); fifo8_push(&modem->data_pending, modem->reg[MREG_LF_CHAR]); } static void process_tx_packet(modem_t *modem, uint8_t *p, uint32_t len) { int received = 0; uint32_t pos = 0; uint8_t *processed_tx_packet = calloc(len, 1); uint8_t c = 0; modem_log("Processing SLIP packet of %u bytes\n", len); while (pos < len) { c = p[pos]; pos++; switch (c) { case END: if (received) goto send_tx_packet; else break; case ESC: { c = p[pos]; pos++; switch (c) { case ESC_END: c = END; break; case ESC_ESC: c = ESC; break; } } default: if (received < len) processed_tx_packet[received++] = c; break; } } send_tx_packet: if (received) { uint8_t *buf = calloc(received + 14, 1); buf[0] = buf[1] = buf[2] = buf[3] = buf[4] = buf[5] = 0xFF; buf[6] = buf[7] = buf[8] = buf[9] = buf[10] = buf[11] = 0xFC; buf[12] = 0x08; buf[13] = 0x00; memcpy(buf + 14, processed_tx_packet, received); network_tx(modem->card, buf, received + 14); free(buf); } free(processed_tx_packet); return; } static void modem_data_mode_process_byte(modem_t *modem, uint8_t data) { if (modem->reg[MREG_ESCAPE_CHAR] <= 127) { if (modem->plusinc >= 1 && modem->plusinc <= 3 && modem->reg[MREG_ESCAPE_CHAR] == data) { modem->plusinc++; } else { modem->plusinc = 0; } } modem->cmdpause = 0; if (modem->tx_count < 0x10000 && modem->connected) { modem->tx_pkt_ser_line[modem->tx_count++] = data; if (data == END && !modem->tcpIpMode) { process_tx_packet(modem, modem->tx_pkt_ser_line, (uint32_t) modem->tx_count); modem->tx_count = 0; } } } static void host_to_modem_cb(void *priv) { modem_t *modem = (modem_t *) priv; if (modem->in_warmup) goto no_write_to_machine; if ((modem->serial->type >= SERIAL_16550) && modem->serial->fifo_enabled) { if (fifo_get_full(modem->serial->rcvr_fifo)) { goto no_write_to_machine; } } else { if (modem->serial->lsr & 1) { goto no_write_to_machine; } } if (!((modem->serial->mctrl & 2) || modem->flowcontrol != 3)) goto no_write_to_machine; if (modem->mode == MODEM_MODE_DATA && fifo8_num_used(&modem->rx_data) && !modem->cooldown) { serial_write_fifo(modem->serial, fifo8_pop(&modem->rx_data)); } else if (fifo8_num_used(&modem->data_pending)) { uint8_t val = fifo8_pop(&modem->data_pending); serial_write_fifo(modem->serial, val); } if (fifo8_num_used(&modem->data_pending) == 0) { modem->cooldown = false; } no_write_to_machine: timer_on_auto(&modem->host_to_serial_timer, (1000000.0 / (double) modem->baudrate) * (double) 9); } static void modem_write(UNUSED(serial_t *s), void *priv, uint8_t txval) { modem_t *modem = (modem_t *) priv; if (modem->mode == MODEM_MODE_COMMAND) { if (modem->cmdpos < 2) { // Ignore everything until we see "AT" sequence. if (modem->cmdpos == 0 && toupper(txval) != 'A') { return; } if (modem->cmdpos == 1 && toupper(txval) != 'T') { if (txval == '/') { // Repeat the last command. modem_echo(modem, txval); modem_log("Repeat last command (%s)\n", modem->prevcmdbuf); modem_do_command(modem, 1); } else { modem_echo(modem, modem->reg[MREG_BACKSPACE_CHAR]); modem->cmdpos = 0; } return; } } else { // Now entering command. if (txval == modem->reg[MREG_BACKSPACE_CHAR]) { if (modem->cmdpos > 2) { modem_echo(modem, txval); modem->cmdpos--; } return; } if (txval == modem->reg[MREG_LF_CHAR]) { return; // Real modem doesn't seem to skip this? } if (txval == modem->reg[MREG_CR_CHAR]) { modem_echo(modem, txval); modem_do_command(modem, 0); return; } } if (modem->cmdpos < 99) { modem_echo(modem, txval); modem->cmdbuf[modem->cmdpos] = txval; modem->cmdpos++; } } else { modem_data_mode_process_byte(modem, txval); } } void modem_send_res(modem_t *modem, const ResTypes response) { char response_str_connect[256] = { 0 }; const char *response_str = NULL; uint32_t code = -1; snprintf(response_str_connect, sizeof(response_str_connect), "CONNECT %u", modem->baudrate); switch (response) { case ResOK: code = 0; response_str = "OK"; break; case ResCONNECT: code = 1; response_str = response_str_connect; break; case ResRING: code = 2; response_str = "RING"; break; case ResNOCARRIER: code = 3; response_str = "NO CARRIER"; break; case ResERROR: code = 4; response_str = "ERROR"; break; case ResNODIALTONE: code = 6; response_str = "NO DIALTONE"; break; case ResBUSY: code = 7; response_str = "BUSY"; break; case ResNOANSWER: code = 8; response_str = "NO ANSWER"; break; case ResNONE: return; } if (modem->doresponse != 1) { if (modem->doresponse == 2 && (response == ResRING || response == ResCONNECT || response == ResNOCARRIER)) { return; } modem_log("Modem response: %s\n", response_str); if (modem->numericresponse && code != ~0) { modem_send_number(modem, code); } else if (response_str != NULL) { modem_send_line(modem, response_str); } // if(CSerial::CanReceiveByte()) // very fast response // if(rqueue->inuse() && CSerial::getRTS()) // { uint8_t rbyte =rqueue->getb(); // CSerial::receiveByte(rbyte); // LOG_MSG("SERIAL: Port %" PRIu8 " modem sending byte %2x back to UART2", // GetPortNumber(), rbyte); // } } } void modem_enter_idle_state(modem_t *modem) { timer_disable(&modem->dtr_timer); modem->connected = false; modem->ringing = false; modem->mode = MODEM_MODE_COMMAND; modem->in_warmup = 0; modem->tcpIpConnInProgress = 0; modem->tcpIpConnCounter = 0; if (modem->waitingclientsocket != (SOCKET) -1) plat_netsocket_close(modem->waitingclientsocket); if (modem->clientsocket != (SOCKET) -1) plat_netsocket_close(modem->clientsocket); modem->clientsocket = modem->waitingclientsocket = (SOCKET) -1; if (modem->serversocket != (SOCKET) -1) { modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); while (modem->waitingclientsocket != (SOCKET) -1) { plat_netsocket_close(modem->waitingclientsocket); modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); } plat_netsocket_close(modem->serversocket); modem->serversocket = (SOCKET) -1; } if (modem->waitingclientsocket != (SOCKET) -1) plat_netsocket_close(modem->waitingclientsocket); modem->waitingclientsocket = (SOCKET) -1; modem->tcpIpMode = false; modem->tcpIpConnInProgress = false; if (modem->listen_port) { modem->serversocket = plat_netsocket_create_server(NET_SOCKET_TCP, modem->listen_port); if (modem->serversocket == (SOCKET) -1) { pclog("Failed to set up server on port %d\n", modem->listen_port); } } serial_set_cts(modem->serial, 1); serial_set_dsr(modem->serial, 1); serial_set_dcd(modem->serial, (!modem->dcdmode ? 1 : 0)); serial_set_ri(modem->serial, 0); } void modem_enter_connected_state(modem_t *modem) { modem_send_res(modem, ResCONNECT); modem->mode = MODEM_MODE_DATA; modem->ringing = false; modem->connected = true; modem->tcpIpMode = true; modem->cooldown = true; modem->tx_count = 0; plat_netsocket_close(modem->serversocket); modem->serversocket = -1; memset(&modem->telClient, 0, sizeof(modem->telClient)); serial_set_dcd(modem->serial, 1); serial_set_ri(modem->serial, 0); } void modem_reset(modem_t *modem) { modem->dcdmode = 1; modem_enter_idle_state(modem); modem->cmdpos = 0; modem->cmdbuf[0] = 0; modem->prevcmdbuf[0] = 0; modem->lastnumber[0] = 0; modem->numberinprogress[0] = 0; modem->flowcontrol = 0; modem->cmdpause = 0; modem->plusinc = 0; modem->dtrmode = 2; memset(&modem->reg, 0, sizeof(modem->reg)); modem->reg[MREG_AUTOANSWER_COUNT] = 0; // no autoanswer modem->reg[MREG_RING_COUNT] = 1; modem->reg[MREG_ESCAPE_CHAR] = '+'; modem->reg[MREG_CR_CHAR] = '\r'; modem->reg[MREG_LF_CHAR] = '\n'; modem->reg[MREG_BACKSPACE_CHAR] = '\b'; modem->reg[MREG_GUARD_TIME] = 50; modem->reg[MREG_DTR_DELAY] = 5; modem->echo = true; modem->doresponse = 0; modem->numericresponse = false; } void modem_dial(modem_t *modem, const char *str) { modem->tcpIpConnCounter = 0; modem->tcpIpMode = false; if (!strncmp(str, "0.0.0.0", sizeof("0.0.0.0") - 1)) { modem_log("Turning on SLIP\n"); modem_enter_connected_state(modem); modem->numberinprogress[0] = 0; modem->tcpIpMode = false; } else { char buf[NUMBER_BUFFER_SIZE] = ""; strncpy(buf, str, sizeof(buf) - 1); strncpy(modem->lastnumber, str, sizeof(modem->lastnumber) - 1); modem_log("Connecting to %s...\n", buf); // Scan host for port uint16_t port; char *hasport = strrchr(buf, ':'); if (hasport) { *hasport++ = 0; port = (uint16_t) atoi(hasport); } else { port = 23; } modem->numberinprogress[0] = 0; modem->clientsocket = plat_netsocket_create(NET_SOCKET_TCP); if (modem->clientsocket == -1) { pclog("Failed to create client socket\n"); modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); return; } if (-1 == plat_netsocket_connect(modem->clientsocket, buf, port)) { pclog("Failed to connect to %s\n", buf); modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); return; } modem->tcpIpConnInProgress = 1; modem->tcpIpConnCounter = 0; } } static bool is_next_token(const char *a, size_t N, const char *b) { // Is 'b' at least as long as 'a'? size_t N_without_null = N - 1; if (strnlen(b, N) < N_without_null) return false; return (strncmp(a, b, N_without_null) == 0); } static const char * modem_get_address_from_phonebook(modem_t *modem, const char *input) { int i = 0; for (i = 0; i < modem->entries_num; i++) { if (strcmp(input, modem->entries[i].phone) == 0) return modem->entries[i].address; } return NULL; } static void modem_do_command(modem_t *modem, int repeat) { int i = 0; char *scanbuf = NULL; if (repeat) { /* Handle the case of A/ being invoked without a previous command to run */ if (modem->prevcmdbuf[0] == '\0') { modem_send_res(modem, ResOK); return; } /* Load the stored previous command line */ strncpy(modem->cmdbuf, modem->prevcmdbuf, sizeof(modem->cmdbuf) - 1); modem->cmdbuf[COMMAND_BUFFER_SIZE - 1] = '\0'; } else { /* Store the command line to be recalled */ strncpy(modem->prevcmdbuf, modem->cmdbuf, sizeof(modem->prevcmdbuf) - 1); modem->prevcmdbuf[COMMAND_BUFFER_SIZE - 1] = '\0'; modem->cmdbuf[modem->cmdpos] = modem->prevcmdbuf[modem->cmdpos] = '\0'; } modem->cmdpos = 0; for (i = 0; i < sizeof(modem->cmdbuf); i++) { modem->cmdbuf[i] = toupper(modem->cmdbuf[i]); } /* AT command set interpretation */ if ((modem->cmdbuf[0] != 'A') || (modem->cmdbuf[1] != 'T')) { modem_send_res(modem, ResERROR); return; } modem_log("Command received: %s (doresponse = %d)\n", modem->cmdbuf, modem->doresponse); scanbuf = &modem->cmdbuf[2]; while (1) { char chr = modem_fetch_character(&scanbuf); switch (chr) { case '+': if (is_next_token("NET", sizeof("NET"), scanbuf)) { // only walk the pointer ahead if the command matches scanbuf += 3; const uint32_t requested_mode = modem_scan_number(&scanbuf); // If the mode isn't valid then stop parsing if (requested_mode != 1 && requested_mode != 0) { modem_send_res(modem, ResERROR); return; } // Inform the user on changes if (modem->telnet_mode != !!requested_mode) { modem->telnet_mode = !!requested_mode; } break; } modem_send_res(modem, ResERROR); return; case 'D': { // Dial. char buffer[NUMBER_BUFFER_SIZE]; char obuffer[NUMBER_BUFFER_SIZE]; char *foundstr = &scanbuf[0]; const char *mappedaddr = NULL; size_t i = 0; if (*foundstr == 'T' || *foundstr == 'P') // Tone/pulse dialing foundstr++; else if (*foundstr == 'L') { // Redial last number if (modem->lastnumber[0] == 0) modem_send_res(modem, ResERROR); else { modem_log("Redialing number %s\n", modem->lastnumber); modem_dial(modem, modem->lastnumber); } return; } if ((!foundstr[0] && !modem->numberinprogress[0]) || ((strlen(modem->numberinprogress) + strlen(foundstr)) > (NUMBER_BUFFER_SIZE - 1))) { // Check for empty or too long strings modem_send_res(modem, ResERROR); modem->numberinprogress[0] = 0; return; } foundstr = trim(foundstr); // Check for ; and return to command mode if found char *semicolon = strchr(foundstr, ';'); if (semicolon != NULL) { modem_log("Semicolon found in number, returning to command mode\n"); strncat(modem->numberinprogress, foundstr, strcspn(foundstr, ";")); scanbuf = semicolon + 1; break; } else { strcat(modem->numberinprogress, foundstr); foundstr = modem->numberinprogress; } modem_log("Dialing number %s\n", foundstr); mappedaddr = modem_get_address_from_phonebook(modem, foundstr); if (mappedaddr) { modem_dial(modem, mappedaddr); return; } if (strlen(foundstr) >= 12) { // Check if supplied parameter only consists of digits bool isNum = true; size_t fl = strlen(foundstr); for (i = 0; i < fl; i++) if (foundstr[i] < '0' || foundstr[i] > '9') isNum = false; if (isNum && (fl > (NUMBER_BUFFER_SIZE - 5))) { // Check if the number is long enough to cause buffer // overflows during the number => IP transformation modem_send_res(modem, ResERROR); modem->numberinprogress[0] = 0; return; } else if (isNum) { // Parameter is a number with at least 12 digits => this cannot // be a valid IP/name // Transform by adding dots size_t j = 0; const size_t foundlen = strlen(foundstr); for (i = 0; i < foundlen; i++) { buffer[j++] = foundstr[i]; // Add a dot after the third, sixth and ninth number if (i == 2 || i == 5 || i == 8) buffer[j++] = '.'; // If the string is longer than 12 digits, // interpret the rest as port if (i == 11 && foundlen > 12) buffer[j++] = ':'; } buffer[j] = 0; foundstr = buffer; // Remove Zeros from beginning of octets size_t k = 0; size_t foundlen2 = strlen(foundstr); for (i = 0; i < foundlen2; i++) { if (i == 0 && foundstr[0] == '0') continue; if (i == 1 && foundstr[0] == '0' && foundstr[1] == '0') continue; if (foundstr[i] == '0' && foundstr[i - 1] == '.') continue; if (foundstr[i] == '0' && foundstr[i - 1] == '0' && foundstr[i - 2] == '.') continue; obuffer[k++] = foundstr[i]; } obuffer[k] = 0; foundstr = obuffer; } } modem_dial(modem, foundstr); return; } case 'I': // Some strings about firmware switch (modem_scan_number(&scanbuf)) { case 3: modem_send_line(modem, "86Box Emulated Modem Firmware V1.00"); break; case 4: modem_send_line(modem, "Modem compiled for 86Box version " EMU_VERSION); break; } break; case 'E': // Echo on/off switch (modem_scan_number(&scanbuf)) { case 0: modem->echo = false; break; case 1: modem->echo = true; break; } break; case 'V': switch (modem_scan_number(&scanbuf)) { case 0: modem->numericresponse = true; break; case 1: modem->numericresponse = false; break; } break; case 'H': // Hang up switch (modem_scan_number(&scanbuf)) { case 0: modem->numberinprogress[0] = 0; if (modem->connected) { modem_send_res(modem, ResNOCARRIER); modem_enter_idle_state(modem); return; } // else return ok } break; case 'O': // Return to data mode switch (modem_scan_number(&scanbuf)) { case 0: if (modem->connected) { modem->mode = MODEM_MODE_DATA; return; } else { modem_send_res(modem, ResERROR); return; } } break; case 'T': // Tone Dial case 'P': // Pulse Dial break; case 'M': // Monitor case 'L': // Volume case 'W': case 'X': modem_scan_number(&scanbuf); break; case 'A': // Answer call { if (modem->waitingclientsocket == -1) { modem_send_res(modem, ResERROR); return; } modem_accept_incoming_call(modem); break; } return; case 'Z': { // Reset and load profiles // scan the number away, if any modem_scan_number(&scanbuf); if (modem->connected) modem_send_res(modem, ResNOCARRIER); modem_reset(modem); break; } case ' ': // skip space break; case 'Q': { // Response options // 0 = all on, 1 = all off, // 2 = no ring and no connect/carrier in answermode const uint32_t val = modem_scan_number(&scanbuf); if (!(val > 2)) { modem->doresponse = val; break; } else { modem_send_res(modem, ResERROR); return; } } case 'S': { // Registers const uint32_t index = modem_scan_number(&scanbuf); if (index >= 100) { modem_send_res(modem, ResERROR); return; // goto ret_none; } while (scanbuf[0] == ' ') scanbuf++; // skip spaces if (scanbuf[0] == '=') { // set register scanbuf++; while (scanbuf[0] == ' ') scanbuf++; // skip spaces const uint32_t val = modem_scan_number(&scanbuf); modem->reg[index] = val; break; } else if (scanbuf[0] == '?') { // get register modem_send_number(modem, modem->reg[index]); scanbuf++; break; } // else // LOG_MSG("SERIAL: Port %" PRIu8 " print reg %" PRIu32 // " with %" PRIu8 ".", // GetPortNumber(), index, reg[index]); } break; case '&': { // & escaped commands char cmdchar = modem_fetch_character(&scanbuf); switch (cmdchar) { case 'C': { const uint32_t val = modem_scan_number(&scanbuf); if (val < 2) modem->dcdmode = val; else { modem_send_res(modem, ResERROR); return; } break; } case 'K': { const uint32_t val = modem_scan_number(&scanbuf); if (val < 5) modem->flowcontrol = val; else { modem_send_res(modem, ResERROR); return; } break; } case 'D': { const uint32_t val = modem_scan_number(&scanbuf); if (val < 4) modem->dtrmode = val; else { modem_send_res(modem, ResERROR); return; } break; } case '\0': // end of string modem_send_res(modem, ResERROR); return; } break; } break; case '\\': { // \ escaped commands char cmdchar = modem_fetch_character(&scanbuf); switch (cmdchar) { case 'N': // error correction stuff - not emulated if (modem_scan_number(&scanbuf) > 5) { modem_send_res(modem, ResERROR); return; } break; case '\0': // end of string modem_send_res(modem, ResERROR); return; } break; } case '\0': modem_send_res(modem, ResOK); return; } } } void modem_dtr_callback_timer(void *priv) { modem_t *dev = (modem_t *) priv; if (dev->connected) { switch (dev->dtrmode) { case 1: modem_log("DTR dropped, returning to command mode (dtrmode = %i)\n", dev->dtrmode); dev->mode = MODEM_MODE_COMMAND; break; case 2: modem_log("DTR dropped, hanging up (dtrmode = %i)\n", dev->dtrmode); modem_send_res(dev, ResNOCARRIER); modem_enter_idle_state(dev); break; case 3: modem_log("DTR dropped, resetting modem (dtrmode = %i)\n", dev->dtrmode); modem_send_res(dev, ResNOCARRIER); modem_reset(dev); break; } } } void modem_dtr_callback(serial_t *serial, int status, void *priv) { modem_t *dev = (modem_t *) priv; dev->dtrstate = !!status; if (status == 1) timer_disable(&dev->dtr_timer); else if (!timer_is_enabled(&dev->dtr_timer)) timer_on_auto(&dev->dtr_timer, 1000000); } static void fifo8_resize_2x(Fifo8 *fifo) { uint32_t pos = 0; uint32_t size = fifo->capacity * 2; uint32_t used = fifo8_num_used(fifo); if (!used) return; uint8_t *temp_buf = calloc(fifo->capacity * 2, 1); if (!temp_buf) { fatal("net_modem: Out Of Memory!\n"); } while (!fifo8_is_empty(fifo)) { temp_buf[pos] = fifo8_pop(fifo); pos++; } pos = 0; fifo8_destroy(fifo); fifo8_create(fifo, size); fifo8_push_all(fifo, temp_buf, used); free(temp_buf); } #define TEL_CLIENT 0 #define TEL_SERVER 1 void modem_process_telnet(modem_t *modem, uint8_t *data, uint32_t size) { uint32_t i = 0; for (i = 0; i < size; i++) { uint8_t c = data[i]; if (modem->telClient.inIAC) { if (modem->telClient.recCommand) { if ((c != 0) && (c != 1) && (c != 3)) { if (modem->telClient.command > 250) { /* Reject anything we don't recognize */ modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 252); modem_data_mode_process_byte(modem, c); /* We won't do crap! */ } } switch (modem->telClient.command) { case 251: /* Will */ if (c == 0) modem->telClient.binary[TEL_SERVER] = true; if (c == 1) modem->telClient.echo[TEL_SERVER] = true; if (c == 3) modem->telClient.supressGA[TEL_SERVER] = true; break; case 252: /* Won't */ if (c == 0) modem->telClient.binary[TEL_SERVER] = false; if (c == 1) modem->telClient.echo[TEL_SERVER] = false; if (c == 3) modem->telClient.supressGA[TEL_SERVER] = false; break; case 253: /* Do */ if (c == 0) { modem->telClient.binary[TEL_CLIENT] = true; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 251); modem_data_mode_process_byte(modem, 0); /* Will do binary transfer */ } if (c == 1) { modem->telClient.echo[TEL_CLIENT] = false; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 252); modem_data_mode_process_byte(modem, 1); /* Won't echo (too lazy) */ } if (c == 3) { modem->telClient.supressGA[TEL_CLIENT] = true; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 251); modem_data_mode_process_byte(modem, 3); /* Will Suppress GA */ } break; case 254: /* Don't */ if (c == 0) { modem->telClient.binary[TEL_CLIENT] = false; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 252); modem_data_mode_process_byte(modem, 0); /* Won't do binary transfer */ } if (c == 1) { modem->telClient.echo[TEL_CLIENT] = false; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 252); modem_data_mode_process_byte(modem, 1); /* Won't echo (fine by me) */ } if (c == 3) { modem->telClient.supressGA[TEL_CLIENT] = true; modem_data_mode_process_byte(modem, 0xff); modem_data_mode_process_byte(modem, 251); modem_data_mode_process_byte(modem, 3); /* Will Suppress GA (too lazy) */ } break; default: break; } modem->telClient.inIAC = false; modem->telClient.recCommand = false; continue; } else { if (c == 249) { /* Go Ahead received */ modem->telClient.inIAC = false; continue; } modem->telClient.command = c; modem->telClient.recCommand = true; if ((modem->telClient.binary[TEL_SERVER]) && (c == 0xff)) { /* Binary data with value of 255 */ modem->telClient.inIAC = false; modem->telClient.recCommand = false; fifo8_push(&modem->rx_data, 0xff); continue; } } } else { if (c == 0xff) { modem->telClient.inIAC = true; continue; } fifo8_push(&modem->rx_data, c); } } } static int modem_rx(void *priv, uint8_t *buf, int io_len) { modem_t *modem = (modem_t *) priv; uint32_t i = 0; if (modem->tcpIpMode) return 0; if (!modem->connected) { /* Drop packet. */ modem_log("Dropping %d bytes\n", io_len - 14); return 0; } while ((io_len) >= (fifo8_num_free(&modem->rx_data) / 2)) { fifo8_resize_2x(&modem->rx_data); } if (!(buf[12] == 0x08 && buf[13] == 0x00)) { modem_log("Dropping %d bytes (non-IP packet (ethtype 0x%02X%02X))\n", io_len - 14, buf[12], buf[13]); return 0; } modem_log("Receiving %d bytes\n", io_len - 14); /* Strip the Ethernet header. */ io_len -= 14; buf += 14; fifo8_push(&modem->rx_data, END); for (i = 0; i < io_len; i++) { switch (buf[i]) { case END: fifo8_push(&modem->rx_data, ESC); fifo8_push(&modem->rx_data, ESC_END); break; case ESC: fifo8_push(&modem->rx_data, ESC); fifo8_push(&modem->rx_data, ESC_ESC); break; default: fifo8_push(&modem->rx_data, buf[i]); break; } } fifo8_push(&modem->rx_data, END); return 1; } static void modem_rcr_cb(UNUSED(struct serial_s *serial), void *priv) { modem_t *dev = (modem_t *) priv; timer_stop(&dev->host_to_serial_timer); /* FIXME: do something to dev->baudrate */ timer_on_auto(&dev->host_to_serial_timer, (1000000.0 / (double) dev->baudrate) * (double) 9); #if 0 serial_clear_fifo(dev->serial); #endif } static void modem_accept_incoming_call(modem_t *modem) { if (modem->waitingclientsocket != -1) { modem->clientsocket = modem->waitingclientsocket; modem->waitingclientsocket = -1; modem_enter_connected_state(modem); modem->in_warmup = 250; } else { modem_enter_idle_state(modem); } } static void modem_cmdpause_timer_callback(void *priv) { modem_t *modem = (modem_t *) priv; uint32_t guard_threshold = 0; timer_on_auto(&modem->cmdpause_timer, 1000); if (modem->tcpIpConnInProgress) { do { int status = plat_netsocket_connected(modem->clientsocket); if (status == -1) { plat_netsocket_close(modem->clientsocket); modem->clientsocket = -1; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); modem->tcpIpConnInProgress = 0; break; } else if (status == 1) { modem_enter_connected_state(modem); modem->tcpIpConnInProgress = 0; break; } modem->tcpIpConnCounter++; if (status < 0 || (status == 0 && modem->tcpIpConnCounter >= 5000)) { plat_netsocket_close(modem->clientsocket); modem->clientsocket = -1; modem_enter_idle_state(modem); modem_send_res(modem, ResNOANSWER); modem->tcpIpConnInProgress = 0; modem->tcpIpMode = 0; break; } } while (0); } if (!modem->connected && modem->waitingclientsocket == -1 && modem->serversocket != -1) { modem->waitingclientsocket = plat_netsocket_accept(modem->serversocket); if (modem->waitingclientsocket != -1) { if (modem->dtrstate == 0 && modem->dtrmode != 0) { modem_enter_idle_state(modem); } else { modem->ringing = true; modem_send_res(modem, ResRING); serial_set_ri(modem->serial, !serial_get_ri(modem->serial)); modem->ringtimer = 3000; modem->reg[MREG_RING_COUNT] = 0; } } } if (modem->ringing) { if (modem->ringtimer <= 0) { modem->reg[MREG_RING_COUNT]++; if ((modem->reg[MREG_AUTOANSWER_COUNT] > 0) && (modem->reg[MREG_RING_COUNT] >= modem->reg[MREG_AUTOANSWER_COUNT])) { modem_accept_incoming_call(modem); return; } modem_send_res(modem, ResRING); serial_set_ri(modem->serial, !serial_get_ri(modem->serial)); modem->ringtimer = 3000; } --modem->ringtimer; } if (modem->in_warmup) { modem->in_warmup--; if (modem->in_warmup == 0) { modem->tx_count = 0; fifo8_reset(&modem->rx_data); } } else if (modem->connected && modem->tcpIpMode) { if (modem->tx_count) { int wouldblock = 0; int res = plat_netsocket_send(modem->clientsocket, modem->tx_pkt_ser_line, modem->tx_count, &wouldblock); if (res <= 0 && !wouldblock) { /* No bytes sent or error. */ modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } else if (res > 0) { if (res == modem->tx_count) { modem->tx_count = 0; } else { memmove(modem->tx_pkt_ser_line, &modem->tx_pkt_ser_line[res], modem->tx_count - res); modem->tx_count -= res; } } } if (modem->connected) { uint8_t buffer[16]; int wouldblock = 0; int res = plat_netsocket_receive(modem->clientsocket, buffer, sizeof(buffer), &wouldblock); if (res > 0) { if (modem->telnet_mode) modem_process_telnet(modem, buffer, res); else fifo8_push_all(&modem->rx_data, buffer, res); } else if (res == 0) { modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } else if (!wouldblock) { modem->tx_count = 0; modem_enter_idle_state(modem); modem_send_res(modem, ResNOCARRIER); } } } modem->cmdpause++; guard_threshold = (uint32_t) (modem->reg[MREG_GUARD_TIME] * 20); if (modem->cmdpause > guard_threshold) { if (modem->plusinc == 0) { modem->plusinc = 1; } else if (modem->plusinc == 4) { modem_log("Escape sequence triggered, returning to command mode\n"); modem->mode = MODEM_MODE_COMMAND; modem_send_res(modem, ResOK); modem->plusinc = 0; } } } /* Initialize the device for use by the user. */ static void * modem_init(const device_t *info) { modem_t *modem = (modem_t *) calloc(1, sizeof(modem_t)); const char *phonebook_file = NULL; memset(modem->mac, 0xfc, 6); modem->port = device_get_config_int("port"); modem->baudrate = device_get_config_int("baudrate"); modem->listen_port = device_get_config_int("listen_port"); modem->telnet_mode = device_get_config_int("telnet_mode"); modem->clientsocket = modem->serversocket = modem->waitingclientsocket = -1; fifo8_create(&modem->data_pending, 0x10000); fifo8_create(&modem->rx_data, 0x10000); timer_add(&modem->dtr_timer, modem_dtr_callback_timer, modem, 0); timer_add(&modem->host_to_serial_timer, host_to_modem_cb, modem, 0); timer_add(&modem->cmdpause_timer, modem_cmdpause_timer_callback, modem, 0); timer_on_auto(&modem->cmdpause_timer, 1000); modem->serial = serial_attach_ex_2(modem->port, modem_rcr_cb, modem_write, modem_dtr_callback, modem); modem_reset(modem); modem->card = network_attach(modem, modem->mac, modem_rx, NULL); phonebook_file = device_get_config_string("phonebook_file"); if (phonebook_file && phonebook_file[0] != 0) { modem_read_phonebook_file(modem, phonebook_file); } return modem; } void modem_close(void *priv) { modem_t *modem = (modem_t *) priv; modem->listen_port = 0; modem_reset(modem); fifo8_destroy(&modem->data_pending); fifo8_destroy(&modem->rx_data); netcard_close(modem->card); free(priv); } // clang-format off static const device_config_t modem_config[] = { { .name = "port", .description = "Serial Port", .type = CONFIG_SELECTION, .default_string = "", .default_int = 0, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "COM1", .value = 0 }, { .description = "COM2", .value = 1 }, { .description = "COM3", .value = 2 }, { .description = "COM4", .value = 3 }, { .description = "" } } }, { .name = "baudrate", .description = "Baud Rate", .type = CONFIG_SELECTION, .default_string = "", .default_int = 115200, .file_filter = NULL, .spinner = { 0 }, .selection = { { .description = "115200", .value = 115200 }, { .description = "57600", .value = 57600 }, { .description = "56000", .value = 56000 }, { .description = "38400", .value = 38400 }, { .description = "19200", .value = 19200 }, { .description = "14400", .value = 14400 }, { .description = "9600", .value = 9600 }, { .description = "7200", .value = 7200 }, { .description = "4800", .value = 4800 }, { .description = "2400", .value = 2400 }, { .description = "1800", .value = 1800 }, { .description = "1200", .value = 1200 }, { .description = "600", .value = 600 }, { .description = "300", .value = 300 }, } }, { .name = "listen_port", .description = "TCP/IP listening port", .type = CONFIG_SPINNER, .spinner = { .min = 0, .max = 32767 }, .default_int = 0 }, { .name = "phonebook_file", .description = "Phonebook File", .type = CONFIG_FNAME, .default_string = "", .file_filter = "Text files (*.txt)|*.txt" }, { .name = "telnet_mode", .description = "Telnet emulation", .type = CONFIG_BINARY, .default_string = "", .default_int = 0 }, { .name = "", .description = "", .type = CONFIG_END } }; // clang-format on const device_t modem_device = { .name = "Standard Hayes-compliant Modem", .internal_name = "modem", .flags = DEVICE_COM, .local = 0, .init = modem_init, .close = modem_close, .reset = NULL, { .poll = NULL }, .speed_changed = modem_speed_changed, .force_redraw = NULL, .config = modem_config };