Skip to content

Commit 5e07e80

Browse files
committed
websockets, initial, half-done implementation
Signed-off-by: Martin Sustrik <[email protected]>
1 parent 8c4f9c5 commit 5e07e80

File tree

5 files changed

+318
-1
lines changed

5 files changed

+318
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ tests/keepalive
5353
tests/fullstack
5454
tests/iovec
5555
tests/http
56+
tests/websock

Makefile.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ libdsock_la_SOURCES = \
5757
unix.c \
5858
utils.h \
5959
utils.c \
60+
websock.c \
6061
dns/dns.h \
6162
dns/dns.c \
6263
lz4/lz4.h \
@@ -166,7 +167,8 @@ check_PROGRAMS = \
166167
tests/keepalive \
167168
tests/fullstack \
168169
tests/iovec \
169-
tests/http
170+
tests/http \
171+
tests/websock
170172

171173
if HAVE_TLS
172174

dsock.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,21 @@ DSOCK_EXPORT int http_recvfield(
293293
size_t valuelen,
294294
int64_t deadline);
295295

296+
/******************************************************************************/
297+
/* WebSocket protocol. */
298+
/******************************************************************************/
299+
300+
DSOCK_EXPORT int websock_client(
301+
int s);
302+
DSOCK_EXPORT int websock_server(
303+
int s);
304+
DSOCK_EXPORT int websock_done(
305+
int s,
306+
int64_t deadline);
307+
DSOCK_EXPORT int websock_stop(
308+
int s,
309+
int64_t deadline);
310+
296311
/******************************************************************************/
297312
/* NaCl encryption and authentication protocol. */
298313
/* Uses crypto_secretbox_xsalsa20poly1305 algorithm. Key is 32B long. */

tests/websock.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
3+
Copyright (c) 2016 Martin Sustrik
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"),
7+
to deal in the Software without restriction, including without limitation
8+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
9+
and/or sell copies of the Software, and to permit persons to whom
10+
the Software is furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included
13+
in all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21+
IN THE SOFTWARE.
22+
23+
*/
24+
25+
#include <assert.h>
26+
#include <string.h>
27+
28+
#include "../dsock.h"
29+
30+
int main() {
31+
int h[2];
32+
int rc = unix_pair(h);
33+
assert(rc == 0);
34+
int s0 = websock_client(h[0]);
35+
assert(s0 >= 0);
36+
int s1 = websock_server(h[1]);
37+
38+
rc = msend(s0, "ABC", 3, -1);
39+
assert(rc == 0);
40+
char buf[16];
41+
ssize_t sz = mrecv(s1, buf, sizeof(buf), -1);
42+
assert(sz == 3 && memcmp(buf, "ABC", 3) == 0);
43+
rc = msend(s1, "DEF", 3, -1);
44+
assert(rc == 0);
45+
sz = mrecv(s0, buf, sizeof(buf), -1);
46+
assert(sz == 3 && memcmp(buf, "DEF", 3) == 0);
47+
48+
rc = hclose(s0);
49+
assert(rc == 0);
50+
rc = hclose(s1);
51+
assert(rc == 0);
52+
return 0;
53+
}
54+

