├── .gitignore ├── LICENSE ├── README.md ├── logger └── logger.v ├── main.v ├── utf8.h └── ws ├── events.v ├── handshake.v ├── io.v ├── ssl.v ├── utf8.v ├── utils.v └── ws.v /.gitignore: -------------------------------------------------------------------------------- 1 | vws 2 | fns.txt -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Abdullah Atta 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"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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 THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebSockets Library for V 2 | 3 | **This is still work-in-progress!** 4 | 5 | Heavily inspired (and used **very** liberally) from [cwebsockets](https://github.com/jeremyhahn/cwebsocket). 6 | 7 | The websockets library itself is ready and working (passes all tests of AutoBahn). What's left: 8 | 9 | 1. It needs to be updated and made to run with latest V. 10 | 2. No Windows Support (SSL issues) 11 | 3. No proper AutoBahn test client (a prototype is in the main.v but nothing clean and neat). 12 | 4. No Websocket Server. 13 | 14 | ## What's needed for Windows support: 15 | 16 | 1. SSL (either make the VSChannel work or OpenSSL) 17 | 18 | General code cleanup etc. is also needed. 19 | 20 | ## Contributors 21 | 22 | Anyone and everyone is welcome to contribute. I don't have time for working on this completely but I will review and merge Pull Requests ASAP. So if anyone is interested, know that I am interested too. 23 | 24 | If anyone has any questions regarding design etc. please open an Issue or contact me on Discord. 25 | 26 | ## Future Planning: 27 | 28 | This is supposed to be merged into V stdlib but it's not ready for that yet. As soon as it is, I will open a PR. 29 | -------------------------------------------------------------------------------- /logger/logger.v: -------------------------------------------------------------------------------- 1 | module logger 2 | 3 | const ( 4 | colors = { 5 | "success": "\e[32", 6 | "debug": "\e[36", 7 | "error": "\e[91", 8 | "warn": "\e[33", 9 | "critical": "\e[31", 10 | "fatal": "\e[31", 11 | "info": "\e[37" 12 | } 13 | ) 14 | 15 | struct Logger { 16 | mod string 17 | } 18 | 19 | pub fn new(mod string) &Logger { 20 | return &Logger{mod: mod} 21 | } 22 | 23 | pub fn (l &Logger) d(message string){ 24 | $if debug { 25 | l.print("debug", message) 26 | } 27 | } 28 | 29 | pub fn (l &Logger) i(message string){ 30 | l.print('info', message) 31 | } 32 | 33 | pub fn (l &Logger) e(message string){ 34 | l.print('error', message) 35 | } 36 | 37 | pub fn (l &Logger) c(message string){ 38 | l.print('critical', message) 39 | } 40 | 41 | pub fn (l &Logger) f(message string){ 42 | l.print('fatal', message) 43 | exit(-1) 44 | } 45 | 46 | pub fn (l &Logger) w(message string){ 47 | l.print('warn', message) 48 | } 49 | 50 | pub fn (l &Logger) s(message string) { 51 | l.print('success', message) 52 | } 53 | 54 | fn (l &Logger) print(mod, message string) { 55 | println('${colors[mod]};7m[${mod}]\e[0m \e[1m${l.mod}\e[0m: ${message}') 56 | } -------------------------------------------------------------------------------- /main.v: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | import ( 4 | ws 5 | eventbus 6 | time 7 | readline 8 | term 9 | benchmark 10 | ) 11 | 12 | const ( 13 | eb = eventbus.new() 14 | ) 15 | #flag -I $PWD 16 | #include "utf8.h" 17 | 18 | fn C.utf8_validate_str() bool 19 | fn main(){ 20 | //println(sss) 21 | /* for sss in 0..10 { 22 | mut bm := benchmark.new_benchmark() 23 | for i in 0..10000 { 24 | for a, t in tests { 25 | ss := ws.utf8_validate(t.str, t.len) 26 | if !ss { 27 | panic("failed") 28 | } 29 | //println("${a}:${ss}") 30 | } 31 | } 32 | bm.stop() 33 | println( bm.total_message('remarks about the benchmark') ) 34 | } */ 35 | mut websocket := ws.new("ws://localhost:9001/getCaseCount") 36 | //defer { } 37 | websocket.subscriber.subscribe("on_open", on_open) 38 | websocket.subscriber.subscribe("on_message", on_message) 39 | websocket.subscriber.subscribe("on_error", on_error) 40 | websocket.subscriber.subscribe("on_close", on_close) 41 | //go 42 | websocket.connect() 43 | websocket.read() 44 | //time.usleep(2000000) 45 | //go websocket.listen() 46 | //term.erase_clear() 47 | /* text := read_line("[client]:") 48 | if text == "close" { 49 | ws.close(1005, "done") 50 | time.usleep(1000000) 51 | exit(0) 52 | } 53 | ws.write(text, .text_frame) */ 54 | /* time.usleep(1000000) 55 | ws.read() */ 56 | //ws.close(1005, "done") 57 | /* 58 | */ 59 | //websocket.close(1005, "done") 60 | //read_line("wait") 61 | } 62 | 63 | fn read_line(text string) string { 64 | mut r := readline.Readline{} 65 | mut output := r.read_line(text + " ") or { 66 | panic(err) 67 | } 68 | output = output.replace("\n","") 69 | if output.len <= 0 { 70 | return "" 71 | } 72 | return output 73 | } 74 | 75 | fn on_open(params eventbus.Params){ 76 | println("Websocket opened.") 77 | } 78 | 79 | fn on_message(params eventbus.Params){ 80 | println("Message recieved. Sending it back.") 81 | typ := params.get_string("type") 82 | len := params.get_int("len") 83 | mut ws := params.get_caller(ws.Client{}) 84 | if typ == "string" { 85 | message := params.get_string("payload") 86 | if ws.uri.ends_with("getCaseCount") { 87 | num := message.int() 88 | ws.close(1005, "done") 89 | start_tests(mut ws, num) 90 | return 91 | } 92 | //println("Message: " + message) 93 | ws.write(message.str, len, .text_frame) 94 | } else { 95 | println("Binary message.") 96 | message := params.get_raw("payload") 97 | ws.write(message, len, .binary_frame) 98 | } 99 | } 100 | 101 | fn start_tests(websocket mut ws.Client, num int) { 102 | for i := 1; i < num; i++ { 103 | println("Running test: " + i.str()) 104 | websocket.uri = "ws://localhost:9001/runCase?case=${i.str()}&agent=vws/1.0a" 105 | if websocket.connect() >= 0 { 106 | websocket.listen() 107 | } 108 | } 109 | println("Done!") 110 | websocket.uri = "ws://localhost:9001/updateReports?agent=vws/1.0a" 111 | if websocket.connect() >= 0 { 112 | websocket.read() 113 | websocket.close(1000, "disconnecting...") 114 | } 115 | exit(0) 116 | } 117 | 118 | fn on_close(params eventbus.Params){ 119 | println("Websocket closed.") 120 | } 121 | 122 | fn on_error(params eventbus.Params){ 123 | println("we have an error.") 124 | } 125 | -------------------------------------------------------------------------------- /utf8.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Andreas Fett 3 | All rights reserved. 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, 9 | this list of conditions and the following disclaimer in the documentation 10 | and/or other materials provided with the distribution. 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 12 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 13 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 14 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 15 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 16 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 17 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 18 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 19 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 20 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | #include 24 | 25 | typedef int utf8_state; 26 | 27 | static utf8_state next_state(utf8_state, unsigned char); 28 | 29 | // Public API see utf8-validate.h for docs of the following function 30 | 31 | bool utf8_validate(utf8_state *const state, int c) 32 | { 33 | assert(state); 34 | return (*state = next_state(*state, c)) != -1; 35 | } 36 | 37 | bool utf8_validate_some(utf8_state *const state, const void * const src, size_t len) 38 | { 39 | assert(state); 40 | assert(src); 41 | for (size_t i = 0; i < len; ++i) { 42 | *state = next_state(*state, *((unsigned char *)src + i)); 43 | if (*state == -1) { 44 | return false; 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | bool utf8_validate_mem(const void * const src, size_t len) 51 | { 52 | assert(src); 53 | utf8_state state = 0; 54 | for (size_t i = 0; i < len; ++i) { 55 | state = next_state(state, *((unsigned char *)src + i)); 56 | if (state == -1) { 57 | return false; 58 | } 59 | } 60 | 61 | // detect unterminated sequence 62 | return state == 0; 63 | } 64 | 65 | bool utf8_validate_str(const char * const str) 66 | { 67 | assert(str); 68 | utf8_state state = 0; 69 | for (size_t i = 0; str[i] != 0; ++i) { 70 | state = next_state(state, str[i]); 71 | if (state == -1) { 72 | return false; 73 | } 74 | } 75 | // detect unterminated sequence 76 | return state == 0; 77 | } 78 | 79 | /* Private state engine 80 | * 81 | * The macros below assemble the cases for a switch statement 82 | * matching the language of the ABNF grammar given in rfc3629. 83 | * 84 | * Each SEQ# macro adds the states to match a # char long sequence. 85 | * 86 | * The SEQ#_HELPERs all have a 'fall through' to the next sequence. 87 | * for # > 1 this is an explicit goto 88 | */ 89 | 90 | #define SEQ_END(n) SEQ_ ## n ## _END 91 | 92 | #define SEQ1_HELPER(s, r0) \ 93 | case (s * 4) + 0: if (r0) return 0; goto SEQ_END(s); \ 94 | SEQ_END(s): 95 | 96 | #define SEQ2_HELPER(s, r0, r1) \ 97 | case (s * 4) + 0: if (r0) { printf("ehe"); return (s * 4) + 1; } goto SEQ_END(s); \ 98 | case (s * 4) + 1: if (r1) return 0; return -1; \ 99 | SEQ_END(s): 100 | 101 | #define SEQ3_HELPER(s, r0, r1, r2) \ 102 | case (s * 4) + 0: if (r0) return (s * 4) + 1; goto SEQ_END(s); \ 103 | case (s * 4) + 1: if (r1) return (s * 4) + 2; return -1; \ 104 | case (s * 4) + 2: if (r2) return 0; return -1; \ 105 | SEQ_END(s): 106 | 107 | #define SEQ4_HELPER(s, r0, r1, r2, r3) \ 108 | case (s * 4) + 0: if (r0) return (s * 4) + 1; goto SEQ_END(s); \ 109 | case (s * 4) + 1: if (r1) return (s * 4) + 2; return -1; \ 110 | case (s * 4) + 2: if (r2) return (s * 4) + 3; return -1; \ 111 | case (s * 4) + 3: if (r3) return 0; return -1; \ 112 | SEQ_END(s): 113 | 114 | #define SEQ1(s, r0) SEQ1_HELPER(s, r0) 115 | #define SEQ2(s, r0, r1) SEQ2_HELPER(s, r0, r1) 116 | #define SEQ3(s, r0, r1, r2) SEQ3_HELPER(s, r0, r1, r2) 117 | #define SEQ4(s, r0, r1, r2, r3) SEQ4_HELPER(s, r0, r1, r2, r3) 118 | 119 | // Matcher macros 120 | 121 | #define VALUE(v) (c == v) 122 | #define RANGE(s, e) (c >= s && c <= e) 123 | /* workaround for "-Wtype-limits" as c >= s is allways true for 124 | * the unsigned char in the case of c == 0 */ 125 | #define EGNAR(s, e) ((c >= s + 1 && c <= e) || c == s) 126 | 127 | /* from rfc3629 128 | * 129 | * UTF8-octets = *( UTF8-char ) 130 | * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 131 | * UTF8-1 = %x00-7F 132 | * UTF8-2 = %xC2-DF UTF8-tail 133 | * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / 134 | * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) 135 | * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / 136 | * %xF4 %x80-8F 2( UTF8-tail ) 137 | * UTF8-tail = %x80-BF 138 | */ 139 | 140 | #define TAIL RANGE(0x80, 0xBF) 141 | 142 | static inline utf8_state next_state(utf8_state state, unsigned char c) 143 | { 144 | printf("C: %d\n", c); 145 | switch (state) { 146 | SEQ1(0, EGNAR(0x00, 0x7F)) 147 | SEQ2(1, RANGE(0xC2, 0xDF), TAIL) 148 | SEQ3(2, VALUE(0xE0), RANGE(0xA0, 0xBF), TAIL) 149 | SEQ3(3, RANGE(0xE1, 0xEC), TAIL, TAIL) 150 | SEQ3(4, VALUE(0xED), RANGE(0x80, 0x9F), TAIL) 151 | SEQ3(5, RANGE(0xEE, 0xEF), TAIL, TAIL) 152 | SEQ4(6, VALUE(0xF0), RANGE(0x90, 0xBF), TAIL, TAIL) 153 | SEQ4(7, RANGE(0xF1, 0xF3), TAIL, TAIL, TAIL) 154 | SEQ4(8, VALUE(0xF4), RANGE(0x80, 0x8F), TAIL, TAIL) 155 | // no sequence start matched 156 | break; 157 | default: 158 | /* 159 | * This should not happen, unless you feed an error 160 | * state or an uninitialized utf8_state to this function. 161 | */ 162 | assert(false && "invalid utf8 state"); 163 | } 164 | 165 | return -1; 166 | } -------------------------------------------------------------------------------- /ws/events.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | import ( 4 | eventbus 5 | ) 6 | 7 | fn (ws &Client) send_message_event(msg Message){ 8 | mut params := eventbus.Params{} 9 | mut typ := "" 10 | if msg.opcode == .text_frame { 11 | params.put_string("payload", string(byteptr(msg.payload))) 12 | typ = 'string' 13 | } else if msg.opcode == .binary_frame { 14 | params.put_custom("payload", "binary", msg.payload) 15 | typ = 'binary' 16 | } 17 | params.put_string("type", typ) 18 | params.put_int("len", msg.payload_len) 19 | ws.eb.publish("on_message", params, ws) 20 | l.d("sending on_message event") 21 | } 22 | 23 | fn (ws &Client) send_error_event(err string) { 24 | mut params := eventbus.Params{} 25 | params.put_string("error", err) 26 | ws.eb.publish("on_error", params, ws) 27 | l.d("sending on_error event") 28 | } 29 | 30 | fn (ws &Client) send_close_event() { 31 | params := eventbus.Params{} 32 | ws.eb.publish("on_close", params, ws) 33 | l.d("sending on_close event") 34 | } 35 | 36 | fn (ws &Client) send_open_event() { 37 | params := eventbus.Params{} 38 | ws.eb.publish("on_open", params, ws) 39 | l.d("sending on_open event") 40 | } -------------------------------------------------------------------------------- /ws/handshake.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | fn (ws mut Client) read_handshake(seckey string){ 4 | l.d("reading handshake...") 5 | mut bytes_read := 0 6 | max_buffer := 256 7 | buffer_size := 1 8 | mut buffer := malloc(max_buffer) 9 | 10 | for bytes_read <= max_buffer { 11 | res := ws.read_from_server(buffer + bytes_read, buffer_size) 12 | if res == 0 || res == -1 { 13 | l.f("read_handshake: Failed to read handshake.") 14 | } 15 | if buffer[bytes_read] == `\n` && buffer[bytes_read-1] == `\r` && buffer[bytes_read-2] == `\n` && buffer[bytes_read-3] == `\r` { 16 | break 17 | } 18 | bytes_read += buffer_size 19 | } 20 | buffer[max_buffer+1] = `\0` 21 | ws.handshake_handler(string(byteptr(buffer)), seckey) 22 | } 23 | 24 | fn (ws mut Client) handshake_handler(handshake_response, seckey string){ 25 | l.d("handshake_handler:\r\n${handshake_response}") 26 | lines := handshake_response.split_into_lines() 27 | 28 | header := lines[0] 29 | if !header.starts_with("HTTP/1.1 101") && !header.starts_with("HTTP/1.0 101") { 30 | l.f("handshake_handler: invalid HTTP status response code") 31 | } 32 | 33 | for i in 1..lines.len { 34 | if lines[i].len <= 0 || lines[i] == "\r\n" { 35 | continue 36 | } 37 | keys := lines[i].split(":") 38 | 39 | match keys[0] { 40 | "Upgrade" { 41 | ws.flags << Flag.has_upgrade 42 | } 43 | "Connection" { 44 | ws.flags << Flag.has_connection 45 | } 46 | "Sec-WebSocket-Accept" { 47 | l.d("comparing hashes") 48 | response := create_key_challenge_response(seckey) 49 | if keys[1].trim_space() != response { 50 | l.e("handshake_handler: Sec-WebSocket-Accept header does not match computed sha1/base64 response.") 51 | } 52 | ws.flags << Flag.has_accept 53 | unsafe { 54 | response.free() 55 | } 56 | } else {} 57 | } 58 | unsafe { 59 | keys.free() 60 | } 61 | } 62 | if ws.flags.len < 3 { 63 | ws.close(1002, "invalid websocket HTTP headers") 64 | l.e("invalid websocket HTTP headers") 65 | } 66 | l.i("handshake successful!") 67 | unsafe { 68 | handshake_response.free() 69 | lines.free() 70 | header.free() 71 | } 72 | } -------------------------------------------------------------------------------- /ws/io.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | fn C.write() int 4 | 5 | fn (ws mut Client) write_to_server(buf voidptr, len int) int { 6 | mut bytes_written := 0 7 | ws.write_lock.lock() 8 | bytes_written = if ws.is_ssl { 9 | C.SSL_write(ws.ssl, buf, len) 10 | } else { 11 | C.write(ws.socket.sockfd, buf, len) 12 | } 13 | ws.write_lock.unlock() 14 | return bytes_written 15 | } 16 | 17 | fn (ws &Client) read_from_server(buffer byteptr, buffer_size int) int { 18 | return if ws.is_ssl { 19 | C.SSL_read(ws.ssl, buffer, buffer_size) 20 | } else { 21 | C.read(ws.socket.sockfd, buffer, buffer_size) 22 | } 23 | } -------------------------------------------------------------------------------- /ws/ssl.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | #flag -lssl 4 | #include 5 | #include 6 | #include 7 | 8 | struct C.SSL_CTX 9 | struct C.SSL 10 | struct C.SSL_METHOD 11 | fn C.SSL_load_error_strings() 12 | fn C.SSL_library_init() 13 | fn C.SSLv23_client_method() &SSL_METHOD 14 | fn C.SSL_CTX_new() &SSL_CTX 15 | fn C.SSL_new() &SSL 16 | fn C.SSL_set_fd() int 17 | fn C.SSL_connect() int 18 | fn C.SSL_shutdown() 19 | fn C.SSL_free() 20 | fn C.SSL_CTX_free() 21 | fn C.SSL_write() int 22 | fn C.SSL_read() int 23 | 24 | fn (ws mut Client) connect_ssl(){ 25 | l.i("Using secure SSL connection") 26 | C.SSL_load_error_strings() 27 | C.SSL_library_init() 28 | 29 | ws.sslctx = SSL_CTX_new(SSLv23_client_method()) 30 | if ws.sslctx == C.NULL { 31 | l.f("Couldn't get ssl context") 32 | } 33 | 34 | ws.ssl = SSL_new(ws.sslctx) 35 | if ws.ssl == C.NULL { 36 | l.f("Couldn't create OpenSSL instance.") 37 | } 38 | 39 | if SSL_set_fd(ws.ssl, ws.socket.sockfd) != 1 { 40 | l.f("Couldn't assign ssl to socket.") 41 | } 42 | 43 | if SSL_connect(ws.ssl) != 1 { 44 | l.f("Couldn't connect using SSL.") 45 | } 46 | } -------------------------------------------------------------------------------- /ws/utf8.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | pub fn utf8_validate_str(str string) bool { 4 | return utf8_validate(str.str, str.len) 5 | } 6 | struct Utf8State { 7 | mut: 8 | index int 9 | subindex int 10 | failed bool 11 | } 12 | pub fn utf8_validate(data byteptr, len int) bool { 13 | mut state := Utf8State{} 14 | for i := 0; i < len; i++ { 15 | s := data[i] 16 | if s == 0 {break} 17 | state.next_state(s) 18 | if state.failed { 19 | return false 20 | } 21 | //i++ //fast forward 22 | } 23 | return !state.failed && state.subindex <= 0 24 | } 25 | 26 | fn (s mut Utf8State) seq(r0 bool, r1 bool, is_tail bool) bool { 27 | if s.subindex == 0 || (s.index > 1 && s.subindex == 1) || (s.index >= 6 && s.subindex == 2) { 28 | if (s.subindex == 0 && r0) || (s.subindex == 1 && r1) || (s.subindex == 2 && is_tail) { 29 | s.subindex++ 30 | return true 31 | } 32 | goto next 33 | } 34 | else { 35 | s.failed = true 36 | if is_tail { 37 | s.index = 0 38 | s.subindex = 0 39 | s.failed = false 40 | } 41 | return true 42 | } 43 | next: 44 | s.index++ 45 | s.subindex = 0 46 | return false 47 | } 48 | 49 | fn (s mut Utf8State) next_state (c byte) { 50 | //sequence 1 51 | if s.index == 0 { 52 | if ((c >= 0x00 + 1 && c <= 0x7F) || c == 0x00) { 53 | return 54 | } 55 | s.index++ 56 | s.subindex = 0 57 | } 58 | is_tail := c >= 0x80 && c <= 0xBF 59 | //sequence 2 60 | if s.index == 1 && s.seq(c >= 0xC2 && c <= 0xDF, false, is_tail) {return} 61 | 62 | //sequence 3 63 | if s.index == 2 && s.seq(c == 0xE0, c >= 0xA0 && c <= 0xBF, is_tail) {return} 64 | if s.index == 3 && s.seq(c >= 0xE1 && c <= 0xEC, c >= 0x80 && c <= 0xBF, is_tail) {return} 65 | if s.index == 4 && s.seq(c == 0xED, c >= 0x80 && c <= 0x9F, is_tail) {return} 66 | if s.index == 5 && s.seq(c >= 0xEE && c <= 0xEF, c >= 0x80 && c <= 0xBF, is_tail) {return} 67 | 68 | //sequence 4 69 | if s.index == 6 && s.seq(c == 0xF0, c >= 0x90 && c <= 0xBF, is_tail) {return} 70 | if s.index == 7 && s.seq(c >= 0xF1 && c <= 0xF3, c >= 0x80 && c <= 0xBF, is_tail) {return} 71 | if s.index == 8 && s.seq(c == 0xF4, c >= 0x80 && c <= 0x8F, is_tail) {return} 72 | 73 | //we should never reach here 74 | s.failed = true 75 | } -------------------------------------------------------------------------------- /ws/utils.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | import ( 4 | time 5 | rand 6 | math 7 | crypto.sha1 8 | encoding.base64 9 | ) 10 | 11 | fn htonl64(payload_len u64) byteptr { 12 | mut ret := malloc(8) 13 | 14 | ret[0] = byte(((payload_len & (u64(0xff) << 56)) >> 56) & 0xff) 15 | ret[1] = byte(((payload_len & (u64(0xff) << 48)) >> 48) & 0xff) 16 | ret[2] = byte(((payload_len & (u64(0xff) << 40)) >> 40) & 0xff) 17 | ret[3] = byte(((payload_len & (u64(0xff) << 32)) >> 32) & 0xff) 18 | ret[4] = byte(((payload_len & (u64(0xff) << 24)) >> 24) & 0xff) 19 | ret[5] = byte(((payload_len & (u64(0xff) << 16)) >> 16) & 0xff) 20 | ret[6] = byte(((payload_len & (u64(0xff) << 8)) >> 8) & 0xff) 21 | ret[7] = byte(((payload_len & (u64(0xff) << 0)) >> 0) & 0xff) 22 | return ret 23 | } 24 | 25 | fn create_masking_key() []byte { 26 | t := time.ticks() 27 | tseq := t % 23237671 28 | mut rnd := rand.new_pcg32(u64(t), u64(tseq) ) 29 | mask_bit := byte(rnd.bounded_next(u32(math.max_i32))) 30 | buf := [`0`].repeat(4) 31 | C.memcpy(buf.data, &mask_bit, 4) 32 | return buf 33 | } 34 | 35 | fn create_key_challenge_response(seckey string) string { 36 | guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 37 | sha1buf := seckey + guid 38 | hash := sha1.sum(sha1buf.bytes()) 39 | hashstr := string(byteptr(hash.data)) 40 | b64 := base64.encode(hashstr) 41 | unsafe { 42 | sha1buf.free() 43 | hash.free() 44 | } 45 | return b64 46 | } 47 | 48 | fn get_nonce() string { 49 | mut nonce := []byte 50 | alphanum := "0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz" 51 | for i in 0..16 { 52 | nonce << alphanum[rand.next(61)] 53 | } 54 | return string(byteptr(nonce.data)) 55 | } -------------------------------------------------------------------------------- /ws/ws.v: -------------------------------------------------------------------------------- 1 | module ws 2 | 3 | import ( 4 | net 5 | net.urllib 6 | encoding.base64 7 | eventbus 8 | sync 9 | logger 10 | ) 11 | 12 | const ( 13 | l = logger.new("ws") 14 | ) 15 | 16 | pub struct Client { 17 | retry int 18 | eb &eventbus.EventBus 19 | is_ssl bool 20 | lock sync.Mutex = sync.new_mutex() 21 | write_lock sync.Mutex = sync.new_mutex() 22 | //subprotocol_len int 23 | //cwebsocket_subprotocol *subprotocol; 24 | //cwebsocket_subprotocol *subprotocols[]; 25 | mut: 26 | state State 27 | socket net.Socket 28 | flags []Flag 29 | sslctx &C.SSL_CTX 30 | ssl &C.SSL 31 | fragments []Fragment 32 | pub mut: 33 | uri string 34 | subscriber &eventbus.Subscriber 35 | } 36 | 37 | struct Fragment { 38 | data voidptr 39 | len u64 40 | code OPCode 41 | } 42 | 43 | struct Message { 44 | opcode OPCode 45 | payload voidptr 46 | payload_len int 47 | } 48 | 49 | pub enum OPCode { 50 | continuation = 0x00, 51 | text_frame = 0x01, 52 | binary_frame = 0x02, 53 | close = 0x08, 54 | ping = 0x09, 55 | pong = 0x0A 56 | } 57 | 58 | enum State { 59 | connecting = 0, 60 | connected, 61 | open, 62 | closing, 63 | closed 64 | } 65 | 66 | struct Uri { 67 | mut: 68 | hostname string 69 | port string 70 | resource string 71 | querystring string 72 | } 73 | 74 | enum Flag { 75 | has_accept, 76 | has_connection, 77 | has_upgrade 78 | } 79 | 80 | struct Frame { 81 | mut: 82 | fin bool 83 | rsv1 bool 84 | rsv2 bool 85 | rsv3 bool 86 | opcode OPCode 87 | mask bool 88 | payload_len u64 89 | masking_key [4]byte 90 | } 91 | 92 | pub fn new(uri string) &Client { 93 | eb := eventbus.new() 94 | ws := &Client{ 95 | uri: uri 96 | state: .closed 97 | eb: eb, 98 | subscriber: eb.subscriber 99 | is_ssl: uri.starts_with("wss") 100 | ssl: C.NULL 101 | sslctx: C.NULL 102 | } 103 | return ws 104 | } 105 | fn C.sscanf() int 106 | 107 | fn (ws &Client) parse_uri() &Uri { 108 | u := urllib.parse(ws.uri) or {panic(err)} 109 | v := u.request_uri().split("?") 110 | querystring := if v.len > 1 {"?" + v[1]} else {""} 111 | return &Uri { 112 | hostname: u.hostname() 113 | port: u.port() 114 | resource: v[0] 115 | querystring: querystring 116 | } 117 | } 118 | 119 | pub fn (ws mut Client) connect() int { 120 | if ws.state == .connected { 121 | l.f("connect: websocket already connected") 122 | } else if ws.state == .connecting { 123 | l.f("connect: websocket already connecting") 124 | } else if ws.state == .open { 125 | l.f("connect: websocket already open") 126 | } 127 | 128 | ws.lock.lock() 129 | ws.state = .connecting 130 | ws.lock.unlock() 131 | 132 | uri := ws.parse_uri() 133 | 134 | nonce := get_nonce() 135 | seckey := base64.encode(nonce) 136 | 137 | ai_family := C.AF_INET 138 | ai_socktype := C.SOCK_STREAM 139 | 140 | 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" 141 | 142 | socket := net.new_socket(ai_family, ai_socktype, 0) or { 143 | l.f(err) 144 | return -1 145 | } 146 | ws.socket = socket 147 | ws.socket.connect(uri.hostname, uri.port.int()) or { 148 | l.f(err) 149 | return -1 150 | } 151 | 152 | optval := 1 153 | ws.socket.setsockopt(C.SOL_SOCKET, C.SO_KEEPALIVE, &optval) or { 154 | l.f(err) 155 | return -1 156 | } 157 | 158 | if ws.is_ssl { 159 | ws.connect_ssl() 160 | } 161 | 162 | ws.lock.lock() 163 | ws.state = .connected 164 | ws.lock.unlock() 165 | 166 | res := ws.write_to_server(handshake.str, handshake.len) 167 | if res <= 0 { 168 | l.f("Handshake failed.") 169 | } 170 | 171 | ws.read_handshake(seckey) 172 | 173 | ws.lock.lock() 174 | ws.state = .open 175 | ws.lock.unlock() 176 | 177 | ws.send_open_event() 178 | 179 | unsafe { 180 | handshake.free() 181 | nonce.free() 182 | free(uri) 183 | } 184 | return 0 185 | } 186 | 187 | pub fn (ws mut Client) close(code int, message string){ 188 | if ws.state != .closed && ws.socket.sockfd > 1 { 189 | 190 | ws.lock.lock() 191 | ws.state = .closing 192 | ws.lock.unlock() 193 | 194 | mut code32 := 0 195 | if code > 0 { 196 | _code := C.htons(code) 197 | message_len := message.len + 2 198 | mut close_frame := [`0`].repeat(message_len) 199 | close_frame[0] = _code & 0xFF 200 | close_frame[1] = (_code >> 8) 201 | code32 = (close_frame[0] << 8) + close_frame[1] 202 | for i in 0..message.len { 203 | close_frame[i+2] = message[i] 204 | } 205 | ws.send_control_frame(.close, "CLOSE", close_frame) 206 | } else { 207 | ws.send_control_frame(.close, "CLOSE", []) 208 | } 209 | 210 | if ws.ssl != C.NULL { 211 | SSL_shutdown(ws.ssl) 212 | SSL_free(ws.ssl) 213 | if ws.sslctx != C.NULL { 214 | SSL_CTX_free(ws.sslctx) 215 | } 216 | } else { 217 | if C.shutdown(ws.socket.sockfd, C.SHUT_WR) == -1 { 218 | l.e("Unabled to shutdown websocket.") 219 | } 220 | mut buf := [`0`] 221 | for ws.read_from_server(buf.data, 1) > 0 { 222 | buf[0] = `\0` 223 | } 224 | unsafe { 225 | buf.free() 226 | } 227 | if C.close(ws.socket.sockfd) == -1 { 228 | //ws.send_close_event()(websocket, 1011, strerror(errno)); 229 | } 230 | } 231 | ws.fragments = [] 232 | ws.send_close_event() 233 | 234 | ws.lock.lock() 235 | ws.state = .closed 236 | ws.lock.unlock() 237 | unsafe { 238 | 239 | } 240 | //TODO impl autoreconnect 241 | } 242 | } 243 | 244 | pub fn (ws mut Client) write(payload byteptr, payload_len int, code OPCode) int { 245 | if ws.state != .open { 246 | ws.send_error_event("WebSocket closed. Cannot write.") 247 | goto free_data 248 | return -1 249 | } 250 | 251 | header_len := 6 + if payload_len > 125 {2} else {0} + if payload_len > 0xffff {6} else {0} 252 | masking_key := create_masking_key() 253 | mut header := [`0`].repeat(header_len) 254 | mut bytes_written := -1 255 | 256 | header[0] = (int(code) | 0x80) 257 | if payload_len <= 125 { 258 | header[1] = (payload_len | 0x80) 259 | header[2] = masking_key[0] 260 | header[3] = masking_key[1] 261 | header[4] = masking_key[2] 262 | header[5] = masking_key[3] 263 | } else if payload_len > 125 && payload_len <= 0xffff { 264 | len16 := C.htons(payload_len) 265 | header[1] = (126 | 0x80) 266 | C.memcpy(header.data+2, &len16, 2) 267 | header[4] = masking_key[0] 268 | header[5] = masking_key[1] 269 | header[6] = masking_key[2] 270 | header[7] = masking_key[3] 271 | } else if payload_len > 0xffff && payload_len <= 0xffffffffffffffff { // 65535 && 18446744073709551615 272 | len64 := htonl64(u64(payload_len)) 273 | header[1] = (127 | 0x80) 274 | C.memcpy(header.data+2, len64, 8) 275 | header[10] = masking_key[0] 276 | header[11] = masking_key[1] 277 | header[12] = masking_key[2] 278 | header[13] = masking_key[3] 279 | } else { 280 | l.c("write: frame too large") 281 | ws.close(1009, "frame too large") 282 | goto free_data 283 | return -1 284 | } 285 | 286 | frame_len := header_len + payload_len 287 | mut frame_buf := [`0`].repeat(frame_len) 288 | 289 | C.memcpy(frame_buf.data, header.data, header_len) 290 | C.memcpy(&frame_buf.data[header_len], payload, payload_len) 291 | 292 | for i in 0..payload_len { 293 | frame_buf[header_len+i] ^= masking_key[i % 4] & 0xff 294 | } 295 | 296 | bytes_written = ws.write_to_server(frame_buf.data, frame_len) 297 | if bytes_written == -1 { 298 | err := string(byteptr(C.strerror(C.errno))) 299 | l.e("write: there was an error writing data: ${err}") 300 | ws.send_error_event("Error writing data") 301 | goto free_data 302 | return -1 303 | } 304 | l.d("write: ${bytes_written} bytes written.") 305 | free_data: 306 | unsafe { 307 | free(payload) 308 | frame_buf.free() 309 | header.free() 310 | masking_key.free() 311 | } 312 | return bytes_written 313 | } 314 | 315 | pub fn (ws mut Client) listen() { 316 | l.i("Starting listener...") 317 | for ws.state == .open { 318 | ws.read() 319 | } 320 | l.i("Listener stopped as websocket was closed.") 321 | } 322 | 323 | pub fn (ws mut Client) read() int { 324 | mut bytes_read := u64(0) 325 | 326 | initial_buffer := u64(256) 327 | mut header_len := 2 328 | header_len_offset := 2 329 | extended_payload16_end_byte := 4 330 | extended_payload64_end_byte := 10 331 | 332 | mut payload_len := u64(0) 333 | mut data := C.calloc(initial_buffer, 1)//[`0`].repeat(int(max_buffer)) 334 | 335 | mut frame := Frame{} 336 | mut frame_size := u64(header_len) 337 | 338 | for bytes_read < frame_size && ws.state == .open { 339 | byt := ws.read_from_server(data + int(bytes_read), 1) 340 | match byt { 341 | 0 { 342 | error := "server closed the connection." 343 | l.e("read: ${error}") 344 | ws.send_error_event(error) 345 | ws.close(1006, error) 346 | goto free_data 347 | return -1 348 | } 349 | -1 { 350 | err := string(byteptr(C.strerror(C.errno))) 351 | l.e("read: error reading frame. ${err}") 352 | ws.send_error_event("error reading frame") 353 | goto free_data 354 | return -1 355 | } else { 356 | bytes_read++ 357 | } 358 | } 359 | if bytes_read == u64(header_len_offset) { 360 | frame.fin = (data[0] & 0x80) == 0x80 361 | frame.rsv1 = (data[0] & 0x40) == 0x40 362 | frame.rsv2 = (data[0] & 0x20) == 0x20 363 | frame.rsv3 = (data[0] & 0x10) == 0x10 364 | frame.opcode = OPCode(int(data[0] & 0x7F)) 365 | frame.mask = (data[1] & 0x80) == 0x80 366 | frame.payload_len = u64(data[1] & 0x7F) 367 | 368 | //masking key 369 | if frame.mask { 370 | frame.masking_key[0] = data[2] 371 | frame.masking_key[1] = data[3] 372 | frame.masking_key[2] = data[4] 373 | frame.masking_key[3] = data[5] 374 | } 375 | 376 | payload_len = frame.payload_len 377 | frame_size = u64(header_len) + payload_len 378 | } 379 | 380 | if frame.payload_len == u64(126) && bytes_read == u64(extended_payload16_end_byte) { 381 | header_len += 2 382 | 383 | mut extended_payload_len := 0 384 | extended_payload_len |= data[2] << 8 385 | extended_payload_len |= data[3] << 0 386 | 387 | //masking key 388 | if frame.mask { 389 | frame.masking_key[0] = data[4] 390 | frame.masking_key[1] = data[5] 391 | frame.masking_key[2] = data[6] 392 | frame.masking_key[3] = data[7] 393 | } 394 | 395 | payload_len = u64(extended_payload_len) 396 | frame_size = u64(header_len) + payload_len 397 | if frame_size > initial_buffer { 398 | l.d("reallocating: ${frame_size}") 399 | data = C.realloc(data, frame_size) 400 | } 401 | } else if frame.payload_len == u64(127) && bytes_read == u64(extended_payload64_end_byte) { 402 | header_len += 8 //TODO Not sure... 403 | 404 | mut extended_payload_len := u64(0) 405 | extended_payload_len |= u64(data[2]) << 56 406 | extended_payload_len |= u64(data[3]) << 48 407 | extended_payload_len |= u64(data[4]) << 40 408 | extended_payload_len |= u64(data[5]) << 32 409 | extended_payload_len |= u64(data[6]) << 24 410 | extended_payload_len |= u64(data[7]) << 16 411 | extended_payload_len |= u64(data[8]) << 8 412 | extended_payload_len |= u64(data[9]) << 0 413 | 414 | //masking key 415 | if frame.mask { 416 | frame.masking_key[0] = data[10] 417 | frame.masking_key[1] = data[11] 418 | frame.masking_key[2] = data[12] 419 | frame.masking_key[3] = data[13] 420 | } 421 | 422 | payload_len = extended_payload_len 423 | frame_size = u64(header_len) + payload_len 424 | if frame_size > initial_buffer { 425 | l.d("reallocating: ${frame_size}") 426 | data = C.realloc(data, frame_size) 427 | } 428 | } 429 | } 430 | 431 | // unmask the payload 432 | if frame.mask { 433 | for i in 0..payload_len { 434 | data[header_len+i] ^= frame.masking_key[i % 4] & 0xff 435 | } 436 | } 437 | 438 | if ws.fragments.len > 0 && frame.opcode in [.text_frame, .binary_frame] { 439 | ws.close(0, "") 440 | goto free_data 441 | return -1 442 | } else if frame.opcode in [.text_frame, .binary_frame] { 443 | data_node: 444 | l.d("read: recieved text_frame or binary_frame") 445 | mut payload := malloc(sizeof(byte) * int(payload_len) + 1) 446 | if payload == C.NULL { 447 | l.f("out of memory") 448 | } 449 | C.memcpy(payload, &data[header_len], payload_len) 450 | if frame.fin { 451 | if ws.fragments.len > 0 { 452 | //join fragments 453 | ws.fragments << Fragment{ 454 | data: payload 455 | len: payload_len 456 | } 457 | mut frags := []Fragment 458 | mut size := u64(0) 459 | for f in ws.fragments { 460 | if f.len > 0 { 461 | frags << f 462 | size += f.len 463 | } 464 | } 465 | mut pl := malloc(sizeof(byte) * int(size)) 466 | if pl == C.NULL { 467 | l.f("out of memory") 468 | } 469 | mut by := 0 470 | for i, f in frags { 471 | C.memcpy(pl + by, f.data, f.len) 472 | by += int(f.len) 473 | unsafe {free(f.data)} 474 | } 475 | payload = pl 476 | frame.opcode = ws.fragments[0].code 477 | payload_len = size 478 | //clear the fragments 479 | unsafe { 480 | ws.fragments.free() 481 | } 482 | ws.fragments = [] 483 | } 484 | payload[payload_len] = `\0` 485 | if frame.opcode == .text_frame && payload_len > 0 { 486 | if !utf8_validate(payload, int(payload_len)) { 487 | l.e("malformed utf8 payload") 488 | ws.send_error_event("Recieved malformed utf8.") 489 | ws.close(1007, "malformed utf8 payload") 490 | goto free_data 491 | return -1 492 | } 493 | } 494 | message := Message { 495 | opcode: frame.opcode 496 | payload: payload 497 | payload_len: int(payload_len) 498 | } 499 | ws.send_message_event(message) 500 | } else { 501 | //fragment start. 502 | ws.fragments << Fragment{ 503 | data: payload 504 | len: payload_len 505 | code: frame.opcode 506 | } 507 | } 508 | unsafe { 509 | free(data) 510 | } 511 | return int(bytes_read) 512 | } 513 | else if frame.opcode == .continuation { 514 | l.d("read: continuation") 515 | if ws.fragments.len <= 0 { 516 | l.e("Nothing to continue.") 517 | ws.close(1002, "nothing to continue") 518 | goto free_data 519 | return -1 520 | } 521 | goto data_node 522 | return 0 523 | } 524 | else if frame.opcode == .ping { 525 | l.d("read: ping") 526 | if !frame.fin { 527 | ws.close(1002, "control message must not be fragmented") 528 | goto free_data 529 | return -1 530 | } 531 | if frame.payload_len > 125 { 532 | ws.close(1002, "control frames must not exceed 125 bytes") 533 | goto free_data 534 | return -1 535 | } 536 | mut payload := []byte 537 | if payload_len > 0 { 538 | payload = [`0`].repeat(int(payload_len)) 539 | C.memcpy(payload.data, &data[header_len], payload_len) 540 | } 541 | unsafe { 542 | free(data) 543 | } 544 | return ws.send_control_frame(.pong, "PONG", payload) 545 | } 546 | else if frame.opcode == .pong { 547 | if !frame.fin { 548 | ws.close(1002, "control message must not be fragmented") 549 | goto free_data 550 | return -1 551 | } 552 | unsafe { 553 | free(data) 554 | } 555 | //got pong 556 | return 0 557 | } 558 | else if frame.opcode == .close { 559 | l.d("read: close") 560 | if frame.payload_len > 125 { 561 | ws.close(1002, "control frames must not exceed 125 bytes") 562 | goto free_data 563 | return -1 564 | } 565 | mut code := 0 566 | mut reason := "" 567 | if payload_len > 2 { 568 | code = (int(data[header_len]) << 8) + int(data[header_len+1]) 569 | header_len += 2 570 | payload_len -= 2 571 | reason = string(&data[header_len]) 572 | l.i("Closing with reason: ${reason} & code: ${code}") 573 | if reason.len > 1 && !utf8_validate(reason.str, reason.len) { 574 | l.e("malformed utf8 payload") 575 | ws.send_error_event("Recieved malformed utf8.") 576 | ws.close(1007, "malformed utf8 payload") 577 | goto free_data 578 | return -1 579 | } 580 | } 581 | unsafe{ 582 | free(data) 583 | } 584 | ws.close(code, reason) 585 | return 0 586 | } 587 | l.e("read: Recieved unsupported opcode: ${frame.opcode} fin: ${frame.fin} uri: ${ws.uri}") 588 | ws.send_error_event("Recieved unsupported opcode: ${frame.opcode}") 589 | ws.close(1002, "Unsupported opcode") 590 | free_data: 591 | unsafe { 592 | free(data) 593 | } 594 | return -1 595 | } 596 | 597 | fn (ws mut Client) send_control_frame(code OPCode, frame_typ string, payload []byte) int { 598 | if ws.socket.sockfd <= 0 { 599 | l.e("No socket opened.") 600 | goto free_data 601 | return -1 602 | } 603 | header_len := 6 604 | frame_len := header_len + payload.len 605 | mut control_frame := [`0`].repeat(frame_len) 606 | masking_key := create_masking_key() 607 | control_frame[0] = (int(code) | 0x80) 608 | control_frame[1] = (payload.len | 0x80) 609 | control_frame[2] = masking_key[0] 610 | control_frame[3] = masking_key[1] 611 | control_frame[4] = masking_key[2] 612 | control_frame[5] = masking_key[3] 613 | if code == .close { 614 | close_code := 1000 615 | if payload.len > 2 { 616 | mut parsed_payload := [`0`].repeat(payload.len + 1) 617 | C.memcpy(parsed_payload.data, &payload[0], payload.len) 618 | parsed_payload[payload.len] = `\0` 619 | for i in 0..payload.len { 620 | control_frame[6+i] = (parsed_payload[i] ^ masking_key[i % 4]) & 0xff 621 | } 622 | unsafe { 623 | parsed_payload.free() 624 | } 625 | } 626 | } else { 627 | for i in 0..payload.len { 628 | control_frame[header_len + i] = (payload[i] ^ masking_key[i % 4]) & 0xff 629 | } 630 | } 631 | mut bytes_written := -1 632 | bytes_written = ws.write_to_server(control_frame.data, frame_len) 633 | free_data: 634 | unsafe { 635 | control_frame.free() 636 | payload.free() 637 | masking_key.free() 638 | } 639 | match bytes_written { 640 | 0 { 641 | l.d("send_control_frame: remote host closed the connection.") 642 | return 0 643 | } 644 | -1 { 645 | l.c("send_control_frame: error sending ${frame_typ} control frame.") 646 | return -1 647 | } else { 648 | l.d("send_control_frame: wrote ${bytes_written} byte ${frame_typ} frame.") 649 | return bytes_written 650 | } 651 | } 652 | } --------------------------------------------------------------------------------