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 }