/* lib9p_util/nut.c - 9P NUT video streaming * * Copyright (C) 2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include #include #include #include #include #include #include #include "nut.h" #include #define MIN(a, b) ( ((a) < (b)) ? (a) : (b) ) LO_IMPLEMENTATION_C(lib9p_srv_file, struct nut_file, nut_file); struct nut_fio { struct nut_file *parent; /** * Offset of the beginning of the current frame (frame intro + * frame data); or `0` if we are not yet to the first frame. */ uoff_t frame_beg; /* Wrapping the array in a struct allows us to assign to it * with a compound literal. */ struct { uint8_t dat[NUT_FRAME_INTRO_LEN]; } frame_intro; }; LO_IMPLEMENTATION_STATIC(lib9p_srv_fio, struct nut_fio, nut_fio); /* NUT encoding ***************************************************************/ /** Encode a number as a 2-byte-wide NUT variable-width-unsigned. */ #define NUT_VU2(x) \ (0x80| ((x)>>(1*7)) ), \ ( (x) &0x7F ) /** Encode a number as a 10-byte-wide NUT variable-width-unsigned. */ #define NUT_VU10(x) \ (0x80| ((x)>>(9*7)) ), \ (0x80|(((x)>>(8*7))&0x7F)), \ (0x80|(((x)>>(7*7))&0x7F)), \ (0x80|(((x)>>(6*7))&0x7F)), \ (0x80|(((x)>>(5*7))&0x7F)), \ (0x80|(((x)>>(4*7))&0x7F)), \ (0x80|(((x)>>(3*7))&0x7F)), \ (0x80|(((x)>>(2*7))&0x7F)), \ (0x80|(((x)>>(1*7))&0x7F)), \ ( (x) &0x7F ) /** Just a placeholder. */ #define NUT_CHECKSUM 0, 0, 0, 0 uint32_t nut_crc32(void *_dat, size_t len) { /* "Generator polynomial is 0x104C11DB7. Starting value is zero." */ static const uint32_t table[] = {NUT_CRC32_TABLE}; #define TABLE_BITS LM_FLOORLOG2(LM_ARRAY_LEN(table)) #define START 0 uint8_t *dat = _dat; uint32_t crc = START; for (size_t i = 0; i < len; i++) { crc ^= dat[i] <<24; for (size_t j = 0; j < 8/TABLE_BITS; j++) crc = (crc << TABLE_BITS) ^ table[crc >> (32 - TABLE_BITS)]; } return crc; #undef START #undef TABLE_BITS } static const uint8_t nut_file_intro[] = {NUT_FILE_INTRO}; #define nut_do_csum(N, DAT) do { \ uint32_t csum = nut_crc32(&((DAT)[NUT_FRAME_INTRO_CSUM##N##_BEG]), \ NUT_FRAME_INTRO_CSUM##N##_END - \ NUT_FRAME_INTRO_CSUM##N##_BEG); \ uint32be_encode(&((DAT)[NUT_FRAME_INTRO_CSUM##N##_END]), csum); \ } while (0) #define NUT_FRAME_LEN (NUT_FRAME_INTRO_LEN + NUT_FB_W*NUT_FB_H) static void nut_frame_intro(struct nut_fio *self) { uint64_t now = LO_CALL(bootclock, get_time_ns); uint32_t prev_frame_len = self->frame_beg < NUT_FILE_INTRO_LEN + NUT_FRAME_LEN ? 0 : NUT_FRAME_LEN; self->frame_intro = (typeof(self->frame_intro)){{ NUT_FRAME_INTRO(prev_frame_len, now) }}; nut_do_csum(0, self->frame_intro.dat); nut_do_csum(1, self->frame_intro.dat); } /* nut_file *******************************************************************/ #define file_assert(self) assert(self); assert(self->framebuffer) void nut_file_free(struct nut_file *self) { file_assert(self); } struct lib9p_qid nut_file_qid(struct nut_file *self) { file_assert(self); return (struct lib9p_qid){ .type = LIB9P_QT_FILE, .vers = 1, .path = self->pathnum, }; } error nut_file_stat(struct nut_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat *out) { file_assert(self); assert(ctx); assert(out); *out = ((struct lib9p_srv_stat){ .qid = nut_file_qid(self), .mode = 0444, .atime_sec = UTIL9P_ATIME, .mtime_sec = UTIL9P_MTIME, .size = 0, .name = lib9p_str(self->name), .owner_uid = { .name = lib9p_str("root"), .num = 0 }, .owner_gid = { .name = lib9p_str("root"), .num = 0 }, .last_modifier_uid = { .name = lib9p_str("root"), .num = 0 }, .extension = lib9p_str(NULL), }); return ERROR_NULL; } error nut_file_wstat(struct nut_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_srv_stat) { file_assert(self); assert(ctx); return error_new(E_POSIX_EROFS, "read-only part of filesystem"); } error nut_file_remove(struct nut_file *self, struct lib9p_srv_ctx *ctx) { file_assert(self); assert(ctx); return error_new(E_POSIX_EROFS, "read-only part of filesystem"); } LIB9P_SRV_NOTDIR(, struct nut_file, nut_file); lib9p_srv_fio_or_error nut_file_fopen(struct nut_file *self, struct lib9p_srv_ctx *ctx, bool LM_UNUSED(rd), bool LM_UNUSED(wr), bool LM_UNUSED(trunc)) { file_assert(self); assert(ctx); struct nut_fio *ret = heap_alloc(1, struct nut_fio); ret->parent = self; return ERROR_NEW_VAL(lib9p_srv_fio, LO_BOX(lib9p_srv_fio, ret)); } /* nut_fio ********************************************************************/ #define fio_assert(self) assert(self); file_assert(self->parent) static uint32_t nut_fio_iounit(struct nut_fio *self) { fio_assert(self); return 0; } static void nut_fio_iofree(struct nut_fio *self) { fio_assert(self); heap_free(self); } static struct lib9p_qid nut_fio_ioqid(struct nut_fio *self) { fio_assert(self); return nut_file_qid(self->parent); } static uint32_t_or_error nut_fio_pwrite(struct nut_fio *self, struct lib9p_srv_ctx *ctx, const void *LM_UNUSED(buf), uint32_t LM_UNUSED(byte_count), uint64_t LM_UNUSED(byte_offset)) { assert(self); assert(ctx); return ERROR_NEW_ERR(uint32_t, error_new(E_POSIX_EROFS, "read-only part of filesystem")); } static error nut_fio_pread(struct nut_fio *self, struct lib9p_srv_ctx *ctx, lo_interface io_writer dst, uint64_t byte_offset, uint32_t byte_count) { fio_assert(self); assert(ctx); assert(!LO_IS_NULL(dst)); if (byte_offset < NUT_FILE_INTRO_LEN) return io_write(dst, &nut_file_intro[byte_offset], MIN(byte_count, NUT_FILE_INTRO_LEN - byte_offset)).err; if (byte_offset < self->frame_beg) return error_new(E_POSIX_ESPIPE, "cannot read earlier frame"); /* Find the current frame. */ uoff_t frame_end = (self->frame_beg == 0) ? NUT_FILE_INTRO_LEN : (self->frame_beg + NUT_FRAME_LEN); bool needs_frame_intro = false; while (frame_end <= byte_offset) { self->frame_beg = frame_end; frame_end += NUT_FRAME_LEN; needs_frame_intro = true; } if (needs_frame_intro) nut_frame_intro(self); /* Write part of the frame. */ size_t frame_off = byte_offset - self->frame_beg; /* offset within the frame */ if (frame_off < NUT_FRAME_INTRO_LEN) { return io_write(dst, &self->frame_intro.dat[frame_off], MIN(byte_count, NUT_FRAME_INTRO_LEN - frame_off)).err; } else { return io_write(dst, &self->parent->framebuffer[frame_off - NUT_FRAME_INTRO_LEN], MIN(byte_count, NUT_FRAME_LEN - frame_off)).err; } }