summaryrefslogtreecommitdiff
path: root/lib9p_util/nut.c
blob: c25dccf53adeca181c131d3ebab74d11a11e832c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/* lib9p_util/nut.c - 9P NUT video streaming
 *
 * Copyright (C) 2025  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <stdint.h>

#include <libhw/generic/alarmclock.h>
#include <libmisc/assert.h>
#include <libmisc/endian.h>
#include <libmisc/heap.h>
#include <libmisc/macro.h>
#include <util9p/static.h>

#include "nut.h"

#include <util9p/nut.h>

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