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 | } |