#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" enum state_flags { STATE_NONE = 0, STATE_FRAME = 1 << 0, STATE_COMMAND = 1 << 1, STATE_ESCAPE = 1 << 2 }; enum tnc_opts { TNC_NONE = 0, TNC_CLOSE_ON_DESTROY = 1 << 0 }; struct _patty_kiss_tnc { struct termios attrs, attrs_old; int fd, opts; void *buf, *frame; size_t bufsz, offset, dropped; ssize_t readlen; }; patty_kiss_tnc *patty_kiss_tnc_new(patty_kiss_tnc_info *info) { patty_kiss_tnc *tnc; if ((tnc = malloc(sizeof(*tnc))) == NULL) { goto error_malloc_tnc; } if ((tnc->buf = malloc(PATTY_KISS_TNC_BUFSZ)) == NULL) { goto error_malloc_buf; } if (info->flags & PATTY_KISS_TNC_DEVICE) { struct stat st; if (strcmp(info->device, "/dev/ptmx") == 0) { int ptysub; if (openpty(&tnc->fd, &ptysub, NULL, NULL, NULL) < 0) { goto error_open; } } else if (stat(info->device, &st) < 0) { goto error_stat; } else if ((st.st_mode & S_IFMT) == S_IFSOCK) { struct sockaddr_un addr; if (strlen(info->device) > sizeof(addr.sun_path)) { errno = EOVERFLOW; goto error_overflow; } if ((tnc->fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { goto error_socket; } memset(&addr, '\0', sizeof(addr)); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, info->device, sizeof(addr.sun_path)-1); if (connect(tnc->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { goto error_connect; } } else { if ((tnc->fd = open(info->device, O_RDWR | O_NOCTTY)) < 0) { goto error_open; } } tnc->opts |= TNC_CLOSE_ON_DESTROY; } else if (info->flags & PATTY_KISS_TNC_FD) { tnc->fd = info->fd; } else { errno = EINVAL; goto error_no_fd; } if (isatty(tnc->fd) && ptsname(tnc->fd) == NULL) { if (tcgetattr(tnc->fd, &tnc->attrs) < 0) { goto error_tcgetattr; } memcpy(&tnc->attrs_old, &tnc->attrs, sizeof(tnc->attrs_old)); cfmakeraw(&tnc->attrs); if (info->flags & PATTY_KISS_TNC_BAUD) { cfsetspeed(&tnc->attrs, info->baud); } if (info->flags & PATTY_KISS_TNC_FLOW) { switch (info->flow) { case PATTY_KISS_TNC_FLOW_NONE: break; case PATTY_KISS_TNC_FLOW_CRTSCTS: tnc->attrs.c_cflag |= CRTSCTS; break; case PATTY_KISS_TNC_FLOW_XONXOFF: tnc->attrs.c_iflag |= IXON | IXOFF; break; } } if (tcflush(tnc->fd, TCIOFLUSH) < 0) { goto error_tcflush; } if (tcsetattr(tnc->fd, TCSANOW, &tnc->attrs) < 0) { goto error_tcsetattr; } } else { errno = 0; } tnc->opts = TNC_NONE; tnc->bufsz = PATTY_KISS_TNC_BUFSZ; tnc->offset = 0; tnc->dropped = 0; tnc->readlen = -1; return tnc; error_tcflush: error_tcsetattr: error_tcgetattr: error_connect: if (info->flags & PATTY_KISS_TNC_DEVICE) { (void)close(tnc->fd); } error_open: error_no_fd: error_socket: error_overflow: error_stat: free(tnc->buf); error_malloc_buf: free(tnc); error_malloc_tnc: return NULL; } void patty_kiss_tnc_destroy(patty_kiss_tnc *tnc) { if (isatty(tnc->fd) && ptsname(tnc->fd) == NULL) { (void)tcsetattr(tnc->fd, TCSANOW, &tnc->attrs_old); } if (tnc->opts & TNC_CLOSE_ON_DESTROY) { (void)close(tnc->fd); } free(tnc->buf); free(tnc); } int patty_kiss_tnc_fd(patty_kiss_tnc *tnc) { return tnc->fd; } static void tnc_drop(patty_kiss_tnc *tnc) { tnc->offset = 0; tnc->readlen = -1; tnc->dropped++; } size_t patty_kiss_tnc_dropped(patty_kiss_tnc *tnc) { return tnc->dropped; } ssize_t patty_kiss_tnc_pending(patty_kiss_tnc *tnc) { if (tnc->readlen < 0) { return -1; } if (tnc->offset == 0) { return 0; } return tnc->readlen - tnc->offset; } ssize_t patty_kiss_tnc_recv(patty_kiss_tnc *tnc, void *buf, size_t len) { size_t w = 0; /* Number of bytes written to buf */ int flags = STATE_NONE; if (tnc->offset == tnc->readlen) { errno = 0; return 0; } while (1) { uint8_t c; if (w == len) { tnc_drop(tnc); flags &= ~STATE_FRAME; w = 0; } if (tnc->offset == 0) { if ((tnc->readlen = read(tnc->fd, tnc->buf, tnc->bufsz)) < 0) { goto error_io; } else if (tnc->readlen == 0) { goto done; } } c = ((uint8_t *)tnc->buf)[tnc->offset++]; if (tnc->offset == tnc->readlen) { tnc->offset = 0; } if (!(flags & STATE_FRAME)) { if (c == PATTY_KISS_FEND) { flags = STATE_FRAME; continue; } else { errno = EIO; goto error_io; } } else { if (c == PATTY_KISS_FEND) { flags = STATE_NONE; if (w > 0) { goto done; } continue; } } if (!(flags & STATE_COMMAND)) { if (PATTY_KISS_COMMAND(c) != PATTY_KISS_DATA) { errno = EIO; goto error_io; } flags |= STATE_COMMAND; continue; } if (!(flags & STATE_ESCAPE)) { if (c == PATTY_KISS_FESC) { flags |= STATE_ESCAPE; continue; } } else { switch (c) { case PATTY_KISS_TFEND: ((uint8_t *)buf)[w++] = PATTY_KISS_FEND; flags &= ~STATE_ESCAPE; continue; case PATTY_KISS_TFESC: ((uint8_t *)buf)[w++] = PATTY_KISS_FESC; flags &= ~STATE_ESCAPE; continue; default: errno = EIO; goto error_io; } } ((uint8_t *)buf)[w++] = c; } done: if (flags & STATE_FRAME) { tnc_drop(tnc); errno = 0; return 0; } return (ssize_t)w; error_io: return -1; } ssize_t patty_kiss_tnc_send(patty_kiss_tnc *tnc, const void *buf, size_t len) { return patty_kiss_frame_send(tnc->fd, buf, len, PATTY_KISS_TNC_PORT); } patty_ax25_if_driver *patty_kiss_tnc_driver() { static patty_ax25_if_driver driver = { .destroy = (patty_ax25_if_driver_destroy *)patty_kiss_tnc_destroy, .fd = (patty_ax25_if_driver_fd *)patty_kiss_tnc_fd, .pending = (patty_ax25_if_driver_pending *)patty_kiss_tnc_pending, .recv = (patty_ax25_if_driver_recv *)patty_kiss_tnc_recv, .send = (patty_ax25_if_driver_send *)patty_kiss_tnc_send }; return &driver; }