AmendHub

Download

jcs

/

subtext

/

serial.c

 

(View History)

jcs   serial: Fix off-by-one Latest amendment: 648 on 2026-09-22

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 /*
18 * On the Macintosh Plus, the RS422 serial ports don't have the GPIn pin
19 * connected like later models do to the Carrier Detect pin on the DCE.
20 * This means we can't properly detect when the call drops, other than
21 * just looking for "NO CARRIER" in the input stream.
22 *
23 * We do have the ability to assert and de-assert DTR so we can forcefully
24 * hangup a call from our end and then reset the modem to put it into a
25 * known good state in preparation for a new call.
26 */
27
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <Serial.h>
32 #include <Devices.h>
33
34 #include "logger.h"
35 #include "serial_local.h"
36 #include "session.h"
37 #include "subtext.h"
38 #include "util.h"
39
40 /* more speeds defined in Inside Macintosh: Serial Driver, may not work */
41 #ifndef baud14400
42 #define baud14400 6
43 #endif
44 #ifndef baud28800
45 #define baud28800 2
46 #endif
47 #ifndef baud38400
48 #define baud38400 1
49 #endif
50
51 /*
52 * The number of seconds after an ATA that we'll allow a connection to
53 * establish and a session to start, before we hang up and reset the
54 * modem.
55 */
56 #define SERIAL_CONNECT_TIMEOUT 45
57
58 /* The number of seconds of continuous write timeouts before we hang up */
59 #define SERIAL_WRITE_GIVE_UP 45
60
61 static short serial_out_refnum = 0, serial_in_refnum = 0;
62 static char serial_input_internal_buf[1024];
63 static char serial_input_buf[128];
64 static size_t serial_input_buf_len = 0;
65 static short serial_rings = 0;
66 static time_t serial_last_ring = 0;
67 static ParamBlockRec serial_write_pbr = { 0 };
68
69 enum {
70 SERIAL_STATE_IDLE = 0,
71 SERIAL_STATE_ANSWERED,
72 SERIAL_STATE_CONNECTED,
73 SERIAL_STATE_HANGUP
74 };
75
76 struct serial_node {
77 short state;
78 unsigned long answered_at;
79 unsigned long write_timeout_since;
80 struct session *session;
81 } the_serial_node;
82
83 void serial_flush(void);
84 void serial_printf(const char *format, ...);
85 char * serial_get_line(bool wait);
86 void serial_wait_for_connect(struct session *session);
87 short serial_input(struct session *session);
88 short serial_output(struct session *session);
89 bool serial_write_with_timeout(long milliseconds);
90 void serial_close(struct session *session);
91
92 struct node_funcs serial_node_funcs = {
93 serial_wait_for_connect,
94 serial_input,
95 serial_output,
96 serial_close
97 };
98
99 void
100 serial_init(void)
101 {
102 Str32 in_port = "\p.AIn";
103 Str32 out_port = "\p.AOut";
104 SerShk flags = { 0 };
105 short baud, error, parity;
106 char *resp;
107 long m;
108
109 if (serial_in_refnum) {
110 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
111 CloseDriver(serial_in_refnum);
112 serial_in_refnum = 0;
113 }
114
115 if (serial_out_refnum) {
116 CloseDriver(serial_out_refnum);
117 serial_out_refnum = 0;
118 }
119
120 if (db->config.modem_port == 0)
121 return;
122
123 if (db->config.modem_port == 2) {
124 /* printer */
125 in_port[2] = 'B';
126 out_port[2] = 'B';
127 }
128
129 if ((error = OpenDriver(in_port, &serial_in_refnum)) != 0)
130 panic("OpenDriver(%s) failed: %d", PtoCstr(in_port), error);
131 if ((error = OpenDriver(out_port, &serial_out_refnum)) != 0)
132 panic("OpenDriver(%s) failed: %d", PtoCstr(out_port), error);
133
134 switch (db->config.modem_speed) {
135 case 300:
136 baud = baud300;
137 break;
138 case 600:
139 baud = baud600;
140 break;
141 case 1200:
142 baud = baud1200;
143 break;
144 case 2400:
145 baud = baud2400;
146 break;
147 case 4800:
148 baud = baud4800;
149 break;
150 case 9600:
151 baud = baud9600;
152 break;
153 case 14400:
154 baud = baud14400;
155 break;
156 case 19200:
157 baud = baud19200;
158 break;
159 case 28800:
160 baud = baud28800;
161 break;
162 case 38400:
163 baud = baud38400;
164 break;
165 case 57600:
166 baud = baud57600;
167 break;
168 default:
169 logger_printf("[modem] Unsupported port speed %ld, using 9600",
170 db->config.modem_speed);
171 db->config.modem_speed = 9600;
172 baud = baud9600;
173 }
174
175 if (strcmp(db->config.modem_parity, "7E1") == 0) {
176 parity = data7 + evenParity + stop10;
177 } else {
178 if (strcmp(db->config.modem_parity, "8N1") != 0) {
179 logger_printf("[modem] Unknown parity \"%s\", using 8N1",
180 db->config.modem_parity);
181 snprintf(db->config.modem_parity,
182 sizeof(db->config.modem_parity), "8N1");
183 }
184 parity = data8 + noParity + stop10;
185 }
186
187 logger_printf("[modem] Initializing %s port at %ld (%s)",
188 (db->config.modem_port == 2 ? "printer" : "modem"),
189 db->config.modem_speed, db->config.modem_parity);
190
191 if ((error = SerReset(serial_in_refnum, baud + parity)) != 0)
192 panic("SerReset(in) failed: %d", error);
193 if ((error = SerReset(serial_out_refnum, baud + parity)) != 0)
194 panic("SerReset(out) failed: %d", error);
195
196 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf,
197 sizeof(serial_input_internal_buf));
198
199 flags.fXOn = true;
200 flags.fInX = true;
201 flags.xOn = 0x11; /* ^Q */
202 flags.xOff = 0x13; /* ^S */
203 flags.fDTR = 1;
204 /* use Control instead of SerHShake to control fDTR */
205 if ((error = Control(serial_out_refnum, 14, &flags)) != 0)
206 panic("Control failed: %d", error);
207
208 /* reset */
209 serial_printf("ATZ\r");
210 Delay(TICKS_PER_SEC * 2, &m);
211 serial_flush();
212
213 /* disable echo */
214 serial_printf("ATE0\r");
215 resp = serial_get_line(true);
216 if (resp && resp[0] == 'A' && resp[1] == 'T' && resp[2] == 'E')
217 /* eat echo */
218 resp = serial_get_line(true);
219 if (!resp || (resp[0] != 'O' || resp[1] != 'K'))
220 logger_printf("[modem] Bad response to ATE0: \"%s\"",
221 resp == NULL ? "" : resp);
222
223 /* initialize */
224 serial_printf("%s\r", db->config.modem_init);
225 Delay(TICKS_PER_SEC, &m);
226 resp = serial_get_line(false);
227 if (!resp || (resp[0] != 'O' || resp[1] != 'K'))
228 logger_printf("[modem] Bad response to init: \"%s\"",
229 resp == NULL ? "" : resp);
230 serial_flush();
231
232 the_serial_node.session = NULL;
233 the_serial_node.state = SERIAL_STATE_IDLE;
234 the_serial_node.answered_at = 0;
235
236 serial_input_buf_len = 0;
237 serial_rings = 0;
238 serial_last_ring = 0;
239 }
240
241 bool
242 serial_reinit(void)
243 {
244 if (the_serial_node.state != SERIAL_STATE_IDLE) {
245 logger_printf("[modem] In use, not re-initializing");
246 return false;
247 }
248
249 logger_printf("[modem] Re-initializing");
250 serial_hangup();
251 serial_init();
252
253 return true;
254 }
255
256 void
257 serial_hangup(void)
258 {
259 long m;
260
261 if (serial_out_refnum == 0)
262 return;
263
264 logger_printf("[modem] Hanging up");
265
266 /* de-assert DTR */
267 Control(serial_out_refnum, 18, NULL);
268 Delay(TICKS_PER_SEC * 1, &m);
269 /* assert DTR */
270 Control(serial_out_refnum, 17, NULL);
271
272 if (the_serial_node.session != NULL)
273 the_serial_node.session->ending = true;
274 }
275
276 void
277 serial_flush(void)
278 {
279 long len;
280 short error;
281
282 for (;;) {
283 error = SerGetBuf(serial_in_refnum, &len);
284 if (error != 0) {
285 warn("SerGetBuf returned %d", error);
286 return;
287 }
288 if (!len)
289 break;
290 if (len > sizeof(serial_input_buf))
291 len = sizeof(serial_input_buf);
292 FSRead(serial_in_refnum, &len, &serial_input_buf);
293 }
294
295 memset(serial_input_buf, 0, sizeof(serial_input_buf));
296 serial_input_buf_len = 0;
297 }
298
299 void
300 serial_printf(const char *format, ...)
301 {
302 static char serial_printf_tbuf[256];
303 va_list ap;
304 size_t len;
305
306 va_start(ap, format);
307 len = vsnprintf(serial_printf_tbuf, sizeof(serial_printf_tbuf),
308 format, ap);
309 va_end(ap);
310
311 logger_printf("[modem] Sending: %s", serial_printf_tbuf);
312
313 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
314 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
315 serial_write_pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf;
316 serial_write_pbr.ioParam.ioReqCount = len;
317 serial_write_with_timeout(1000);
318 }
319
320 bool
321 serial_write_with_timeout(long milliseconds)
322 {
323 unsigned long ts = milliseconds / ((double)1000 / (double)60);
324 unsigned long start = Ticks;
325 unsigned long expire = start + ts;
326
327 PBWrite(&serial_write_pbr, true);
328
329 while (serial_write_pbr.ioParam.ioResult == 1) {
330 if (Ticks >= expire) {
331 if (the_serial_node.write_timeout_since == 0)
332 logger_printf("[modem] Timed out waiting %ld milliseconds "
333 "to write %d byte(s), aborting", milliseconds,
334 serial_write_pbr.ioParam.ioReqCount);
335 PBKillIO(&serial_write_pbr, false);
336 return false;
337 }
338 if (Ticks - start > 30)
339 uthread_yield();
340 }
341
342 if (serial_write_pbr.ioParam.ioResult == 0)
343 return true;
344
345 logger_printf("[modem] Error writing to serial: %d",
346 serial_write_pbr.ioParam.ioResult);
347 return false;
348 }
349
350 char *
351 serial_get_line(bool wait)
352 {
353 static char serial_cur_line[sizeof(serial_input_buf)];
354 size_t n;
355 long len, rem;
356 unsigned long started = Time;
357 short error;
358
359 maybe_read:
360 /* append as much new data as we can fit */
361 if (serial_input_buf_len < sizeof(serial_input_buf) - 1) {
362 error = SerGetBuf(serial_in_refnum, &len);
363 if (error != 0) {
364 logger_printf("[modem] SerGetBuf returned %d", error);
365 return NULL;
366 }
367 if (len) {
368 len = MIN(len, sizeof(serial_input_buf) - 1 -
369 serial_input_buf_len);
370 FSRead(serial_in_refnum, &len,
371 serial_input_buf + serial_input_buf_len);
372 serial_input_buf[serial_input_buf_len + len] = '\0';
373 serial_input_buf_len += len;
374 }
375 }
376
377 find_line:
378 for (n = 0; n < serial_input_buf_len; n++) {
379 if (serial_input_buf[n] == '\r' ||
380 serial_input_buf[n] == '\n') {
381 if (n > 0)
382 memcpy(serial_cur_line, serial_input_buf, n);
383 serial_cur_line[n] = '\0';
384
385 /* eat any trailing newlines */
386 while (n + 1 < serial_input_buf_len &&
387 (serial_input_buf[n + 1] == '\r' ||
388 serial_input_buf[n + 1] == '\n'))
389 n++;
390
391 /* shift remaining data down */
392 rem = serial_input_buf_len - n - 1;
393 if (rem > 0)
394 memmove(serial_input_buf, serial_input_buf + n + 1, rem);
395 else if (rem < 0)
396 panic("bogus serial input remaining %ld", rem);
397 serial_input_buf_len = rem;
398
399 /* skip blank lines */
400 if (serial_cur_line[0] == '\0')
401 goto find_line;
402
403 logger_printf("[modem] Read: %s", serial_cur_line);
404
405 return (char *)&serial_cur_line;
406 }
407 }
408
409 if (wait) {
410 if (serial_input_buf_len > sizeof(serial_input_buf))
411 panic("serial_get_line with wait but input buffer full");
412 if (Time - started > 5) {
413 logger_printf("[modem] Timed out waiting for modem response");
414 return NULL;
415 }
416 goto maybe_read;
417 }
418
419 return NULL;
420 }
421
422 void
423 serial_atexit(void)
424 {
425 serial_init();
426
427 if (serial_in_refnum) {
428 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
429 CloseDriver(serial_in_refnum);
430 }
431 if (serial_out_refnum)
432 CloseDriver(serial_out_refnum);
433 }
434
435 void
436 serial_wait_for_connect(struct session *session)
437 {
438 /*
439 * We get here as session setup shortly after we answered the call.
440 * Spin until serial_idle moves us to a connected state and we're
441 * ready for the actual session to proceed, or just end the session
442 * early if the negotiation failed.
443 */
444 while (the_serial_node.state == SERIAL_STATE_ANSWERED)
445 uthread_yield();
446
447 if (the_serial_node.state != SERIAL_STATE_CONNECTED &&
448 session != NULL)
449 session->ending = true;
450 }
451
452 void
453 serial_idle(void)
454 {
455 SerStaRec status;
456 char *line;
457
458 if (db->config.modem_port == 0)
459 return;
460
461 SerStatus(serial_in_refnum, &status);
462 #if 0
463 if (status.ctsHold != 0)
464 logger_printf("[modem] CTS set");
465 if (status.xOffHold != 0)
466 logger_printf("[modem] XOFF set");
467 #endif
468 if (status.cumErrs != 0)
469 logger_printf("[modem] Reported errors: %d", status.cumErrs);
470
471 switch (the_serial_node.state) {
472 case SERIAL_STATE_IDLE: {
473 char tty[7];
474
475 if ((line = serial_get_line(false)) == NULL)
476 return;
477
478 if (strstr(line, "RING") != NULL) {
479 if (Time - serial_last_ring > 10)
480 serial_rings = 0;
481
482 serial_last_ring = Time;
483 serial_rings++;
484 if (serial_rings < db->config.modem_rings)
485 break;
486
487 sprintf(tty, "ttym%d", (db->config.modem_port == 2 ? 1 : 0));
488 the_serial_node.session = session_create(tty, "modem",
489 &serial_node_funcs);
490 if (the_serial_node.session == NULL) {
491 logger_printf("[modem] No free nodes, can't answer call");
492 break;
493 }
494
495 the_serial_node.session->cookie = (void *)&the_serial_node;
496 the_serial_node.session->vt100 = 1;
497 the_serial_node.state = SERIAL_STATE_ANSWERED;
498 the_serial_node.answered_at = Time;
499
500 logger_printf("[modem] Answering call after %d ring%s",
501 serial_rings, serial_rings == 1 ? "" : "s");
502 serial_printf("ATA\r");
503 }
504 break;
505 }
506 case SERIAL_STATE_ANSWERED: {
507 char *connect;
508 long baud; /* optimistic :) */
509 short count;
510
511 if (Time - the_serial_node.answered_at > SERIAL_CONNECT_TIMEOUT) {
512 logger_printf("[modem] Timed out establishing a "
513 "connection, aborting");
514 the_serial_node.session->ending = true;
515 the_serial_node.state = SERIAL_STATE_HANGUP;
516 break;
517 }
518
519 if ((line = serial_get_line(false)) == NULL)
520 break;
521
522 /* sometimes we don't get the beginning of CONNECT */
523 if ((connect = strstr(line, "NECT")) != NULL) {
524 the_serial_node.state = SERIAL_STATE_CONNECTED;
525
526 if (sscanf(connect, "NECT %ld/%n", &baud, &count) == 1 &&
527 count > 0)
528 the_serial_node.session->tspeed = baud;
529 }
530
531 break;
532 }
533 }
534 }
535
536 short
537 serial_input(struct session *session)
538 {
539 long len;
540 short error, n;
541 short lenwas = session->ibuflen;
542
543 if (session->ending)
544 return 0;
545
546 if (serial_input_buf_len) {
547 /* drain input buf first */
548 n = MIN(serial_input_buf_len,
549 sizeof(session->ibuf) - session->ibuflen);
550 memcpy(session->ibuf + session->ibuflen, serial_input_buf, n);
551 session->ibuflen += n;
552 if (serial_input_buf_len - n > 0)
553 memmove(serial_input_buf, serial_input_buf + n,
554 serial_input_buf_len - n);
555 serial_input_buf_len -= n;
556
557 if (session->ibuflen < lenwas)
558 panic("serial ibuflen shrank");
559 if (session->ibuflen > sizeof(session->ibuf))
560 panic("serial ibuflen bogus");
561 session_check_buf_canaries(session);
562
563 return n;
564 }
565
566 error = SerGetBuf(serial_in_refnum, &len);
567 if (error != 0) {
568 logger_printf("[modem] SerGetBuf returned %d", error);
569 return 0;
570 }
571 if (!len)
572 return 0;
573
574 len = MIN(len, sizeof(session->ibuf) - session->ibuflen);
575 error = FSRead(serial_in_refnum, &len,
576 session->ibuf + session->ibuflen);
577 session_check_buf_canaries(session);
578 if (error == noErr)
579 session->ibuflen += len;
580 else
581 logger_printf("[modem] Error from FSRead: %d", error);
582
583 if (session->ibuflen < lenwas)
584 panic("serial ibuflen shrank");
585 if (session->ibuflen > sizeof(session->ibuf))
586 panic("serial ibuflen bogus");
587
588 return len;
589 }
590
591 short
592 serial_output(struct session *session)
593 {
594 if (session->obuflen == 0 || session->ending)
595 return 0;
596
597 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
598 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
599 serial_write_pbr.ioParam.ioBuffer = (Ptr)&session->obuf;
600 serial_write_pbr.ioParam.ioReqCount = session->obuflen;
601
602 if (!serial_write_with_timeout(2000)) {
603 if (the_serial_node.write_timeout_since == 0)
604 the_serial_node.write_timeout_since = Time;
605
606 if (Time - the_serial_node.write_timeout_since >=
607 SERIAL_WRITE_GIVE_UP) {
608 logger_printf("[modem] Reached %lu seconds of write timeouts, "
609 "hanging up", Time - the_serial_node.write_timeout_since);
610 session->ending = true;
611 }
612
613 return 0;
614 }
615
616 the_serial_node.write_timeout_since = 0;
617 if (serial_write_pbr.ioParam.ioReqCount > session->obuflen) {
618 warn("serial wrote more than obuflen?");
619 session->obuflen = 0;
620 } else
621 session->obuflen -= serial_write_pbr.ioParam.ioReqCount;
622 session_check_buf_canaries(session);
623
624 return serial_write_pbr.ioParam.ioReqCount;
625 }
626
627 void
628 serial_close(struct session *session)
629 {
630 session_logf(session, "Closing serial session");
631 serial_hangup();
632 serial_init();
633 }