1 module dast.ws.frame;
2 
3 import dast.ws.server,
4 std.bitmanip,
5 std.algorithm : min;
6 
7 enum Op : ubyte {
8 	CONT = 0,
9 	TEXT = 1,
10 	BINARY = 2,
11 	CLOSE = 8,
12 	PING = 9,
13 	PONG = 10
14 }
15 
16 struct Frame {
17 	bool fin;
18 	Op op;
19 	bool masked;
20 	State state;
21 
22 	ubyte[4] mask;
23 	ulong length;
24 	const(ubyte)[] data;
25 
26 	invariant (op < 16);
27 
28 pure nothrow:
29 	@property bool done() const @nogc => state == State.done;
30 
31 	@property size_t remaining() const @nogc => cast(size_t)length - data.length;
32 
33 	ubyte[] serialize() scope const @trusted {
34 		ubyte[14] buf = void;
35 		auto p = buf.ptr;
36 
37 		*p++ = fin << 7 | op;
38 		ubyte b2 = masked << 7;
39 		if (length < 126)
40 			*p++ = b2 | cast(ubyte)length;
41 		else if (length < 0xffff) {
42 			*p++ = b2 | 126;
43 			p[0 .. 2] = nativeToBigEndian(cast(ushort)length);
44 			p += 2;
45 		} else {
46 			*p++ = b2 | 127;
47 			p[0 .. 8] = nativeToBigEndian(length);
48 			p += 8;
49 		}
50 
51 		if (masked) {
52 			p[0 .. 4] = mask;
53 			const U u = {a: mask};
54 			auto result = buf[0 .. p + 4 - buf.ptr];
55 			result.reserve(result.length + data.length);
56 			auto i = data.length & ~3;
57 			foreach (x; cast(const(uint)[])data[0 .. i])
58 				result ~= U(x ^ u.m).a;
59 			for (; i < data.length; i++)
60 				result ~= data[i] ^ mask[i & 3];
61 			return result;
62 		}
63 		return buf[0 .. p - buf.ptr] ~ data;
64 	}
65 }
66 
67 private union U {
68 	uint m;
69 	ubyte[4] a;
70 }
71 
72 private auto read(size_t n = 1)(ref const(ubyte)[] data, size_t m = n)
73 in (data.length >= m, "Insufficient data") {
74 	static if (n == 1) {
75 		ubyte b = data[0];
76 		data = data[1 .. $];
77 	} else {
78 		auto b = data[0 .. m];
79 		data = data[m .. $];
80 	}
81 	return b;
82 }
83 
84 enum State : ubyte {
85 	start,
86 	fin_rsv_opcode = 0,
87 	mask_len,
88 	len126_ext_len,
89 	len127_ext_len,
90 	maskOn_mask,
91 	message_extraction,
92 	done,
93 	prev_done
94 }
95 
96 Frame parse(WSClient client, const(ubyte)[] data) nothrow {
97 	Frame f = client.frame;
98 	data = client.data ~ data;
99 
100 	enum changeState(string state) =
101 		"{ f.state = " ~ state ~ "; goto case " ~ state ~ "; }";
102 
103 	switch (f.state) with (State) {
104 	case start:
105 		if (!data.length)
106 			goto save;
107 		f = Frame.init;
108 		ubyte b = data.read; // read() modifies `data` by consuming the first byte
109 		if ((b >> 4) & 7)
110 			return f;
111 		f.fin = b >> 7;
112 		f.op = cast(Op)(b & 0xf);
113 		f.state = mask_len;
114 		goto case;
115 	case mask_len:
116 		if (!data.length)
117 			goto save;
118 		ubyte b = data.read;
119 		f.masked = b >> 7;
120 		f.length = b & 0x7f;
121 		if (f.length <= 125)
122 			mixin(changeState!"maskOn_mask");
123 		if (f.length == 127)
124 			mixin(changeState!"len127_ext_len");
125 
126 		f.state = len126_ext_len;
127 		goto case;
128 	case len126_ext_len:
129 		if (data.length <= 1)
130 			goto save;
131 		f.length = bigEndianToNative!ushort(data[0 .. 2]);
132 		data = data[2 .. $];
133 		mixin(changeState!"maskOn_mask"); // edge case: length=127
134 	case len127_ext_len:
135 		if (data.length < 8)
136 			goto save;
137 		f.length = bigEndianToNative!ulong(data[0 .. 8]);
138 		data = data[8 .. $];
139 		f.state = maskOn_mask;
140 		goto case;
141 	case maskOn_mask:
142 		if (f.masked) {
143 			if (data.length < 4)
144 				goto save;
145 			f.mask = data.read!4; // read!n when n > 1 returns ubyte[]
146 		}
147 		f.state = message_extraction;
148 		goto case;
149 	case message_extraction:
150 		if (f.length && (!f.length || !data.length))
151 			goto save;
152 		auto len = min(f.remaining, data.length);
153 		if (f.masked) {
154 			auto i = f.data.length;
155 			for (; (i & 3) && f.remaining && data.length; i++, len--)
156 				f.data ~= data.read ^ f.mask[i & 3];
157 			const U u = {a: f.mask};
158 			foreach (x; cast(const(uint)[])data[0 .. len & ~3])
159 				f.data ~= U(x ^ u.m).a;
160 			for (data = data[len & ~3 .. $]; f.remaining && data.length; i++)
161 				f.data ~= data.read ^ f.mask[i & 3];
162 		} else
163 			f.data ~= data.read!2(len);
164 
165 		f.state = done;
166 		goto case;
167 	case done:
168 		// to allow for streaming we have this changeState(..) loop
169 		if (f.remaining)
170 			mixin(changeState!"message_extraction");
171 
172 		f.state = State.done;
173 		goto save_data;
174 	default:
175 		f.state = start;
176 		client.data = data;
177 	}
178 	return f;
179 save:
180 	client.frame = f;
181 save_data:
182 	client.data = data;
183 	return f;
184 }
185 
186 unittest { // test multiple frames in one go
187 	scope c = new WSClient(null, Socket.init);
188 
189 	auto f1 = Frame(true, Op.TEXT, true, State.done, [0, 0, 0, 0],
190 		6, [0, 1, 2, 3, 4, 5]);
191 	auto f2 = Frame(false, Op.BINARY, true, State.done, [0, 1, 2, 3],
192 		3, [8, 7, 6]);
193 	auto f3 = Frame(false, Op.CLOSE, true, State.done, [0, 1, 2, 3],
194 		10, [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
195 	const d = f1.serialize ~ f2.serialize ~ f3.serialize;
196 	auto f4 = c.parse(d);
197 	auto f5 = c.parse([]);
198 	auto f6 = c.parse([]);
199 	assert(f1 == f4);
200 	assert(f2 == f5);
201 	assert(f3 == f6);
202 }
203 
204 unittest { // test one splitted frame
205 	scope c = new WSClient(null, Socket.init);
206 	auto f = Frame(false, Op.BINARY, true, State.done, [47, 119, 231, 3],
207 		10, [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
208 	const d1 = f.serialize[0 .. 7];
209 	const d2 = f.serialize[7 .. $];
210 	auto f1 = c.parse(d1);
211 	assert(!f1.done);
212 	auto f2 = c.parse(d2);
213 	assert(f2.done);
214 	assert(f == f2);
215 }
216 
217 unittest { // test streaming one byte at a time
218 	scope c = new WSClient(null, Socket.init);
219 
220 	auto f = Frame(true, Op.TEXT, true, State.done, [1, 2, 3, 4],
221 		6, [0, 1, 2, 3, 4, 5]);
222 	const data = f.serialize;
223 	foreach (b; data[0 .. $ - 1]) {
224 		auto _f = c.parse([b]);
225 		assert(!_f.done);
226 	}
227 	auto _f = c.parse([data[$ - 1]]);
228 	assert(_f.done);
229 	assert(f == _f);
230 }
231 
232 unittest { // test some funky streaming
233 	scope c = new WSClient(null, Socket.init);
234 
235 	ubyte[] data;
236 	foreach (i; 0 .. 1 << 20)
237 		data ~= cast(ubyte)i;
238 	auto f = Frame(false, Op.BINARY, true, State.done, [0, 0, 0, 0], data.length, data);
239 	ubyte[] serialized = f.serialize;
240 	size_t i0 = 0, i1 = 0, t = 1;
241 	do {
242 		i0 = i1;
243 		i1 = i0 + (((i0 & i1 | 3) ^ t) & 0x3f);
244 		if (i1 >= serialized.length)
245 			i1 = serialized.length;
246 		t++;
247 		auto _f = c.parse(serialized[i0 .. i1]);
248 		if (i1 == serialized.length) {
249 			assert(_f.done);
250 			assert(_f == f);
251 		} else
252 			assert(!_f.done);
253 	}
254 	while (i1 < serialized.length);
255 }
256 
257 unittest { // test edge-case length=127
258 	scope c = new WSClient(null, Socket.init);
259 
260 	ubyte[127] data = void;
261 	for (size_t i; i < 127; i++)
262 		data[i] = cast(ubyte)i;
263 	auto f = Frame(true, Op.BINARY, false, State.done, [0, 0, 0, 0], data.length, data[]);
264 	auto _f = c.parse(f.serialize);
265 	assert(f == _f);
266 }
267 
268 unittest { // test edge-case length=0
269 	scope c = new WSClient(null, Socket.init);
270 
271 	auto f = Frame(true, Op.CLOSE, false, State.done, [0, 0, 0, 0], 0, []);
272 	auto _f = c.parse(f.serialize);
273 	assert(f == _f);
274 }