websock.c

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
3+
Copyright (c) 2016 Martin Sustrik
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"),
7+
to deal in the Software without restriction, including without limitation
8+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
9+
and/or sell copies of the Software, and to permit persons to whom
10+
the Software is furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included
13+
in all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21+
IN THE SOFTWARE.
22+
23+
*/
24+
25+
#include <errno.h>
26+
#include <stdint.h>
27+
#include <stdlib.h>
28+
#include <string.h>
29+
30+
#include "bsock.h"
31+
#include "dsock.h"
32+
#include "iov.h"
33+
#include "msock.h"
34+
#include "utils.h"
35+
36+
dsock_unique_id(websock_type);
37+
38+
static void *websock_hquery(struct hvfs *hvfs, const void *type);
39+
static void websock_hclose(struct hvfs *hvfs);
40+
static int websock_msendv(struct msock_vfs *mvfs,
41+
const struct iovec *iov, size_t iovlen, int64_t deadline);
42+
static ssize_t websock_mrecvv(struct msock_vfs *mvfs,
43+
const struct iovec *iov, size_t iovlen, int64_t deadline);
44+
45+
struct websock_sock {
46+
struct hvfs hvfs;
47+
struct msock_vfs mvfs;
48+
int s;
49+
int txerr;
50+
int rxerr;
51+
int client;
52+
uint8_t txbuf[2048];
53+
};
54+
55+
static void *websock_hquery(struct hvfs *hvfs, const void *type) {
56+
struct websock_sock *obj = (struct websock_sock*)hvfs;
57+
if(type == msock_type) return &obj->mvfs;
58+
if(type == websock_type) return obj;
59+
errno = ENOTSUP;
60+
return NULL;
61+
}
62+
63+
static int websock_start(int s, int client) {
64+
/* Check whether underlying socket is a bytestream. */
65+
if(dsock_slow(!hquery(s, bsock_type))) return -1;
66+
/* Create the object. */
67+
struct websock_sock *obj = malloc(sizeof(struct websock_sock));
68+
if(dsock_slow(!obj)) {errno = ENOMEM; return -1;}
69+
obj->hvfs.query = websock_hquery;
70+
obj->hvfs.close = websock_hclose;
71+
obj->mvfs.msendv = websock_msendv;
72+
obj->mvfs.mrecvv = websock_mrecvv;
73+
obj->s = s;
74+
obj->txerr = 0;
75+
obj->rxerr = 0;
76+
obj->client = client;
77+
/* Create the handle. */
78+
int h = hcreate(&obj->hvfs);
79+
if(dsock_slow(h < 0)) {
80+
int err = errno;
81+
free(obj);
82+
errno = err;
83+
return -1;
84+
}
85+
return h;
86+
}
87+
88+
int websock_client(int s) {
89+
return websock_start(s, 1);
90+
}
91+
92+
int websock_server(int s) {
93+
return websock_start(s, 0);
94+
}
95+
96+
int websock_done(int s, int64_t deadline) {
97+
struct websock_sock *obj = hquery(s, websock_type);
98+
if(dsock_slow(!obj)) return -1;
99+
if(dsock_slow(obj->txerr)) {errno = obj->txerr; return -1;}
100+
dsock_assert(0);
101+
}
102+
103+
int websock_stop(int s, int64_t deadline) {
104+
int err;
105+
struct websock_sock *obj = hquery(s, websock_type);
106+
if(dsock_slow(!obj)) return -1;
107+
dsock_assert(0);
108+
}
109+
110+
static int websock_msendv(struct msock_vfs *mvfs,
111+
const struct iovec *iov, size_t iovlen, int64_t deadline) {
112+
struct websock_sock *obj = dsock_cont(mvfs, struct websock_sock, mvfs);
113+
if(dsock_slow(obj->txerr)) {errno = obj->txerr; return -1;}
114+
size_t len = iov_size(iov, iovlen);
115+
/* Construct message header. */
116+
uint8_t buf[12];
117+
size_t sz;
118+
buf[0] = 0x82;
119+
if(len > 0xffff) {
120+
buf[1] = 127;
121+
dsock_putll(buf + 2, len);
122+
sz = 10;
123+
}
124+
else if(len > 125) {
125+
buf[1] = 126;
126+
dsock_puts(buf + 2, len);
127+
sz = 4;
128+
}
129+
else {
130+
buf[1] = (uint8_t)len;
131+
sz = 2;
132+
}
133+
/* Server sends unmasked message. */
134+
if(!obj->client) {
135+
struct iovec vec[iovlen + 1];
136+
vec[0].iov_base = buf;
137+
vec[0].iov_len = sz;
138+
iov_copy(vec + 1, iov, iovlen);
139+
int rc = bsendv(obj->s, vec, iovlen + 1, deadline);
140+
if(dsock_slow(rc < 0)) {obj->txerr = errno; return -1;}
141+
return 0;
142+
}
143+
/* Client sends masked message. */
144+
uint8_t mask[4];
145+
int rc = dsock_random(mask, 4, deadline);
146+
if(dsock_slow(rc < 0)) return -1;
147+
buf[1] |= 0x80;
148+
memcpy(buf + sz, mask, 4);
149+
sz += 4;
150+
rc = bsend(obj->s, buf, sz, deadline);
151+
if(dsock_slow(rc < 0)) {obj->txerr = errno; return -1;}
152+
size_t pos = 0;
153+
while(len) {
154+
size_t tosend = sizeof(obj->txbuf);
155+
if(len < tosend) tosend = len;
156+
iov_copyfrom(obj->txbuf, iov, iovlen, pos, tosend);
157+
size_t i;
158+
for(i = 0; i != tosend; ++i)
159+
obj->txbuf[i] ^= mask[i % 4];
160+
pos += tosend;
161+
len -= tosend;
162+
rc = bsend(obj->s, obj->txbuf, tosend, deadline);
163+
if(dsock_slow(rc < 0)) {obj->txerr = errno; return -1;}
164+
}
165+
return 0;
166+
}
167+
168+
static ssize_t websock_mrecvv(struct msock_vfs *mvfs,
169+
const struct iovec *iov, size_t iovlen, int64_t deadline) {
170+
struct websock_sock *obj = dsock_cont(mvfs, struct websock_sock, mvfs);
171+
if(dsock_slow(obj->rxerr)) {errno = obj->rxerr; return -1;}
172+
size_t pos = 0;
173+
size_t len = iov_size(iov, iovlen);
174+
while(1) {
175+
uint8_t hdr1[2];
176+
int rc = brecv(obj->s, hdr1, 2, deadline);
177+
if(dsock_slow(rc < 0)) {obj->rxerr = errno; return -1;}
178+
if(hdr1[0] & 0x70) {errno = obj->rxerr = EPROTO; return -1;}
179+
int opcode = hdr1[0] & 0x0f;
180+
switch(opcode) {
181+
case 0:
182+
case 1:
183+
case 2:
184+
goto dataframe;
185+
case 8:
186+
/* TODO: close frame */
187+
dsock_assert(0);
188+
case 9:
189+
/* TODO: ping frame */
190+
dsock_assert(0);
191+
case 10:
192+
/* TODO: pong frame */
193+
dsock_assert(0);
194+
default:
195+
errno = obj->rxerr = EPROTO;
196+
return -1;
197+
}
198+
dataframe:
199+
if(!!(obj->client) ^ !(hdr1[1] & 0x80)) {
200+
errno = obj->rxerr = EPROTO; return -1;}
201+
size_t sz = hdr1[1] & 0x7f;
202+
if(sz == 126) {
203+
uint8_t hdr2[2];
204+
int rc = brecv(obj->s, hdr2, 2, deadline);
205+
if(dsock_slow(rc < 0)) {obj->rxerr = errno; return -1;}
206+
sz = dsock_gets(hdr2);
207+
}
208+
else if(sz == 127) {
209+
uint8_t hdr2[8];
210+
int rc = brecv(obj->s, hdr2, 8, deadline);
211+
if(dsock_slow(rc < 0)) {obj->rxerr = errno; return -1;}
212+
sz = dsock_getll(hdr2);
213+
}
214+
uint8_t mask[4];
215+
if(!obj->client) {
216+
int rc = brecv(obj->s, mask, 4, deadline);
217+
if(dsock_slow(rc < 0)) {obj->rxerr = errno; return -1;}
218+
}
219+
if(dsock_slow(sz > len)) {errno = obj->rxerr = EMSGSIZE; return -1;}
220+
struct iovec vec[iovlen];
221+
size_t veclen = iov_cut(vec, iov, iovlen, pos, sz);
222+
rc = brecvv(obj->s, vec, veclen, deadline);
223+
if(dsock_slow(rc < 0)) {obj->rxerr = errno; return -1;}
224+
if(!obj->client) {
225+
/* Unmask the frame data. */
226+
size_t i, j, mpos = 0;
227+
for(i = 0; i != veclen; ++i)
228+
for(j = 0; j != vec[i].iov_len; ++j)
229+
((uint8_t*)vec[i].iov_base)[j] ^= mask[mpos++ % 4];
230+
}
231+
pos += sz;
232+
len -= sz;
233+
if(hdr1[0] & 0x80)
234+
break;
235+
}
236+
return pos;
237+
}
238+
239+
static void websock_hclose(struct hvfs *hvfs) {
240+
struct websock_sock *obj = (struct websock_sock*)hvfs;
241+
int rc = hclose(obj->s);
242+
dsock_assert(rc == 0);
243+
free(obj);
244+
}
245+

0 commit comments

Comments
 (0)