2020-09-13 16:56:10 -04:00
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <errno.h>
|
|
|
|
|
2020-09-14 23:24:11 -05:00
|
|
|
#include <patty/ax25.h>
|
2020-09-13 16:56:10 -04:00
|
|
|
#include <patty/kiss.h>
|
|
|
|
|
|
|
|
#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) {
|
2020-09-16 14:53:37 -05:00
|
|
|
size_t o = 0; /* Number of bytes written to buf */
|
2020-09-13 16:56:10 -04:00
|
|
|
|
|
|
|
int flags = STATE_NONE;
|
|
|
|
|
|
|
|
if (tnc->offset == tnc->readlen) {
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
uint8_t c;
|
|
|
|
|
2020-09-16 14:53:37 -05:00
|
|
|
if (o == len) {
|
2020-09-13 16:56:10 -04:00
|
|
|
tnc_drop(tnc);
|
|
|
|
|
|
|
|
flags &= ~STATE_FRAME;
|
2020-09-16 14:53:37 -05:00
|
|
|
o = 0;
|
2020-09-13 16:56:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-09-16 14:53:37 -05:00
|
|
|
if (o > 0) {
|
2020-09-13 16:56:10 -04:00
|
|
|
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:
|
2020-09-16 14:53:37 -05:00
|
|
|
((uint8_t *)buf)[o++] = PATTY_KISS_FEND;
|
2020-09-13 16:56:10 -04:00
|
|
|
flags &= ~STATE_ESCAPE;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
case PATTY_KISS_TFESC:
|
2020-09-16 14:53:37 -05:00
|
|
|
((uint8_t *)buf)[o++] = PATTY_KISS_FESC;
|
2020-09-13 16:56:10 -04:00
|
|
|
flags &= ~STATE_ESCAPE;
|
|
|
|
continue;
|
|
|
|
|
|
|
|
default:
|
|
|
|
errno = EIO;
|
|
|
|
|
|
|
|
goto error_io;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-16 14:53:37 -05:00
|
|
|
((uint8_t *)buf)[o++] = c;
|
2020-09-13 16:56:10 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
if (flags & STATE_FRAME) {
|
|
|
|
tnc_drop(tnc);
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-16 14:53:37 -05:00
|
|
|
return (ssize_t)o;
|
2020-09-13 16:56:10 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|