From 145b125155d3ff9c31e1ef52b586634a70de6e06 Mon Sep 17 00:00:00 2001 From: Enzo Baldisserri Date: Tue, 26 May 2020 12:50:37 +0200 Subject: [PATCH] websocket: make compile --- cmd/tools/vtest-fixed.v | 5 +- vlib/net/websocket/handshake.v | 56 +++--- vlib/net/websocket/io.v | 2 +- vlib/net/websocket/ssl.v | 48 +++-- vlib/net/websocket/utf8.v | 71 ++++--- vlib/net/websocket/utils.v | 11 +- vlib/net/websocket/ws.v | 353 +++++++++++++++------------------ vlib/net/websocket/ws_test.v | 6 + 8 files changed, 284 insertions(+), 268 deletions(-) create mode 100644 vlib/net/websocket/ws_test.v diff --git a/cmd/tools/vtest-fixed.v b/cmd/tools/vtest-fixed.v index 61a87bf6d7..98eea4cda4 100644 --- a/cmd/tools/vtest-fixed.v +++ b/cmd/tools/vtest-fixed.v @@ -11,11 +11,14 @@ const ( skip_on_musl = [ 'vlib/net/http/http_test.v', 'vlib/net/http/cookie_test.v', + 'vlib/net/websocket/ws_test.v' 'vlib/sqlite/sqlite_test.v', 'vlib/clipboard/clipboard_test.v', ] skip_on_linux = []string{} - skip_on_non_linux = []string{} + skip_on_non_linux = [ + 'vlib/net/websocket/ws_test.v' + ] skip_on_windows = []string{} skip_on_non_windows = []string{} skip_on_macos = []string{} diff --git a/vlib/net/websocket/handshake.v b/vlib/net/websocket/handshake.v index 9335f2b096..3982bcd176 100644 --- a/vlib/net/websocket/handshake.v +++ b/vlib/net/websocket/handshake.v @@ -1,75 +1,73 @@ module websocket -fn (mut ws Client) read_handshake(seckey string){ - l.d("reading handshake...") +fn (mut ws Client) read_handshake(seckey string) { + l.d('reading handshake...') mut bytes_read := 0 max_buffer := 1024 buffer_size := 1 mut buffer := malloc(max_buffer) - for bytes_read <= max_buffer { res := ws.read_from_server(buffer + bytes_read, buffer_size) if res == 0 || res == -1 { - l.f("read_handshake: Failed to read handshake.") + l.f('read_handshake: Failed to read handshake.') } - if buffer[bytes_read] == `\n` && buffer[bytes_read-1] == `\r` && buffer[bytes_read-2] == `\n` && buffer[bytes_read-3] == `\r` { + if buffer[bytes_read] == `\n` && buffer[bytes_read - 1] == `\r` && buffer[bytes_read - + 2] == `\n` && buffer[bytes_read - 3] == `\r` { break } bytes_read += buffer_size } - buffer[max_buffer+1] = `\0` + buffer[max_buffer + 1] = `\0` ws.handshake_handler(string(byteptr(buffer)), seckey) } -fn (mut ws Client) handshake_handler(handshake_response, seckey string){ - l.d("handshake_handler:\r\n${handshake_response}") +fn (mut ws Client) handshake_handler(handshake_response, seckey string) { + l.d('handshake_handler:\r\n${handshake_response}') lines := handshake_response.split_into_lines() - header := lines[0] - if !header.starts_with("HTTP/1.1 101") && !header.starts_with("HTTP/1.0 101") { - l.f("handshake_handler: invalid HTTP status response code") + if !header.starts_with('HTTP/1.1 101') && !header.starts_with('HTTP/1.0 101') { + l.f('handshake_handler: invalid HTTP status response code') } - - for i in 1..lines.len { - if lines[i].len <= 0 || lines[i] == "\r\n" { + for i in 1 .. lines.len { + if lines[i].len <= 0 || lines[i] == '\r\n' { continue } - keys := lines[i].split(":") - + keys := lines[i].split(':') match keys[0] { - "Upgrade", "upgrade" { + 'Upgrade', 'upgrade' { ws.flags << .has_upgrade } - "Connection", "connection" { + 'Connection', 'connection' { ws.flags << .has_connection } - "Sec-WebSocket-Accept", "sec-websocket-accept" { - l.d("comparing hashes") - l.d("seckey: ${seckey}") + 'Sec-WebSocket-Accept', 'sec-websocket-accept' { + l.d('comparing hashes') + l.d('seckey: ${seckey}') challenge := create_key_challenge_response(seckey) - l.d("challenge: ${challenge}") - l.d("response: ${keys[1]}") + l.d('challenge: ${challenge}') + l.d('response: ${keys[1]}') if keys[1].trim_space() != challenge { - l.e("handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.") + l.e('handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.') } ws.flags << .has_accept unsafe { challenge.free() } - } else {} + } + else {} } unsafe { keys.free() } } if ws.flags.len < 3 { - ws.close(1002, "invalid websocket HTTP headers") - l.e("invalid websocket HTTP headers") + ws.close(1002, 'invalid websocket HTTP headers') + l.e('invalid websocket HTTP headers') } - l.i("handshake successful!") + l.i('handshake successful!') unsafe { handshake_response.free() lines.free() header.free() } -} \ No newline at end of file +} diff --git a/vlib/net/websocket/io.v b/vlib/net/websocket/io.v index 7e1f20095b..e4c6b5494b 100644 --- a/vlib/net/websocket/io.v +++ b/vlib/net/websocket/io.v @@ -20,4 +20,4 @@ fn (ws &Client) read_from_server(buffer byteptr, buffer_size int) int { } else { C.read(ws.socket.sockfd, buffer, buffer_size) } -} \ No newline at end of file +} diff --git a/vlib/net/websocket/ssl.v b/vlib/net/websocket/ssl.v index 9c6b8dc856..fbb37dd07d 100644 --- a/vlib/net/websocket/ssl.v +++ b/vlib/net/websocket/ssl.v @@ -1,46 +1,70 @@ module websocket +// On linux, prefer a localy build openssl, because it is +// much more likely for it to be newer, than the system +// openssl from libssl-dev. If there is no local openssl, +// the next flag is harmless, since it will still use the +// (older) system openssl. +#flag linux -I/usr/local/include/openssl -L/usr/local/lib #flag -lssl +// MacPorts +#flag darwin -I/opt/local/include +#flag darwin -L/opt/local/lib +// Brew +#flag darwin -I/usr/local/opt/openssl/include +#flag darwin -L/usr/local/opt/openssl/lib #include #include #include +struct SSL_CTX { +} + +struct SSL { +} + +struct SSL_METHOD { +} -struct C.SSL_CTX -struct C.SSL -struct C.SSL_METHOD fn C.SSL_load_error_strings() + fn C.SSL_library_init() + fn C.SSLv23_client_method() &C.SSL_METHOD + fn C.SSL_CTX_new() &C.SSL_CTX + fn C.SSL_new() &C.SSL + fn C.SSL_set_fd() int + fn C.SSL_connect() int + fn C.SSL_shutdown() + fn C.SSL_free() + fn C.SSL_CTX_free() + fn C.SSL_write() int + fn C.SSL_read() int -fn (mut ws Client) connect_ssl(){ - l.i("Using secure SSL connection") +fn (mut ws Client) connect_ssl() { + l.i('Using secure SSL connection') C.SSL_load_error_strings() C.SSL_library_init() - ws.sslctx = C.SSL_CTX_new(C.SSLv23_client_method()) - if ws.sslctx == C.NULL { + if ws.sslctx == 0 { l.f("Couldn't get ssl context") } - ws.ssl = C.SSL_new(ws.sslctx) - if ws.ssl == C.NULL { + if ws.ssl == 0 { l.f("Couldn't create OpenSSL instance.") } - if C.SSL_set_fd(ws.ssl, ws.socket.sockfd) != 1 { l.f("Couldn't assign ssl to socket.") } - if C.SSL_connect(ws.ssl) != 1 { l.f("Couldn't connect using SSL.") } -} \ No newline at end of file +} diff --git a/vlib/net/websocket/utf8.v b/vlib/net/websocket/utf8.v index 468f7e264b..f35a1e8381 100644 --- a/vlib/net/websocket/utf8.v +++ b/vlib/net/websocket/utf8.v @@ -3,35 +3,39 @@ module websocket pub fn utf8_validate_str(str string) bool { return utf8_validate(str.str, str.len) } + struct Utf8State { - mut: - index int +mut: + index int subindex int - failed bool + failed bool } + pub fn utf8_validate(data byteptr, len int) bool { mut state := Utf8State{} for i := 0; i < len; i++ { s := data[i] - if s == 0 {break} + if s == 0 { + break + } state.next_state(s) if state.failed { return false } - //i++ //fast forward + // i++ //fast forward } return !state.failed && state.subindex <= 0 } -fn (mut s Utf8State) seq(r0 bool, r1 bool, is_tail bool) bool { - if s.subindex == 0 || (s.index > 1 && s.subindex == 1) || (s.index >= 6 && s.subindex == 2) { +fn (mut s Utf8State) seq(r0, r1, is_tail bool) bool { + if s.subindex == 0 || (s.index > 1 && s.subindex == 1) || (s.index >= 6 && s.subindex == + 2) { if (s.subindex == 0 && r0) || (s.subindex == 1 && r1) || (s.subindex == 2 && is_tail) { s.subindex++ return true } goto next - } - else { + } else { s.failed = true if is_tail { s.index = 0 @@ -46,8 +50,8 @@ fn (mut s Utf8State) seq(r0 bool, r1 bool, is_tail bool) bool { return false } -fn (mut s Utf8State) next_state (c byte) { - //sequence 1 +fn (mut s Utf8State) next_state(c byte) { + // sequence 1 if s.index == 0 { if (c >= 0x00 + 1 && c <= 0x7F) || c == 0x00 { return @@ -56,20 +60,33 @@ fn (mut s Utf8State) next_state (c byte) { s.subindex = 0 } is_tail := c >= 0x80 && c <= 0xBF - //sequence 2 - if s.index == 1 && s.seq(c >= 0xC2 && c <= 0xDF, false, is_tail) {return} - - //sequence 3 - if s.index == 2 && s.seq(c == 0xE0, c >= 0xA0 && c <= 0xBF, is_tail) {return} - if s.index == 3 && s.seq(c >= 0xE1 && c <= 0xEC, c >= 0x80 && c <= 0xBF, is_tail) {return} - if s.index == 4 && s.seq(c == 0xED, c >= 0x80 && c <= 0x9F, is_tail) {return} - if s.index == 5 && s.seq(c >= 0xEE && c <= 0xEF, c >= 0x80 && c <= 0xBF, is_tail) {return} - - //sequence 4 - if s.index == 6 && s.seq(c == 0xF0, c >= 0x90 && c <= 0xBF, is_tail) {return} - if s.index == 7 && s.seq(c >= 0xF1 && c <= 0xF3, c >= 0x80 && c <= 0xBF, is_tail) {return} - if s.index == 8 && s.seq(c == 0xF4, c >= 0x80 && c <= 0x8F, is_tail) {return} - - //we should never reach here + // sequence 2 + if s.index == 1 && s.seq(c >= 0xC2 && c <= 0xDF, false, is_tail) { + return + } + // sequence 3 + if s.index == 2 && s.seq(c == 0xE0, c >= 0xA0 && c <= 0xBF, is_tail) { + return + } + if s.index == 3 && s.seq(c >= 0xE1 && c <= 0xEC, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + if s.index == 4 && s.seq(c == 0xED, c >= 0x80 && c <= 0x9F, is_tail) { + return + } + if s.index == 5 && s.seq(c >= 0xEE && c <= 0xEF, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + // sequence 4 + if s.index == 6 && s.seq(c == 0xF0, c >= 0x90 && c <= 0xBF, is_tail) { + return + } + if s.index == 7 && s.seq(c >= 0xF1 && c <= 0xF3, c >= 0x80 && c <= 0xBF, is_tail) { + return + } + if s.index == 8 && s.seq(c == 0xF4, c >= 0x80 && c <= 0x8F, is_tail) { + return + } + // we should never reach here s.failed = true -} \ No newline at end of file +} diff --git a/vlib/net/websocket/utils.v b/vlib/net/websocket/utils.v index abc345ba1b..c1bfec04de 100644 --- a/vlib/net/websocket/utils.v +++ b/vlib/net/websocket/utils.v @@ -8,7 +8,6 @@ import encoding.base64 fn htonl64(payload_len u64) byteptr { mut ret := malloc(8) - ret[0] = byte(((payload_len & (u64(0xff) << 56)) >> 56) & 0xff) ret[1] = byte(((payload_len & (u64(0xff) << 48)) >> 48) & 0xff) ret[2] = byte(((payload_len & (u64(0xff) << 40)) >> 40) & 0xff) @@ -23,7 +22,7 @@ fn htonl64(payload_len u64) byteptr { fn create_masking_key() []byte { t := time.ticks() tseq := t % 23237671 - mut rnd := rand.new_pcg32(u64(t), u64(tseq) ) + mut rnd := rand.new_pcg32(u64(t), u64(tseq)) mask_bit := byte(rnd.bounded_next(u32(math.max_i32))) buf := [`0`].repeat(4) C.memcpy(buf.data, &mask_bit, 4) @@ -31,7 +30,7 @@ fn create_masking_key() []byte { } fn create_key_challenge_response(seckey string) string { - guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + guid := '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' sha1buf := seckey + guid hash := sha1.sum(sha1buf.bytes()) hashstr := string(byteptr(hash.data)) @@ -45,9 +44,9 @@ fn create_key_challenge_response(seckey string) string { fn get_nonce() string { mut nonce := []byte{} - alphanum := "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz" - for i in 0..18 { + alphanum := '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz' + for _ in 0 .. 18 { nonce << alphanum[rand.next(61)] } return string(byteptr(nonce.data)) -} \ No newline at end of file +} diff --git a/vlib/net/websocket/ws.v b/vlib/net/websocket/ws.v index f68d2e2639..ee3118fc32 100644 --- a/vlib/net/websocket/ws.v +++ b/vlib/net/websocket/ws.v @@ -8,40 +8,40 @@ import sync import net.websocket.logger const ( - l = logger.new("ws") + l = logger.new('ws') ) pub struct Client { - retry int - eb &eventbus.EventBus - is_ssl bool - lock sync.Mutex = sync.new_mutex() - write_lock sync.Mutex = sync.new_mutex() - //subprotocol_len int - //cwebsocket_subprotocol *subprotocol; - //cwebsocket_subprotocol *subprotocols[]; - mut: - state State - socket net.Socket - flags []Flag - sslctx &C.SSL_CTX - ssl &C.SSL - fragments []Fragment - pub mut: - uri string + retry int + eb &eventbus.EventBus + is_ssl bool + lock &sync.Mutex = sync.new_mutex() + write_lock &sync.Mutex = sync.new_mutex() + // subprotocol_len int + // cwebsocket_subprotocol *subprotocol; + // cwebsocket_subprotocol *subprotocols[]; +mut: + state State + socket net.Socket + flags []Flag + sslctx &C.SSL_CTX + ssl &C.SSL + fragments []Fragment +pub mut: + uri string subscriber &eventbus.Subscriber } struct Fragment { data voidptr - len u64 + len u64 code OPCode } pub struct Message { - pub: - opcode OPCode - payload voidptr +pub: + opcode OPCode + payload voidptr payload_len int } @@ -58,15 +58,15 @@ enum State { connecting = 0 connected open - closing + closing closed } struct Uri { - mut: - hostname string - port string - resource string +mut: + hostname string + port string + resource string querystring string } @@ -77,13 +77,13 @@ enum Flag { } struct Frame { - mut: - fin bool - rsv1 bool - rsv2 bool - rsv3 bool - opcode OPCode - mask bool +mut: + fin bool + rsv1 bool + rsv2 bool + rsv3 bool + opcode OPCode + mask bool payload_len u64 masking_key [4]byte } @@ -93,21 +93,24 @@ pub fn new(uri string) &Client { ws := &Client{ uri: uri state: .closed - eb: eb, + eb: eb subscriber: eb.subscriber - is_ssl: uri.starts_with("wss") - ssl: C.NULL - sslctx: C.NULL + is_ssl: uri.starts_with('wss') + ssl: 0 + sslctx: 0 } return ws } + fn C.sscanf() int fn (ws &Client) parse_uri() &Uri { - u := urllib.parse(ws.uri) or {panic(err)} - v := u.request_uri().split("?") - querystring := if v.len > 1 {"?" + v[1]} else {""} - return &Uri { + u := urllib.parse(ws.uri) or { + panic(err) + } + v := u.request_uri().split('?') + querystring := if v.len > 1 { '?' + v[1] } else { '' } + return &Uri{ hostname: u.hostname() port: u.port() resource: v[0] @@ -118,34 +121,29 @@ fn (ws &Client) parse_uri() &Uri { pub fn (mut ws Client) connect() int { match ws.state { .connected { - l.f("connect: websocket already connected") + l.f('connect: websocket already connected') } .connecting { - l.f("connect: websocket already connecting") + l.f('connect: websocket already connecting') } .open { - l.f("connect: websocket already open") - } else { + l.f('connect: websocket already open') + } + else { // do nothing } } - ws.lock.lock() ws.state = .connecting ws.lock.unlock() - uri := ws.parse_uri() - nonce := get_nonce() seckey := base64.encode(nonce) - ai_family := C.AF_INET ai_socktype := C.SOCK_STREAM - - l.d("handshake header:") - handshake := "GET ${uri.resource}${uri.querystring} HTTP/1.1\r\nHost: ${uri.hostname}:${uri.port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ${seckey}\r\nSec-WebSocket-Version: 13\r\n\r\n" + l.d('handshake header:') + handshake := 'GET ${uri.resource}${uri.querystring} HTTP/1.1\r\nHost: ${uri.hostname}:${uri.port}\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: ${seckey}\r\nSec-WebSocket-Version: 13\r\n\r\n' l.d(handshake) - socket := net.new_socket(ai_family, ai_socktype, 0) or { l.f(err) return -1 @@ -155,34 +153,26 @@ pub fn (mut ws Client) connect() int { l.f(err) return -1 } - optval := 1 ws.socket.setsockopt(C.SOL_SOCKET, C.SO_KEEPALIVE, &optval) or { l.f(err) return -1 } - if ws.is_ssl { ws.connect_ssl() } - ws.lock.lock() ws.state = .connected ws.lock.unlock() - res := ws.write_to_server(handshake.str, handshake.len) if res <= 0 { - l.f("Handshake failed.") + l.f('Handshake failed.') } - ws.read_handshake(seckey) - ws.lock.lock() ws.state = .open ws.lock.unlock() - ws.send_open_event() - unsafe { handshake.free() nonce.free() @@ -191,38 +181,35 @@ pub fn (mut ws Client) connect() int { return 0 } -pub fn (mut ws Client) close(code int, message string){ +pub fn (mut ws Client) close(code int, message string) { if ws.state != .closed && ws.socket.sockfd > 1 { - ws.lock.lock() ws.state = .closing ws.lock.unlock() - mut code32 := 0 if code > 0 { - _code := C.htons(code) + code_ := C.htons(code) message_len := message.len + 2 mut close_frame := [`0`].repeat(message_len) - close_frame[0] = _code & 0xFF - close_frame[1] = (_code >> 8) + close_frame[0] = code_ & 0xFF + close_frame[1] = (code_ >> 8) code32 = (close_frame[0] << 8) + close_frame[1] - for i in 0..message.len { - close_frame[i+2] = message[i] + for i in 0 .. message.len { + close_frame[i + 2] = message[i] } - ws.send_control_frame(.close, "CLOSE", close_frame) + ws.send_control_frame(.close, 'CLOSE', close_frame) } else { - ws.send_control_frame(.close, "CLOSE", []) + ws.send_control_frame(.close, 'CLOSE', []) } - - if ws.ssl != C.NULL { + if ws.ssl != 0 { C.SSL_shutdown(ws.ssl) C.SSL_free(ws.ssl) - if ws.sslctx != C.NULL { + if ws.sslctx != 0 { C.SSL_CTX_free(ws.sslctx) } } else { if C.shutdown(ws.socket.sockfd, C.SHUT_WR) == -1 { - l.e("Unabled to shutdown websocket.") + l.e('Unabled to shutdown websocket.') } mut buf := [`0`] for ws.read_from_server(buf.data, 1) > 0 { @@ -232,34 +219,35 @@ pub fn (mut ws Client) close(code int, message string){ buf.free() } if C.close(ws.socket.sockfd) == -1 { - //ws.send_close_event()(websocket, 1011, strerror(errno)); + // ws.send_close_event()(websocket, 1011, strerror(C.errno)); } } ws.fragments = [] ws.send_close_event() - ws.lock.lock() ws.state = .closed ws.lock.unlock() unsafe { - } - //TODO impl autoreconnect + // TODO impl autoreconnect } } pub fn (mut ws Client) write(payload byteptr, payload_len int, code OPCode) int { + mut bytes_written := -1 if ws.state != .open { - ws.send_error_event("WebSocket closed. Cannot write.") - goto free_data + ws.send_error_event('WebSocket closed. Cannot write.') + unsafe { + free(payload) + } return -1 } - - header_len := 6 + if payload_len > 125 {2} else {0} + if payload_len > 0xffff {6} else {0} + header_len := 6 + if payload_len > 125 { 2 } else { 0 } + if payload_len > 0xffff { 6 } else { 0 } + frame_len := header_len + payload_len + mut frame_buf := [`0`].repeat(frame_len) + fbdata := byteptr( frame_buf.data ) masking_key := create_masking_key() mut header := [`0`].repeat(header_len) - mut bytes_written := -1 - header[0] = (int(code) | 0x80) if payload_len <= 125 { header[1] = (payload_len | 0x80) @@ -270,7 +258,7 @@ pub fn (mut ws Client) write(payload byteptr, payload_len int, code OPCode) int } else if payload_len > 125 && payload_len <= 0xffff { len16 := C.htons(payload_len) header[1] = (126 | 0x80) - C.memcpy(header.data+2, &len16, 2) + C.memcpy(header.data + 2, &len16, 2) header[4] = masking_key[0] header[5] = masking_key[1] header[6] = masking_key[2] @@ -278,37 +266,31 @@ pub fn (mut ws Client) write(payload byteptr, payload_len int, code OPCode) int } else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { // 65535 && 18446744073709551615 len64 := htonl64(u64(payload_len)) header[1] = (127 | 0x80) - C.memcpy(header.data+2, len64, 8) + C.memcpy(header.data + 2, len64, 8) header[10] = masking_key[0] header[11] = masking_key[1] header[12] = masking_key[2] header[13] = masking_key[3] } else { - l.c("write: frame too large") - ws.close(1009, "frame too large") + l.c('write: frame too large') + ws.close(1009, 'frame too large') goto free_data return -1 } - - frame_len := header_len + payload_len - mut frame_buf := [`0`].repeat(frame_len) - - C.memcpy(frame_buf.data, header.data, header_len) - C.memcpy(&frame_buf.data[header_len], payload, payload_len) - - for i in 0..payload_len { - frame_buf[header_len+i] ^= masking_key[i % 4] & 0xff + C.memcpy(fbdata, header.data, header_len) + C.memcpy(fbdata + header_len, payload, payload_len) + for i in 0 .. payload_len { + frame_buf[header_len + i] ^= masking_key[i % 4] & 0xff } - - bytes_written = ws.write_to_server(frame_buf.data, frame_len) + bytes_written = ws.write_to_server(fbdata, frame_len) if bytes_written == -1 { err := string(byteptr(C.strerror(C.errno))) - l.e("write: there was an error writing data: ${err}") - ws.send_error_event("Error writing data") + l.e('write: there was an error writing data: ${err}') + ws.send_error_event('Error writing data') goto free_data return -1 } - l.d("write: ${bytes_written} bytes written.") + l.d('write: ${bytes_written} bytes written.') free_data: unsafe { free(payload) @@ -320,34 +302,30 @@ pub fn (mut ws Client) write(payload byteptr, payload_len int, code OPCode) int } pub fn (mut ws Client) listen() { - l.i("Starting listener...") + l.i('Starting listener...') for ws.state == .open { ws.read() } - l.i("Listener stopped as websocket was closed.") + l.i('Listener stopped as websocket was closed.') } pub fn (mut ws Client) read() int { mut bytes_read := u64(0) - initial_buffer := u64(256) mut header_len := 2 header_len_offset := 2 extended_payload16_end_byte := 4 extended_payload64_end_byte := 10 - mut payload_len := u64(0) - mut data := C.calloc(initial_buffer, 1)//[`0`].repeat(int(max_buffer)) - + mut data := C.calloc(initial_buffer, 1) // [`0`].repeat(int(max_buffer)) mut frame := Frame{} mut frame_size := u64(header_len) - for bytes_read < frame_size && ws.state == .open { byt := ws.read_from_server(data + int(bytes_read), 1) match byt { 0 { - error := "server closed the connection." - l.e("read: ${error}") + error := 'server closed the connection.' + l.e('read: ${error}') ws.send_error_event(error) ws.close(1006, error) goto free_data @@ -355,11 +333,12 @@ pub fn (mut ws Client) read() int { } -1 { err := string(byteptr(C.strerror(C.errno))) - l.e("read: error reading frame. ${err}") - ws.send_error_event("error reading frame") + l.e('read: error reading frame. ${err}') + ws.send_error_event('error reading frame') goto free_data return -1 - } else { + } + else { bytes_read++ } } @@ -371,43 +350,36 @@ pub fn (mut ws Client) read() int { frame.opcode = OPCode(int(data[0] & 0x7F)) frame.mask = (data[1] & 0x80) == 0x80 frame.payload_len = u64(data[1] & 0x7F) - - //masking key + // masking key if frame.mask { frame.masking_key[0] = data[2] frame.masking_key[1] = data[3] frame.masking_key[2] = data[4] frame.masking_key[3] = data[5] } - payload_len = frame.payload_len frame_size = u64(header_len) + payload_len } - if frame.payload_len == u64(126) && bytes_read == u64(extended_payload16_end_byte) { header_len += 2 - mut extended_payload_len := 0 extended_payload_len |= data[2] << 8 extended_payload_len |= data[3] << 0 - - //masking key + // masking key if frame.mask { frame.masking_key[0] = data[4] frame.masking_key[1] = data[5] frame.masking_key[2] = data[6] frame.masking_key[3] = data[7] } - payload_len = u64(extended_payload_len) frame_size = u64(header_len) + payload_len if frame_size > initial_buffer { - l.d("reallocating: ${frame_size}") + l.d('reallocating: ${frame_size}') data = C.realloc(data, frame_size) } } else if frame.payload_len == u64(127) && bytes_read == u64(extended_payload64_end_byte) { - header_len += 8 //TODO Not sure... - + header_len += 8 // TODO Not sure... mut extended_payload_len := u64(0) extended_payload_len |= u64(data[2]) << 56 extended_payload_len |= u64(data[3]) << 48 @@ -417,46 +389,42 @@ pub fn (mut ws Client) read() int { extended_payload_len |= u64(data[7]) << 16 extended_payload_len |= u64(data[8]) << 8 extended_payload_len |= u64(data[9]) << 0 - - //masking key + // masking key if frame.mask { frame.masking_key[0] = data[10] frame.masking_key[1] = data[11] frame.masking_key[2] = data[12] frame.masking_key[3] = data[13] } - payload_len = extended_payload_len frame_size = u64(header_len) + payload_len if frame_size > initial_buffer { - l.d("reallocating: ${frame_size}") + l.d('reallocating: ${frame_size}') data = C.realloc(data, frame_size) } } } - // unmask the payload if frame.mask { - for i in 0..payload_len { - data[header_len+i] ^= frame.masking_key[i % 4] & 0xff + for i in 0 .. payload_len { + data[header_len + i] ^= frame.masking_key[i % 4] & 0xff } } - if ws.fragments.len > 0 && frame.opcode in [.text_frame, .binary_frame] { - ws.close(0, "") + ws.close(0, '') goto free_data return -1 } else if frame.opcode in [.text_frame, .binary_frame] { data_node: - l.d("read: recieved text_frame or binary_frame") + l.d('read: recieved text_frame or binary_frame') mut payload := malloc(sizeof(byte) * int(payload_len) + 1) - if payload == C.NULL { - l.f("out of memory") + if payload == 0 { + l.f('out of memory') } C.memcpy(payload, &data[header_len], payload_len) if frame.fin { if ws.fragments.len > 0 { - //join fragments + // join fragments ws.fragments << Fragment{ data: payload len: payload_len @@ -470,19 +438,21 @@ pub fn (mut ws Client) read() int { } } mut pl := malloc(sizeof(byte) * int(size)) - if pl == C.NULL { - l.f("out of memory") + if pl == 0 { + l.f('out of memory') } mut by := 0 - for i, f in frags { + for f in frags { C.memcpy(pl + by, f.data, f.len) by += int(f.len) - unsafe {free(f.data)} + unsafe { + free(f.data) + } } payload = pl frame.opcode = ws.fragments[0].code payload_len = size - //clear the fragments + // clear the fragments unsafe { ws.fragments.free() } @@ -491,21 +461,21 @@ pub fn (mut ws Client) read() int { payload[payload_len] = `\0` if frame.opcode == .text_frame && payload_len > 0 { if !utf8_validate(payload, int(payload_len)) { - l.e("malformed utf8 payload") - ws.send_error_event("Recieved malformed utf8.") - ws.close(1007, "malformed utf8 payload") + l.e('malformed utf8 payload') + ws.send_error_event('Recieved malformed utf8.') + ws.close(1007, 'malformed utf8 payload') goto free_data return -1 } } - message := Message { + message := Message{ opcode: frame.opcode payload: payload payload_len: int(payload_len) } ws.send_message_event(message) } else { - //fragment start. + // fragment start. ws.fragments << Fragment{ data: payload len: payload_len @@ -516,27 +486,25 @@ pub fn (mut ws Client) read() int { free(data) } return int(bytes_read) - } - else if frame.opcode == .continuation { - l.d("read: continuation") + } else if frame.opcode == .continuation { + l.d('read: continuation') if ws.fragments.len <= 0 { - l.e("Nothing to continue.") - ws.close(1002, "nothing to continue") + l.e('Nothing to continue.') + ws.close(1002, 'nothing to continue') goto free_data return -1 } goto data_node return 0 - } - else if frame.opcode == .ping { - l.d("read: ping") + } else if frame.opcode == .ping { + l.d('read: ping') if !frame.fin { - ws.close(1002, "control message must not be fragmented") + ws.close(1002, 'control message must not be fragmented') goto free_data return -1 } if frame.payload_len > 125 { - ws.close(1002, "control frames must not exceed 125 bytes") + ws.close(1002, 'control frames must not exceed 125 bytes') goto free_data return -1 } @@ -548,52 +516,50 @@ pub fn (mut ws Client) read() int { unsafe { free(data) } - return ws.send_control_frame(.pong, "PONG", payload) - } - else if frame.opcode == .pong { + return ws.send_control_frame(.pong, 'PONG', payload) + } else if frame.opcode == .pong { if !frame.fin { - ws.close(1002, "control message must not be fragmented") + ws.close(1002, 'control message must not be fragmented') goto free_data return -1 } unsafe { free(data) } - //got pong + // got pong return 0 - } - else if frame.opcode == .close { - l.d("read: close") + } else if frame.opcode == .close { + l.d('read: close') if frame.payload_len > 125 { - ws.close(1002, "control frames must not exceed 125 bytes") + ws.close(1002, 'control frames must not exceed 125 bytes') goto free_data return -1 } mut code := 0 - mut reason := "" + mut reason := '' if payload_len > 2 { - code = (int(data[header_len]) << 8) + int(data[header_len+1]) + code = (int(data[header_len]) << 8) + int(data[header_len + 1]) header_len += 2 payload_len -= 2 reason = string(&data[header_len]) - l.i("Closing with reason: ${reason} & code: ${code}") + l.i('Closing with reason: ${reason} & code: ${code}') if reason.len > 1 && !utf8_validate(reason.str, reason.len) { - l.e("malformed utf8 payload") - ws.send_error_event("Recieved malformed utf8.") - ws.close(1007, "malformed utf8 payload") + l.e('malformed utf8 payload') + ws.send_error_event('Recieved malformed utf8.') + ws.close(1007, 'malformed utf8 payload') goto free_data return -1 } } - unsafe{ + unsafe { free(data) } ws.close(code, reason) return 0 } - l.e("read: Recieved unsupported opcode: ${frame.opcode} fin: ${frame.fin} uri: ${ws.uri}") - ws.send_error_event("Recieved unsupported opcode: ${frame.opcode}") - ws.close(1002, "Unsupported opcode") + l.e('read: Recieved unsupported opcode: ${frame.opcode} fin: ${frame.fin} uri: ${ws.uri}') + ws.send_error_event('Recieved unsupported opcode: ${frame.opcode}') + ws.close(1002, 'Unsupported opcode') free_data: unsafe { free(data) @@ -602,9 +568,13 @@ pub fn (mut ws Client) read() int { } fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []byte) int { + mut bytes_written := -1 if ws.socket.sockfd <= 0 { - l.e("No socket opened.") - goto free_data + l.e('No socket opened.') + unsafe { + payload.free() + } + l.c('send_control_frame: error sending ${frame_typ} control frame.') return -1 } header_len := 6 @@ -618,24 +588,22 @@ fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []b control_frame[4] = masking_key[2] control_frame[5] = masking_key[3] if code == .close { - close_code := 1000 if payload.len > 2 { mut parsed_payload := [`0`].repeat(payload.len + 1) C.memcpy(parsed_payload.data, &payload[0], payload.len) parsed_payload[payload.len] = `\0` - for i in 0..payload.len { - control_frame[6+i] = (parsed_payload[i] ^ masking_key[i % 4]) & 0xff + for i in 0 .. payload.len { + control_frame[6 + i] = (parsed_payload[i] ^ masking_key[i % 4]) & 0xff } unsafe { parsed_payload.free() } } } else { - for i in 0..payload.len { + for i in 0 .. payload.len { control_frame[header_len + i] = (payload[i] ^ masking_key[i % 4]) & 0xff } } - mut bytes_written := -1 bytes_written = ws.write_to_server(control_frame.data, frame_len) free_data: unsafe { @@ -645,15 +613,16 @@ fn (mut ws Client) send_control_frame(code OPCode, frame_typ string, payload []b } match bytes_written { 0 { - l.d("send_control_frame: remote host closed the connection.") + l.d('send_control_frame: remote host closed the connection.') return 0 } -1 { - l.c("send_control_frame: error sending ${frame_typ} control frame.") + l.c('send_control_frame: error sending ${frame_typ} control frame.') return -1 - } else { - l.d("send_control_frame: wrote ${bytes_written} byte ${frame_typ} frame.") + } + else { + l.d('send_control_frame: wrote ${bytes_written} byte ${frame_typ} frame.') return bytes_written } } -} \ No newline at end of file +} diff --git a/vlib/net/websocket/ws_test.v b/vlib/net/websocket/ws_test.v new file mode 100644 index 0000000000..0c7aef3c78 --- /dev/null +++ b/vlib/net/websocket/ws_test.v @@ -0,0 +1,6 @@ +module websocket + +// TODO This only checks that the client compiles, do real tests +fn test_compile() { + new('http://examples.com') +}