AmendHub

Download

jcs

/

subtext

/

serial.c

 

(View History)

jcs   serial: Fix maximum input length calculation when reading serial data Latest amendment: 272 on 2022-11-07

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 a RING 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 bool serial_initialized = false;
62 static short serial_out_refnum = 0, serial_in_refnum = 0;
63 static char serial_input_internal_buf[1024];
64 static char serial_input_buf[128];
65 static size_t serial_input_buf_len = 0;
66 static ParamBlockRec serial_write_pbr = { 0 };
67
68 enum {
69 SERIAL_STATE_IDLE = 0,
70 SERIAL_STATE_ANSWERED,
71 SERIAL_STATE_CONNECTED,
72 SERIAL_STATE_HANGUP
73 };
74
75 struct serial_node {
76 short state;
77 unsigned short obuflen;
78 unsigned char obuf[512];
79 unsigned char ibuf[64];
80 unsigned short ibuflen;
81 unsigned long answered_at;
82 unsigned long write_timeout_since;
83 struct session *session;
84 } the_serial_node;
85
86 void serial_flush(void);
87 void serial_printf(const char *format, ...);
88 char * serial_get_line(bool wait);
89 void serial_wait_for_connect(struct session *session);
90 short serial_input(struct session *session);
91 short serial_output(struct session *session);
92 bool serial_write_with_timeout(long milliseconds);
93 void serial_close(struct session *session);
94
95 struct node_funcs serial_node_funcs = {
96 serial_wait_for_connect,
97 serial_input,
98 serial_output,
99 serial_close
100 };
101
102 void
103 serial_init(void)
104 {
105 Str32 in_port = "\p.AIn";
106 Str32 out_port = "\p.AOut";
107 SerShk flags = { 0 };
108 short baud, error, parity;
109 char *resp;
110 long m;
111
112 if (db->config.modem_port == 0)
113 return;
114
115 if (db->config.modem_port == 2) {
116 /* printer */
117 in_port[2] = 'B';
118 out_port[2] = 'B';
119 }
120
121 if (serial_in_refnum || serial_out_refnum) {
122 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
123 if (serial_in_refnum)
124 CloseDriver(serial_in_refnum);
125 if (serial_out_refnum)
126 CloseDriver(serial_out_refnum);
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 19200",
170 db->config.modem_speed);
171 db->config.modem_speed = 19200;
172 baud = baud19200;
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 warn("bad response to ATE0: \"%s\"", resp == NULL ? "" : resp);
221
222 /* initialize */
223 serial_printf("%s\r", db->config.modem_init);
224 Delay(TICKS_PER_SEC, &m);
225 serial_flush();
226
227 the_serial_node.state = SERIAL_STATE_IDLE;
228 the_serial_node.obuflen = 0;
229 the_serial_node.ibuflen = 0;
230 the_serial_node.answered_at = 0;
231
232 serial_input_buf_len = 0;
233 }
234
235 void
236 serial_hangup(void)
237 {
238 long m;
239
240 logger_printf("[modem] Hanging up");
241
242 /* de-assert DTR */
243 Control(serial_out_refnum, 18, NULL);
244 Delay(TICKS_PER_SEC * 1, &m);
245 /* assert DTR */
246 Control(serial_out_refnum, 17, NULL);
247 }
248
249 void
250 serial_flush(void)
251 {
252 long len;
253
254 for (;;) {
255 SerGetBuf(serial_in_refnum, &len);
256 if (!len)
257 break;
258 if (len > sizeof(serial_input_buf))
259 len = sizeof(serial_input_buf);
260 FSRead(serial_in_refnum, &len, &serial_input_buf);
261 }
262
263 memset(serial_input_buf, 0, sizeof(serial_input_buf));
264 serial_input_buf_len = 0;
265 }
266
267 void
268 serial_printf(const char *format, ...)
269 {
270 static char serial_printf_tbuf[256];
271 va_list ap;
272 size_t len, n;
273 long one;
274
275 va_start(ap, format);
276 len = vsnprintf(serial_printf_tbuf, sizeof(serial_printf_tbuf),
277 format, ap);
278 va_end(ap);
279
280 logger_printf("[modem] Sending: %s", serial_printf_tbuf);
281
282 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
283 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
284 serial_write_pbr.ioParam.ioBuffer = (Ptr)&serial_printf_tbuf;
285 serial_write_pbr.ioParam.ioReqCount = len;
286 serial_write_with_timeout(1000);
287 }
288
289 bool
290 serial_write_with_timeout(long milliseconds)
291 {
292 unsigned long ts = milliseconds / ((double)1000 / (double)60);
293 unsigned long start = Ticks;
294 unsigned long expire = start + ts;
295
296 PBWrite(&serial_write_pbr, true);
297
298 while (serial_write_pbr.ioParam.ioResult == 1) {
299 if (Ticks >= expire) {
300 if (the_serial_node.write_timeout_since == 0)
301 logger_printf("[modem] Timed out waiting %ld milliseconds "
302 "to write %d byte(s), aborting", milliseconds,
303 serial_write_pbr.ioParam.ioReqCount);
304 PBKillIO(&serial_write_pbr, false);
305 return false;
306 }
307 if (Ticks - start > 30)
308 uthread_yield();
309 }
310
311 if (serial_write_pbr.ioParam.ioResult == 0)
312 return true;
313
314 logger_printf("[modem] Error writing to serial: %d",
315 serial_write_pbr.ioParam.ioResult);
316 return false;
317 }
318
319 char *
320 serial_get_line(bool wait)
321 {
322 static char serial_cur_line[sizeof(serial_input_buf)];
323 size_t n;
324 long len, rem;
325 unsigned long started = Time;
326
327 maybe_read:
328 /* append as much new data as we can fit */
329 if (serial_input_buf_len < sizeof(serial_input_buf)) {
330 SerGetBuf(serial_in_refnum, &len);
331 if (len) {
332 len = MIN(len, sizeof(serial_input_buf) - serial_input_buf_len);
333 FSRead(serial_in_refnum, &len,
334 serial_input_buf + serial_input_buf_len);
335 serial_input_buf[serial_input_buf_len + len] = '\0';
336 serial_input_buf_len += len;
337 }
338 }
339
340 find_line:
341 for (n = 0; n < serial_input_buf_len; n++) {
342 if (serial_input_buf[n] == '\r' ||
343 serial_input_buf[n] == '\n') {
344 if (n > 0)
345 memcpy(serial_cur_line, serial_input_buf, n);
346 serial_cur_line[n] = '\0';
347
348 /* eat any trailing newlines */
349 while (n + 1 < serial_input_buf_len &&
350 (serial_input_buf[n + 1] == '\r' ||
351 serial_input_buf[n + 1] == '\n'))
352 n++;
353
354 /* shift remaining data down */
355 rem = serial_input_buf_len - n - 1;
356 if (rem > 0)
357 memmove(serial_input_buf, serial_input_buf + n + 1, rem);
358 else if (rem < 0)
359 panic("bogus serial input remaining %ld", rem);
360 serial_input_buf_len = rem;
361
362 /* skip blank lines */
363 if (serial_cur_line[0] == '\0')
364 goto find_line;
365
366 logger_printf("[modem] Read: %s", serial_cur_line);
367
368 return (char *)&serial_cur_line;
369 }
370 }
371
372 if (wait) {
373 if (serial_input_buf_len >= sizeof(serial_input_buf))
374 panic("serial_get_line with wait but input buffer full");
375 if (Time - started > 5) {
376 warn("Timed out waiting for modem response");
377 return NULL;
378 }
379 goto maybe_read;
380 }
381
382 return NULL;
383 }
384
385 void
386 serial_atexit(void)
387 {
388 serial_init();
389
390 SerSetBuf(serial_in_refnum, (Ptr)&serial_input_internal_buf, 0);
391 if (serial_in_refnum)
392 CloseDriver(serial_in_refnum);
393 if (serial_out_refnum)
394 CloseDriver(serial_out_refnum);
395 }
396
397 void
398 serial_wait_for_connect(struct session *session)
399 {
400 /*
401 * We get here as session setup shortly after we answered the call.
402 * Spin until serial_idle moves us to a connected state and we're
403 * ready for the actual session to proceed, or just end the session
404 * early if the negotiation failed.
405 */
406 while (the_serial_node.state == SERIAL_STATE_ANSWERED)
407 uthread_yield();
408
409 if (the_serial_node.state != SERIAL_STATE_CONNECTED &&
410 session != NULL)
411 session->ending = true;
412 }
413
414 void
415 serial_idle(void)
416 {
417 SerStaRec status;
418 long count, i;
419 char *line;
420
421 SerStatus(serial_in_refnum, &status);
422 if (status.ctsHold != 0)
423 logger_printf("[modem] CTS set");
424 if (status.xOffHold != 0)
425 logger_printf("[modem] XOFF set");
426 if (status.cumErrs != 0)
427 logger_printf("[modem] Reported errors: %d", status.cumErrs);
428
429 switch (the_serial_node.state) {
430 case SERIAL_STATE_IDLE: {
431 char tty[7];
432
433 if ((line = serial_get_line(false)) == NULL)
434 return;
435
436 if (strstr(line, "RING") != NULL) {
437 sprintf(tty, "ttym%d", (db->config.modem_port == 2 ? 1 : 0));
438 the_serial_node.session = session_create(tty, "modem",
439 &serial_node_funcs);
440 if (the_serial_node.session == NULL) {
441 logger_printf("[modem] No free nodes, can't answer call");
442 break;
443 }
444
445 the_serial_node.session->cookie = (void *)&the_serial_node;
446 the_serial_node.session->vt100 = 1;
447 the_serial_node.state = SERIAL_STATE_ANSWERED;
448 the_serial_node.answered_at = Time;
449
450 logger_printf("[modem] Answering call");
451 serial_printf("ATA\r");
452 }
453 break;
454 }
455 case SERIAL_STATE_ANSWERED: {
456 char *connect;
457 long baud; /* optimistic :) */
458 short count;
459
460 if (Time - the_serial_node.answered_at > SERIAL_CONNECT_TIMEOUT) {
461 logger_printf("[modem] Timed out establishing a "
462 "connection, aborting");
463 the_serial_node.session->ending = true;
464 the_serial_node.state = SERIAL_STATE_HANGUP;
465 break;
466 }
467
468 if ((line = serial_get_line(false)) == NULL)
469 break;
470
471 /* sometimes we don't get the beginning of CONNECT */
472 if ((connect = strstr(line, "NECT")) != NULL) {
473 the_serial_node.state = SERIAL_STATE_CONNECTED;
474
475 if (sscanf(connect, "NECT %ld/%n", &baud, &count) == 1 &&
476 count > 0)
477 the_serial_node.session->tspeed = baud;
478 }
479
480 break;
481 }
482 }
483 }
484
485 short
486 serial_input(struct session *session)
487 {
488 struct serial_node *node = (struct serial_node *)session->cookie;
489 unsigned short rlen;
490 long len;
491 short error, n;
492 unsigned char c;
493
494 if (session->ending)
495 return 0;
496
497 if (serial_input_buf_len) {
498 /* drain input buf first */
499 n = MIN(serial_input_buf_len,
500 sizeof(session->ibuf) - session->ibuflen);
501 memcpy(session->ibuf + session->ibuflen, serial_input_buf, n);
502 session->ibuflen += n;
503 if (serial_input_buf_len - n > 0)
504 memmove(serial_input_buf, serial_input_buf + n,
505 serial_input_buf_len - n);
506 serial_input_buf_len -= n;
507 return n;
508 }
509
510 SerGetBuf(serial_in_refnum, &len);
511 if (!len)
512 return 0;
513
514 len = MIN(len, sizeof(session->ibuf) - session->ibuflen);
515 error = FSRead(serial_in_refnum, &len,
516 &session->ibuf + session->ibuflen);
517 if (error == noErr)
518 session->ibuflen += len;
519 else
520 logger_printf("[modem] Error from FSRead: %d", error);
521
522 return len;
523 }
524
525 short
526 serial_output(struct session *session)
527 {
528 if (session->obuflen == 0 || session->ending)
529 return 0;
530
531 if (session->obuflen > sizeof(session->obuf)) {
532 warn("Bogus obuflen %d > %ld", session->obuflen,
533 sizeof(session->obuf));
534 session->ending = true;
535 session_close(session);
536 return 0;
537 }
538
539 memset(&serial_write_pbr, 0, sizeof(serial_write_pbr));
540 serial_write_pbr.ioParam.ioRefNum = serial_out_refnum;
541 serial_write_pbr.ioParam.ioBuffer = (Ptr)&session->obuf;
542 serial_write_pbr.ioParam.ioReqCount = session->obuflen;
543
544 if (!serial_write_with_timeout(2000)) {
545 if (the_serial_node.write_timeout_since == 0)
546 the_serial_node.write_timeout_since = Time;
547
548 if (Time - the_serial_node.write_timeout_since >=
549 SERIAL_WRITE_GIVE_UP) {
550 logger_printf("[modem] Reached %d seconds of write timeouts, "
551 "hanging up", Time - the_serial_node.write_timeout_since);
552 session->ending = true;
553 }
554
555 return 0;
556 }
557
558 the_serial_node.write_timeout_since = 0;
559 if (serial_write_pbr.ioParam.ioReqCount > session->obuflen)
560 warn("serial wrote more than obuflen?");
561 session->obuflen -= serial_write_pbr.ioParam.ioReqCount;
562
563 return serial_write_pbr.ioParam.ioReqCount;
564 }
565
566 void
567 serial_close(struct session *session)
568 {
569 session_logf(session, "Closing serial session");
570 serial_hangup();
571 serial_init();
572 }