diff -uNr a/manifest b/manifest --- a/manifest f50f64528f87b08912da7c4348064f004137b111fe04b4b3ddf0d1277119194ee3a420d83c04378268fe799b5db460401d1afbd718a114d0a2973453c76c79c0 +++ b/manifest 463357900a7ded80694e0480c1c89bd311e0482457ca7b16dcf1c5524ea24ccce07e743b7dbc21828cddbde42fd41ea4d9fabef1298fa96985081874cf0a9beb @@ -8,3 +8,4 @@ 972640 signpost tempest_crypto_ciphers_and_hashes crypto: Serpent, SHA-256, SHA-384 and SHA-512, and HMAC-384, each with its known answers. 972640 signpost tempest_seam_red_and_black seam: what crosses between red and black, red's end and black's end, each its own, and the four calls by which red reaches a black. 972640 signpost tempest_black_software_black black: the software black, which holds the running stations' keys, opens and seals every packet and alone owns the sockets, on threads of its own, and its draws. +972640 signpost tempest_box_usb_backend box: red's box backend, the box at the far end of its USB cable, over Linux's usbfs. diff -uNr a/src/box/backend.c b/src/box/backend.c --- a/src/box/backend.c false +++ b/src/box/backend.c b3f0f8fd323d547aae756699c5e52706134b4a048fdbefa5678df6af1e5e2815895c62bc77aca6e59bdd25f16952088110350e9370c08f85ce7937cfab600d12 @@ -0,0 +1,469 @@ +/* Red's box backend. See box/box.h. + * + * Four rings, each a message whole in a slot of its own: the state commands + * and the packet commands red gives, written by red's threads and taken by + * the cable's; and the state answers and the packets the box gives, written + * by the cable's thread and taken by red's. One thread owns the cable: it + * moves each OUT ring's oldest onto its endpoint when that endpoint is free, + * and each IN endpoint's transfer into its ring, arming it again at once. */ +#include "box/box.h" + +#include "bytes/bytes.h" +#include "log/log.h" + +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + size_t len; + uint8_t bytes[TEMPEST_BOX_MAX_BYTES]; +} slot; + +typedef struct { + pthread_mutex_t lock; + slot *slots; + size_t cap; + size_t head; + size_t count; +} ring; + +/* The OUT endpoints, in the order the thread fills them: state first. */ +enum { STATE, PACKET, KINDS }; +static const unsigned OUT_EP[KINDS] = {TEMPEST_BOX_STATE_OUT, TEMPEST_BOX_PACKET_OUT}; +static const unsigned IN_EP[KINDS] = {TEMPEST_BOX_STATE_IN, TEMPEST_BOX_PACKET_IN}; + +struct tempest_box_backend { + tempest_box_cable cable; + ring out[KINDS]; /* from red, for the box */ + ring in[KINDS]; /* from the box, for red */ + int wake_cable[2]; + int wake_red[2]; + int stop[2]; + pthread_t thread; + int thread_started; + /* The cable thread's own. */ + int is_open; + int out_busy[KINDS]; + tempest_status last_open; + /* Read by any thread. */ + atomic_int opened; + atomic_uint opens; + atomic_int gone; + atomic_uint_least64_t dropped; +}; + +/* ---- rings ------------------------------------------------------------------ */ + +static int ring_init(ring *r, size_t cap) { + r->slots = calloc(cap, sizeof(*r->slots)); + if (r->slots == NULL) { + return 0; + } + r->cap = cap; + r->head = 0; + r->count = 0; + pthread_mutex_init(&r->lock, NULL); + return 1; +} + +static void ring_free(ring *r) { + if (r->slots == NULL) { + return; + } + tempest_secure_memzero(r->slots, r->cap * sizeof(*r->slots)); + free(r->slots); + r->slots = NULL; + pthread_mutex_destroy(&r->lock); +} + +/* Non-blocking: EAGAIN is a full pipe, which already wakes its reader, and + * nothing else befalls a pipe the backend holds open. */ +static void wake(int fd) { + uint8_t byte = 1; + while (write(fd, &byte, 1) < 0 && errno == EINTR) { + } +} + +static void drain(int fd) { + uint8_t sink[64]; + while (read(fd, sink, sizeof(sink)) > 0) { + } +} + +/* Put a copy of `len` bytes in, waking `fd`'s reader if the ring was empty; + * full, it dumps its oldest, counted. */ +static void ring_put(tempest_box_backend *b, ring *r, const uint8_t *bytes, size_t len, + int fd) { + pthread_mutex_lock(&r->lock); + int was_empty = r->count == 0; + int dumped = 0; + if (r->count == r->cap) { + tempest_secure_memzero(&r->slots[r->head], sizeof(r->slots[r->head])); + r->head = (r->head + 1U) % r->cap; + --r->count; + dumped = 1; + } + slot *s = &r->slots[(r->head + r->count) % r->cap]; + s->len = len; + memcpy(s->bytes, bytes, len); + ++r->count; + pthread_mutex_unlock(&r->lock); + if (dumped) { + atomic_fetch_add_explicit(&b->dropped, 1, memory_order_relaxed); + } + if (was_empty) { + wake(fd); + } +} + +/* Put a message taken back where it was, the oldest; with the ring full + * since, it is the oldest and is dumped, counted. */ +static void ring_put_back(tempest_box_backend *b, ring *r, const slot *s) { + pthread_mutex_lock(&r->lock); + int dumped = r->count == r->cap; + if (!dumped) { + r->head = (r->head + r->cap - 1U) % r->cap; + r->slots[r->head] = *s; + ++r->count; + } + pthread_mutex_unlock(&r->lock); + if (dumped) { + atomic_fetch_add_explicit(&b->dropped, 1, memory_order_relaxed); + } +} + +/* Take the oldest whole into `out`, its slot zeroed: 1, or 0 for none. */ +static int ring_take(ring *r, slot *out) { + pthread_mutex_lock(&r->lock); + int got = r->count > 0; + if (got) { + slot *s = &r->slots[r->head]; + *out = *s; + tempest_secure_memzero(s, sizeof(*s)); + r->head = (r->head + 1U) % r->cap; + --r->count; + } + pthread_mutex_unlock(&r->lock); + return got; +} + +/* ---- the cable's thread ------------------------------------------------------ */ + +static void count_drop(tempest_box_backend *b) { + atomic_fetch_add_explicit(&b->dropped, 1, memory_order_relaxed); +} + +/* Each free OUT endpoint given its ring's oldest, state's first. 0, or -1 + * once the box has gone, the command it would not take put back to wait for + * the box with the rest. */ +static int fill_outs(tempest_box_backend *b) { + for (int k = 0; k < KINDS; ++k) { + slot s; + while (!b->out_busy[k] && ring_take(&b->out[k], &s)) { + tempest_status sent = b->cable.out(b->cable.cable, OUT_EP[k], s.bytes, s.len); + if (sent == TEMPEST_STATUS_ERR_IO) { + ring_put_back(b, &b->out[k], &s); + } + tempest_secure_memzero(&s, sizeof(s)); + if (sent == TEMPEST_STATUS_OK) { + b->out_busy[k] = 1; + } else if (sent == TEMPEST_STATUS_ERR_IO) { + return -1; + } else { + count_drop(b); /* that transfer was not made */ + } + } + } + return 0; +} + +static int kind_of_in(unsigned ep) { + return ep == TEMPEST_BOX_STATE_IN ? STATE : ep == TEMPEST_BOX_PACKET_IN ? PACKET : -1; +} + +static int kind_of_out(unsigned ep) { + return ep == TEMPEST_BOX_STATE_OUT ? STATE : ep == TEMPEST_BOX_PACKET_OUT ? PACKET : -1; +} + +/* Every finished transfer taken: an OUT frees its endpoint, an IN's message + * goes to its ring and its endpoint is armed again. 0, or -1 once the box + * has gone. */ +static int reap_all(tempest_box_backend *b) { + for (;;) { + tempest_box_transfer done; + tempest_status got = b->cable.reap(b->cable.cable, &done); + if (got == TEMPEST_STATUS_ERR_NOT_FOUND) { + return 0; + } + if (got != TEMPEST_STATUS_OK) { + tempest_secure_memzero(&done, sizeof(done)); + return -1; + } + int out = kind_of_out(done.ep); + int in = kind_of_in(done.ep); + if (out >= 0) { + b->out_busy[out] = 0; + if (done.status != TEMPEST_STATUS_OK) { + count_drop(b); + } + } else if (in >= 0) { + if (done.status == TEMPEST_STATUS_OK && done.len > 0U && + done.len <= TEMPEST_BOX_MAX_BYTES) { + ring_put(b, &b->in[in], done.bytes, done.len, b->wake_red[1]); + } else { + count_drop(b); /* not one message whole */ + } + if (b->cable.in(b->cable.cable, IN_EP[in]) != TEMPEST_STATUS_OK) { + tempest_secure_memzero(&done, sizeof(done)); + return -1; + } + } + tempest_secure_memzero(&done, sizeof(done)); + } +} + +static void lose(tempest_box_backend *b) { + b->cable.close(b->cable.cable); + b->is_open = 0; + b->out_busy[STATE] = 0; + b->out_busy[PACKET] = 0; + b->last_open = TEMPEST_STATUS_OK; + atomic_store(&b->opened, 0); + tempest_log_message(TEMPEST_LOG_WARN, "box", + "The box left the bus; tempest waits for it to come back."); +} + +/* Open the box if it is there, its reads armed and what waits sent; why it + * is not, logged when that changes. */ +static void try_open(tempest_box_backend *b) { + tempest_status opened = b->cable.open(b->cable.cable); + if (opened != TEMPEST_STATUS_OK) { + if (opened != b->last_open) { + if (opened == TEMPEST_STATUS_ERR_NOT_FOUND) { + tempest_log_message(TEMPEST_LOG_INFO, "box", + "No box is on the bus; tempest waits for one."); + } else if (opened == TEMPEST_STATUS_ERR_BUSY) { + tempest_log_message(TEMPEST_LOG_ERROR, "box", + "The box is held by another program, or two boxes are " + "on the bus; tempest takes it once only one is there " + "and it is plugged in again."); + } else { + tempest_log_message(TEMPEST_LOG_ERROR, "box", + "The box would not open (status %d); tempest tries " + "again when the bus changes.", + (int)opened); + } + } + b->last_open = opened; + return; + } + b->is_open = 1; + b->last_open = TEMPEST_STATUS_OK; + if (b->cable.in(b->cable.cable, TEMPEST_BOX_STATE_IN) != TEMPEST_STATUS_OK || + b->cable.in(b->cable.cable, TEMPEST_BOX_PACKET_IN) != TEMPEST_STATUS_OK || + fill_outs(b) != 0) { + lose(b); + return; + } + atomic_store(&b->opened, 1); + atomic_fetch_add(&b->opens, 1U); + tempest_log_message(TEMPEST_LOG_INFO, "box", "The box is open."); +} + +static void *cable_main(void *arg) { + tempest_box_backend *b = arg; + try_open(b); + for (;;) { + struct pollfd pfd[3]; + pfd[0] = (struct pollfd){.fd = b->stop[0], .events = POLLIN}; + b->cable.poll_on(b->cable.cable, &pfd[1]); + pfd[1].revents = 0; + pfd[2] = (struct pollfd){.fd = b->is_open ? b->wake_cable[0] : -1, .events = POLLIN}; + if (poll(pfd, 3, -1) < 0) { + if (errno == EINTR) { + continue; + } + tempest_log_message(TEMPEST_LOG_ERROR, "box", + "The box's cable thread could not wait (%s); the box is " + "gone to this instance.", + strerror(errno)); + atomic_store(&b->gone, 1); + wake(b->wake_red[1]); + break; + } + if (pfd[0].revents != 0) { + break; + } + if (!b->is_open) { + if (pfd[1].revents != 0) { + try_open(b); + } + continue; + } + if (pfd[2].revents != 0) { + drain(b->wake_cable[0]); + } + if ((pfd[1].revents != 0 && reap_all(b) != 0) || fill_outs(b) != 0) { + lose(b); + } + } + if (b->is_open) { + b->cable.close(b->cable.cable); + b->is_open = 0; + atomic_store(&b->opened, 0); + } + return NULL; +} + +/* ---- the four calls ---------------------------------------------------------- */ + +static tempest_status give(tempest_box_backend *b, int kind, const uint8_t *msg, size_t len) { + if (b == NULL || msg == NULL || len == 0U || len > TEMPEST_BOX_MAX_BYTES) { + return TEMPEST_STATUS_ERR_INVALID; + } + if (atomic_load(&b->gone)) { + return TEMPEST_STATUS_ERR_IO; + } + ring_put(b, &b->out[kind], msg, len, b->wake_cable[1]); + return TEMPEST_STATUS_OK; +} + +tempest_status tempest_box_backend_state(tempest_box_backend *backend, + const uint8_t *msg, + size_t len) { + return give(backend, STATE, msg, len); +} + +tempest_status tempest_box_backend_packet(tempest_box_backend *backend, + const uint8_t *msg, + size_t len) { + return give(backend, PACKET, msg, len); +} + +tempest_status tempest_box_backend_next(tempest_box_backend *backend, + uint8_t *msg, + size_t cap, + size_t *len) { + if (backend == NULL || msg == NULL || len == NULL || cap < TEMPEST_BOX_MAX_BYTES) { + return TEMPEST_STATUS_ERR_INVALID; + } + slot s; + if (ring_take(&backend->in[STATE], &s) || ring_take(&backend->in[PACKET], &s)) { + memcpy(msg, s.bytes, s.len); + *len = s.len; + tempest_secure_memzero(&s, sizeof(s)); + return TEMPEST_STATUS_OK; + } + return atomic_load(&backend->gone) ? TEMPEST_STATUS_ERR_IO : TEMPEST_STATUS_ERR_NOT_FOUND; +} + +int tempest_box_backend_wake_fd(const tempest_box_backend *backend) { + return backend != NULL ? backend->wake_red[0] : -1; +} + +uint64_t tempest_box_backend_dropped(const tempest_box_backend *backend) { + return backend != NULL ? atomic_load_explicit(&backend->dropped, memory_order_relaxed) : 0U; +} + +int tempest_box_backend_open(const tempest_box_backend *backend) { + return backend != NULL ? atomic_load(&backend->opened) : 0; +} + +unsigned tempest_box_backend_opens(const tempest_box_backend *backend) { + return backend != NULL ? atomic_load(&backend->opens) : 0U; +} + +/* ---- start and stop ------------------------------------------------------------ */ + +static int pipe_nonblocking(int fds[2]) { + if (pipe(fds) != 0) { + fds[0] = -1; + fds[1] = -1; + return 0; + } + for (int i = 0; i < 2; ++i) { + int flags = fcntl(fds[i], F_GETFL, 0); + if (flags < 0 || fcntl(fds[i], F_SETFL, flags | O_NONBLOCK) != 0 || + fcntl(fds[i], F_SETFD, FD_CLOEXEC) != 0) { + return 0; + } + } + return 1; +} + +static void close_pipe(int fds[2]) { + for (int i = 0; i < 2; ++i) { + if (fds[i] >= 0) { + close(fds[i]); + fds[i] = -1; + } + } +} + +static void backend_free(tempest_box_backend *b) { + if (b->cable.free != NULL) { + b->cable.free(b->cable.cable); + } + for (int k = 0; k < KINDS; ++k) { + ring_free(&b->out[k]); + ring_free(&b->in[k]); + } + close_pipe(b->wake_cable); + close_pipe(b->wake_red); + close_pipe(b->stop); + tempest_secure_memzero(b, sizeof(*b)); + free(b); +} + +tempest_status tempest_box_backend_start(const tempest_box_cable *cable, + tempest_box_backend **out) { + if (cable == NULL || out == NULL) { + return TEMPEST_STATUS_ERR_INVALID; + } + *out = NULL; + tempest_box_backend *b = calloc(1, sizeof(*b)); + if (b == NULL) { + cable->free(cable->cable); + return TEMPEST_STATUS_ERR_NOMEM; + } + b->cable = *cable; + b->wake_cable[0] = b->wake_cable[1] = -1; + b->wake_red[0] = b->wake_red[1] = -1; + b->stop[0] = b->stop[1] = -1; + b->last_open = TEMPEST_STATUS_OK; + if (!ring_init(&b->out[STATE], TEMPEST_BOX_STATE_RING) || + !ring_init(&b->out[PACKET], TEMPEST_BOX_PACKET_RING) || + !ring_init(&b->in[STATE], TEMPEST_BOX_STATE_RING) || + !ring_init(&b->in[PACKET], TEMPEST_BOX_PACKET_RING)) { + backend_free(b); + return TEMPEST_STATUS_ERR_NOMEM; + } + if (!pipe_nonblocking(b->wake_cable) || !pipe_nonblocking(b->wake_red) || + !pipe_nonblocking(b->stop)) { + backend_free(b); + return TEMPEST_STATUS_ERR_IO; + } + if (pthread_create(&b->thread, NULL, cable_main, b) != 0) { + backend_free(b); + return TEMPEST_STATUS_ERR_IO; + } + b->thread_started = 1; + *out = b; + return TEMPEST_STATUS_OK; +} + +void tempest_box_backend_stop(tempest_box_backend *backend) { + if (backend == NULL) { + return; + } + if (backend->thread_started) { + wake(backend->stop[1]); + pthread_join(backend->thread, NULL); + } + backend_free(backend); +} diff -uNr a/src/box/box.h b/src/box/box.h --- a/src/box/box.h false +++ b/src/box/box.h 6034a83d52f18779ffb5bcfb6f5a4a7a9bc0adaa8b0a635e4b24039ef9b56a3c0a0677dca29a7a5d2e35d9cbc9a5c719f094895916336f60ced0d23f8d58c639 @@ -0,0 +1,140 @@ +/* Red's box backend (VARIATIONS.md, Carriers): red's end of the USB cable to + * the box, which is a black on an FPGA. It + * answers to the four calls every black answers to (seam/black_ops.h), and + * carries each message whole, one bulk transfer of its own: state commands + * on OUT 1 and their answers on IN 2, packets on OUT 3 and theirs on IN 4. + * It reads no message: the call a command + * came by, and the endpoint a message came on, say which kind it is. + * + * One thread alone owns the cable, and its only wait is a poll() that a stop + * always ends. It keeps a read armed on each IN endpoint and at most one + * write in flight on each OUT endpoint, so state never waits behind a + * packet, and a blocking transfer never parks the thread where only a + * timeout lets it out. Every ring here dumps + * its oldest when full, and counts it. + * + * When the box leaves the bus the thread closes the cable and waits for it + * to come back, by an event and never by a clock; commands given meanwhile + * wait in the rings, one the cable would not take among them, and a box + * that lost its power answers *not started*, at which red starts it again + * (VARIATIONS.md, The red node). What was in flight on the cable when + * it went is lost, as a packet lost on the network is. */ +#ifndef TEMPEST_BOX_H +#define TEMPEST_BOX_H + +#include "status/status.h" + +#include +#include +#include + +/* The endpoints, by their addresses on the bus. */ +#define TEMPEST_BOX_STATE_OUT 0x01U +#define TEMPEST_BOX_STATE_IN 0x82U +#define TEMPEST_BOX_PACKET_OUT 0x03U +#define TEMPEST_BOX_PACKET_IN 0x84U + +/* The longest message, SEND and RED IN: what `next` must have room for. */ +#define TEMPEST_BOX_MAX_BYTES 509U +/* An IN is read into this much, a whole number of full-speed packets, so + * that a transfer of a message ends at the box's short packet. */ +#define TEMPEST_BOX_TRANSFER_BYTES 512U + +/* How many messages each ring holds. A state ring holds the burst red gives + * when STARTED comes, an ADD KEY for every key of every station and the few + * commands beside them, and the burst of their answers; a packet ring some + * two seconds of the cable at full speed. */ +#define TEMPEST_BOX_STATE_RING 4096U +#define TEMPEST_BOX_PACKET_RING 4096U + +/* A transfer the cable has finished. */ +typedef struct { + unsigned ep; + /* OK: it went whole, and an IN's bytes are its message. ERR_FORMAT: it + * did not, and is dropped; its endpoint is free again. */ + tempest_status status; + size_t len; + uint8_t bytes[TEMPEST_BOX_TRANSFER_BYTES]; +} tempest_box_transfer; + +/* The cable, as the backend's thread drives it: usbfs's asynchronous + * transfers, or a simulation of the box in a test. Only that thread + * calls these. ERR_IO from any of them means the box has left the bus: the + * thread closes the cable and opens it again once its descriptor says the + * box may be back. */ +typedef struct { + void *cable; + /* The box, if it is on the bus: OK, open; ERR_NOT_FOUND, not there; + * ERR_BUSY, there but another holds it, or two are there. */ + tempest_status (*open)(void *cable); + /* Let it go, whatever is in flight. */ + void (*close)(void *cable); + /* What to poll: while closed, an event that the box may have come onto + * the bus; while open, that a transfer is done or the box has gone. */ + void (*poll_on)(void *cable, struct pollfd *pfd); + /* One whole transfer OUT, of len bytes at most TEMPEST_BOX_MAX_BYTES. */ + tempest_status (*out)(void *cable, unsigned ep, const uint8_t *bytes, size_t len); + /* A read of one whole transfer IN, into TEMPEST_BOX_TRANSFER_BYTES. */ + tempest_status (*in)(void *cable, unsigned ep); + /* One finished transfer into *done: OK; ERR_NOT_FOUND, none is. */ + tempest_status (*reap)(void *cable, tempest_box_transfer *done); + /* Free the cable, closed. */ + void (*free)(void *cable); +} tempest_box_cable; + +typedef struct tempest_box_backend tempest_box_backend; + +/* Start the backend on a thread of its own, owning `cable` from then until + * stop frees it. It opens the box at once if it is on the bus, and else when + * it comes. ERR_NOMEM or ERR_IO when its memory or thread cannot be had, the + * cable freed. */ +tempest_status tempest_box_backend_start(const tempest_box_cable *cable, + tempest_box_backend **out); + +/* Give the box a state command, its `len` bytes in the wire form, and + * return at once: copied into a ring that, full, dumps its oldest. OK, or + * ERR_INVALID for a length of 0 or past TEMPEST_BOX_MAX_BYTES. Any thread. */ +tempest_status tempest_box_backend_state(tempest_box_backend *backend, + const uint8_t *msg, + size_t len); + +/* Give it a packet command, as the state call does a state command. */ +tempest_status tempest_box_backend_packet(tempest_box_backend *backend, + const uint8_t *msg, + size_t len); + +/* Take a message the box gave into `msg`, which holds `cap` bytes, and its + * length into *len: a state answer or report while one waits, and a packet + * only then. ERR_NOT_FOUND when none waits; ERR_INVALID, with nothing taken, + * when `cap` is less than TEMPEST_BOX_MAX_BYTES. Any thread. */ +tempest_status tempest_box_backend_next(tempest_box_backend *backend, + uint8_t *msg, + size_t cap, + size_t *len); + +/* Readable when a message may wait: read it empty, then take until + * ERR_NOT_FOUND, and none is missed. */ +int tempest_box_backend_wake_fd(const tempest_box_backend *backend); + +/* What its rings have dumped, their oldest, and the transfers that did not + * go whole, since it started. */ +uint64_t tempest_box_backend_dropped(const tempest_box_backend *backend); + +/* Whether it holds the box open now: 1 or 0; and how many times it has + * opened it since it started. */ +int tempest_box_backend_open(const tempest_box_backend *backend); +unsigned tempest_box_backend_opens(const tempest_box_backend *backend); + +/* End it: its thread joined, the cable closed and freed, its rings zeroed, + * and it freed. */ +void tempest_box_backend_stop(tempest_box_backend *backend); + +/* The box on this machine's bus, 1209:0001, reached through Linux's usbfs + * (/dev/bus/usb) and found in sysfs (/sys/bus/usb/devices): `sysfs` and + * `dev` name those roots, NULL for the system's, so that a test can stand a + * tree of its own in their place. ERR_IO when the bus cannot be watched. */ +tempest_status tempest_box_usbfs_cable(const char *sysfs, + const char *dev, + tempest_box_cable *out); + +#endif /* TEMPEST_BOX_H */ diff -uNr a/src/box/usbfs.c b/src/box/usbfs.c --- a/src/box/usbfs.c false +++ b/src/box/usbfs.c 33081cc412d57f1a0161aa54d28e08d24fbc8d36e69e6f357900cc434348d425486e699da6d87961fa18085ac58cd9e914475de87efd168285941db902978877 @@ -0,0 +1,375 @@ +/* The box's cable through Linux's usbfs. See box/box.h. + * + * The box is the one device on the bus that is 1209:0001, found in sysfs and + * opened at its node under /dev/bus/usb, its interface claimed so that no + * other program takes its endpoints while tempest holds them. Each transfer + * is an asynchronous one: submitted (USBDEVFS_SUBMITURB), run on the bus by + * the host controller, and reaped (USBDEVFS_REAPURBNDELAY) once the node + * polls writable, which it does while a finished transfer waits. Nothing + * here blocks. Closing the node cancels what is in flight, the kernel + * holding its own copy of every buffer. + * + * While the box is not open, inotify on /dev/bus/usb says when the bus may + * have changed: a node made or removed, or its permissions set after it, as + * udev does; each is a reason to look again. */ +#include "box/box.h" + +#include "bytes/bytes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BOX_VENDOR 0x1209U +#define BOX_PRODUCT 0x0001U +#define BOX_INTERFACE 0U +#define ROOT_BYTES 256U + +/* The four endpoints' slots: state OUT and IN, packet OUT and IN. */ +enum { STATE_OUT, STATE_IN, PACKET_OUT, PACKET_IN, ENDPOINTS }; + +typedef struct { + char sysfs[ROOT_BYTES]; + char dev[ROOT_BYTES]; + int watch; /* inotify on dev and each bus's directory in it */ + int fd; /* the box's node; -1 while closed */ + unsigned max_packet[ENDPOINTS]; + /* Each its own allocation: a transfer ends in a flexible array, the + * frames of an isochronous one, and so cannot be an array's element. */ + struct usbdevfs_urb *urb[ENDPOINTS]; + uint8_t buf[ENDPOINTS][TEMPEST_BOX_TRANSFER_BYTES]; +} usbfs; + +static int slot_of(unsigned ep) { + switch (ep) { + case TEMPEST_BOX_STATE_OUT: return STATE_OUT; + case TEMPEST_BOX_STATE_IN: return STATE_IN; + case TEMPEST_BOX_PACKET_OUT: return PACKET_OUT; + case TEMPEST_BOX_PACKET_IN: return PACKET_IN; + default: return -1; + } +} + +/* ---- sysfs ------------------------------------------------------------------- */ + +/* A small sysfs file's first line, as a number in `base`: 1, or 0. */ +static int read_number(const char *path, int base, unsigned long *value) { + FILE *f = fopen(path, "re"); + if (f == NULL) { + return 0; + } + char line[32]; + int got = fgets(line, sizeof(line), f) != NULL; + fclose(f); + if (!got) { + return 0; + } + char *end = NULL; + errno = 0; + *value = strtoul(line, &end, base); + return errno == 0 && end != line && (*end == '\n' || *end == '\0'); +} + +static int read_attr(const usbfs *u, const char *device, const char *attr, int base, + unsigned long *value) { + char path[ROOT_BYTES + 128U]; + int n = snprintf(path, sizeof(path), "%s/%s/%s", u->sysfs, device, attr); + return n > 0 && (size_t)n < sizeof(path) && read_number(path, base, value); +} + +/* The box's device in sysfs: OK and its name; ERR_NOT_FOUND for none, and + * ERR_BUSY for two or more, which one to take being no one's to guess. */ +static tempest_status find_box(const usbfs *u, char *name, size_t cap) { + DIR *dir = opendir(u->sysfs); + if (dir == NULL) { + return TEMPEST_STATUS_ERR_NOT_FOUND; + } + int found = 0; + struct dirent *e; + while ((e = readdir(dir)) != NULL) { + /* A device's own directory; an interface's has a ':' in its name. */ + if (e->d_name[0] == '.' || strchr(e->d_name, ':') != NULL) { + continue; + } + unsigned long vendor = 0; + unsigned long product = 0; + if (read_attr(u, e->d_name, "idVendor", 16, &vendor) && + read_attr(u, e->d_name, "idProduct", 16, &product) && vendor == BOX_VENDOR && + product == BOX_PRODUCT) { + if (found++ == 0) { + (void)snprintf(name, cap, "%s", e->d_name); + } + } + } + closedir(dir); + return found == 0 ? TEMPEST_STATUS_ERR_NOT_FOUND + : found == 1 ? TEMPEST_STATUS_OK : TEMPEST_STATUS_ERR_BUSY; +} + +/* ---- watching the bus ------------------------------------------------------------ */ + +/* Watch dev for buses coming, and each bus's directory for nodes made, + * removed, or given their permissions. Adding a watch already held is + * harmless, so this is done at every look. 1, or 0 when dev itself cannot + * be watched. */ +static int watch_bus(usbfs *u) { + if (inotify_add_watch(u->watch, u->dev, IN_CREATE | IN_DELETE) < 0) { + return 0; + } + DIR *dir = opendir(u->dev); + if (dir == NULL) { + return 0; + } + struct dirent *e; + while ((e = readdir(dir)) != NULL) { + if (e->d_name[0] == '.') { + continue; + } + char path[ROOT_BYTES + 64U]; + int n = snprintf(path, sizeof(path), "%s/%s", u->dev, e->d_name); + if (n > 0 && (size_t)n < sizeof(path)) { + /* A bus gone between the read and the watch is no bus. */ + (void)inotify_add_watch(u->watch, path, IN_CREATE | IN_DELETE | IN_ATTRIB); + } + } + closedir(dir); + return 1; +} + +static void drain_watch(usbfs *u) { + uint8_t events[4096]; + while (read(u->watch, events, sizeof(events)) > 0) { + } +} + +/* ---- the cable's calls ----------------------------------------------------------- */ + +static void cable_close(void *cable) { + usbfs *u = cable; + if (u->fd >= 0) { + close(u->fd); + u->fd = -1; + } + tempest_secure_memzero(u->buf, sizeof(u->buf)); + for (int i = 0; i < ENDPOINTS; ++i) { + if (u->urb[i] != NULL) { + memset(u->urb[i], 0, sizeof(*u->urb[i])); + } + } +} + +static tempest_status cable_open(void *cable) { + usbfs *u = cable; + drain_watch(u); + (void)watch_bus(u); + char name[128]; + tempest_status found = find_box(u, name, sizeof(name)); + if (found != TEMPEST_STATUS_OK) { + return found; + } + unsigned long bus = 0; + unsigned long devnum = 0; + if (!read_attr(u, name, "busnum", 10, &bus) || !read_attr(u, name, "devnum", 10, &devnum)) { + return TEMPEST_STATUS_ERR_NOT_FOUND; /* gone as it was read */ + } + static const char *const ENDPOINT_DIRS[ENDPOINTS] = {"ep_01", "ep_82", "ep_03", "ep_84"}; + for (int i = 0; i < ENDPOINTS; ++i) { + /* The interface's directory is named after the device's, in its + * first configuration. */ + char endpoint[sizeof(name) + 32U]; + (void)snprintf(endpoint, sizeof(endpoint), "%s:1.%u/%s", name, BOX_INTERFACE, + ENDPOINT_DIRS[i]); + unsigned long max = 0; + if (!read_attr(u, endpoint, "wMaxPacketSize", 16, &max) || max == 0U) { + return TEMPEST_STATUS_ERR_FORMAT; /* not the box's endpoints */ + } + u->max_packet[i] = (unsigned)max; + } + char node[ROOT_BYTES + 16U]; + (void)snprintf(node, sizeof(node), "%s/%03lu/%03lu", u->dev, bus, devnum); + int fd = open(node, O_RDWR | O_CLOEXEC); + if (fd < 0) { + /* Not made yet, or not yet given its permissions: the watch will say + * when it is. */ + return errno == ENOENT || errno == EACCES || errno == EPERM + ? TEMPEST_STATUS_ERR_NOT_FOUND + : TEMPEST_STATUS_ERR_IO; + } + unsigned interface = BOX_INTERFACE; + if (ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface) != 0) { + int why = errno; + close(fd); + return why == EBUSY ? TEMPEST_STATUS_ERR_BUSY : TEMPEST_STATUS_ERR_IO; + } + u->fd = fd; + return TEMPEST_STATUS_OK; +} + +static void cable_poll_on(void *cable, struct pollfd *pfd) { + usbfs *u = cable; + if (u->fd >= 0) { + *pfd = (struct pollfd){.fd = u->fd, .events = POLLOUT}; + } else { + *pfd = (struct pollfd){.fd = u->watch, .events = POLLIN}; + } +} + +static int gone(int why) { + return why == ENODEV || why == ESHUTDOWN; +} + +static tempest_status submit(usbfs *u, int i, unsigned ep, size_t len, unsigned flags) { + struct usbdevfs_urb *urb = u->urb[i]; + memset(urb, 0, sizeof(*urb)); + urb->type = USBDEVFS_URB_TYPE_BULK; + urb->endpoint = (unsigned char)ep; + urb->flags = flags; + urb->buffer = u->buf[i]; + urb->buffer_length = (int)len; + urb->usercontext = urb; + int submitted; + while ((submitted = ioctl(u->fd, USBDEVFS_SUBMITURB, urb)) != 0 && errno == EINTR) { + } + if (submitted != 0) { + return gone(errno) ? TEMPEST_STATUS_ERR_IO : TEMPEST_STATUS_ERR_FORMAT; + } + return TEMPEST_STATUS_OK; +} + +static tempest_status cable_out(void *cable, unsigned ep, const uint8_t *bytes, size_t len) { + usbfs *u = cable; + int i = slot_of(ep); + if (u->fd < 0 || (i != STATE_OUT && i != PACKET_OUT) || len == 0U || + len > TEMPEST_BOX_MAX_BYTES) { + return TEMPEST_STATUS_ERR_INVALID; + } + memcpy(u->buf[i], bytes, len); + /* A message that fills its last packet is ended by a packet of none, so + * every message is one bulk transfer (VARIATIONS.md, Carriers). */ + unsigned flags = len % u->max_packet[i] == 0U ? USBDEVFS_URB_ZERO_PACKET : 0U; + tempest_status submitted = submit(u, i, ep, len, flags); + /* The kernel copied it at the submit. */ + tempest_secure_memzero(u->buf[i], sizeof(u->buf[i])); + return submitted; +} + +static tempest_status cable_in(void *cable, unsigned ep) { + usbfs *u = cable; + int i = slot_of(ep); + if (u->fd < 0 || (i != STATE_IN && i != PACKET_IN)) { + return TEMPEST_STATUS_ERR_INVALID; + } + tempest_status submitted = submit(u, i, ep, TEMPEST_BOX_TRANSFER_BYTES, 0U); + return submitted == TEMPEST_STATUS_OK ? TEMPEST_STATUS_OK : TEMPEST_STATUS_ERR_IO; +} + +static tempest_status cable_reap(void *cable, tempest_box_transfer *done) { + usbfs *u = cable; + if (u->fd < 0) { + return TEMPEST_STATUS_ERR_IO; + } + struct usbdevfs_urb *urb = NULL; + int reaped; + while ((reaped = ioctl(u->fd, USBDEVFS_REAPURBNDELAY, &urb)) != 0 && errno == EINTR) { + } + if (reaped != 0) { + return errno == EAGAIN ? TEMPEST_STATUS_ERR_NOT_FOUND : TEMPEST_STATUS_ERR_IO; + } + int i = 0; + while (i < ENDPOINTS && u->urb[i] != urb) { + ++i; + } + if (i == ENDPOINTS) { + return TEMPEST_STATUS_ERR_IO; /* no transfer of ours */ + } + memset(done, 0, sizeof(*done)); + done->ep = urb->endpoint; + if (gone(-urb->status)) { + return TEMPEST_STATUS_ERR_IO; + } + if (urb->status == -EPIPE) { + /* The box stalled its endpoint: cleared, and the transfer lost. */ + unsigned ep = urb->endpoint; + if (ioctl(u->fd, USBDEVFS_CLEAR_HALT, &ep) != 0 && gone(errno)) { + return TEMPEST_STATUS_ERR_IO; + } + } + if (urb->status != 0 || urb->actual_length < 0 || + (size_t)urb->actual_length > sizeof(done->bytes)) { + done->status = TEMPEST_STATUS_ERR_FORMAT; + } else { + done->status = TEMPEST_STATUS_OK; + done->len = (size_t)urb->actual_length; + if (i == STATE_IN || i == PACKET_IN) { + memcpy(done->bytes, u->buf[i], done->len); + } + } + tempest_secure_memzero(u->buf[i], sizeof(u->buf[i])); + return TEMPEST_STATUS_OK; +} + +static void cable_free(void *cable) { + usbfs *u = cable; + if (u == NULL) { + return; + } + cable_close(u); + for (int i = 0; i < ENDPOINTS; ++i) { + free(u->urb[i]); + } + if (u->watch >= 0) { + close(u->watch); + } + tempest_secure_memzero(u, sizeof(*u)); + free(u); +} + +tempest_status tempest_box_usbfs_cable(const char *sysfs, + const char *dev, + tempest_box_cable *out) { + if (out == NULL) { + return TEMPEST_STATUS_ERR_INVALID; + } + memset(out, 0, sizeof(*out)); + usbfs *u = calloc(1, sizeof(*u)); + if (u == NULL) { + return TEMPEST_STATUS_ERR_NOMEM; + } + u->fd = -1; + u->watch = -1; + for (int i = 0; i < ENDPOINTS; ++i) { + u->urb[i] = calloc(1, sizeof(*u->urb[i])); + if (u->urb[i] == NULL) { + cable_free(u); + return TEMPEST_STATUS_ERR_NOMEM; + } + } + int n1 = snprintf(u->sysfs, sizeof(u->sysfs), "%s", + sysfs != NULL ? sysfs : "/sys/bus/usb/devices"); + int n2 = snprintf(u->dev, sizeof(u->dev), "%s", dev != NULL ? dev : "/dev/bus/usb"); + if (n1 <= 0 || (size_t)n1 >= sizeof(u->sysfs) || n2 <= 0 || (size_t)n2 >= sizeof(u->dev)) { + cable_free(u); + return TEMPEST_STATUS_ERR_INVALID; + } + u->watch = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); + if (u->watch < 0 || !watch_bus(u)) { + cable_free(u); + return TEMPEST_STATUS_ERR_IO; + } + out->cable = u; + out->open = cable_open; + out->close = cable_close; + out->poll_on = cable_poll_on; + out->out = cable_out; + out->in = cable_in; + out->reap = cable_reap; + out->free = cable_free; + return TEMPEST_STATUS_OK; +}