/* Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "debugger.h" #include "jcontext.h" #include "jerryscript-port.h" #ifdef JERRY_DEBUGGER #include #include #include #include /** * Masking-key is available. */ #define JERRY_DEBUGGER_WEBSOCKET_MASK_BIT 0x80 /** * Opcode type mask. */ #define JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0fu /** * Packet length mask. */ #define JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7fu /** * Payload mask size in bytes of a websocket package. */ #define JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE 4 /** * Header for incoming packets. */ typedef struct { uint8_t ws_opcode; /**< websocket opcode */ uint8_t size; /**< size of the message */ uint8_t mask[4]; /**< mask bytes */ } jerry_debugger_receive_header_t; /** * Close the socket connection to the client. */ static void jerry_debugger_close_connection_tcp (bool log_error) /**< log error */ { JERRY_ASSERT (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED); JERRY_CONTEXT (debugger_flags) = (uint8_t) JERRY_DEBUGGER_VM_IGNORE; if (log_error) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); } jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Debugger client connection closed.\n"); close (JERRY_CONTEXT (debugger_connection)); JERRY_CONTEXT (debugger_connection) = -1; jerry_debugger_free_unreferenced_byte_code (); } /* jerry_debugger_close_connection_tcp */ /** * Send message to the client side. * * @return true - if the data was sent successfully to the client side * false - otherwise */ static bool jerry_debugger_send_tcp (const uint8_t *data_p, /**< data pointer */ size_t data_size) /**< data size */ { JERRY_ASSERT (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED); do { ssize_t sent_bytes = send (JERRY_CONTEXT (debugger_connection), data_p, data_size, 0); if (sent_bytes < 0) { if (errno == EWOULDBLOCK) { continue; } jerry_debugger_close_connection_tcp (true); return false; } data_size -= (size_t) sent_bytes; data_p += sent_bytes; } while (data_size > 0); return true; } /* jerry_debugger_send_tcp */ /** * Convert a 6-bit value to a Base64 character. * * @return Base64 character */ static uint8_t jerry_to_base64_character (uint8_t value) /**< 6-bit value */ { if (value < 26) { return (uint8_t) (value + 'A'); } if (value < 52) { return (uint8_t) (value - 26 + 'a'); } if (value < 62) { return (uint8_t) (value - 52 + '0'); } if (value == 62) { return (uint8_t) '+'; } return (uint8_t) '/'; } /* jerry_to_base64_character */ /** * Encode a byte sequence into Base64 string. */ static void jerry_to_base64 (const uint8_t *source_p, /**< source data */ uint8_t *destination_p, /**< destination buffer */ size_t length) /**< length of source, must be divisible by 3 */ { while (length >= 3) { uint8_t value = (source_p[0] >> 2); destination_p[0] = jerry_to_base64_character (value); value = (uint8_t) (((source_p[0] << 4) | (source_p[1] >> 4)) & 0x3f); destination_p[1] = jerry_to_base64_character (value); value = (uint8_t) (((source_p[1] << 2) | (source_p[2] >> 6)) & 0x3f); destination_p[2] = jerry_to_base64_character (value); value = (uint8_t) (source_p[2] & 0x3f); destination_p[3] = jerry_to_base64_character (value); source_p += 3; destination_p += 4; length -= 3; } } /* jerry_to_base64 */ /** * Process WebSocket handshake. * * @return true - if the handshake was completed successfully * false - otherwise */ static bool jerry_process_handshake (int client_socket, /**< client socket */ uint8_t *request_buffer_p) /**< temporary buffer */ { size_t request_buffer_size = 1024; uint8_t *request_end_p = request_buffer_p; /* Buffer request text until the double newlines are received. */ while (true) { size_t length = request_buffer_size - 1u - (size_t) (request_end_p - request_buffer_p); if (length == 0) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Handshake buffer too small.\n"); return false; } ssize_t size = recv (client_socket, request_end_p, length, 0); if (size < 0) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } request_end_p += (size_t) size; *request_end_p = 0; if (request_end_p > request_buffer_p + 4 && memcmp (request_end_p - 4, "\r\n\r\n", 4) == 0) { break; } } /* Check protocol. */ const char *text_p = "GET /jerry-debugger"; size_t text_len = strlen (text_p); if ((size_t) (request_end_p - request_buffer_p) < text_len || memcmp (request_buffer_p, text_p, text_len) != 0) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Invalid handshake format.\n"); return false; } uint8_t *websocket_key_p = request_buffer_p + text_len; text_p = "Sec-WebSocket-Key:"; text_len = strlen (text_p); while (true) { if ((size_t) (request_end_p - websocket_key_p) < text_len) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Sec-WebSocket-Key not found.\n"); return false; } if (websocket_key_p[0] == 'S' && websocket_key_p[-1] == '\n' && websocket_key_p[-2] == '\r' && memcmp (websocket_key_p, text_p, text_len) == 0) { websocket_key_p += text_len; break; } websocket_key_p++; } /* String terminated by double newlines. */ while (*websocket_key_p == ' ') { websocket_key_p++; } uint8_t *websocket_key_end_p = websocket_key_p; while (*websocket_key_end_p > ' ') { websocket_key_end_p++; } /* Since the request_buffer_p is not needed anymore it can * be reused for storing the SHA-1 key and Base64 string. */ const size_t sha1_length = 20; jerry_debugger_compute_sha1 (websocket_key_p, (size_t) (websocket_key_end_p - websocket_key_p), (const uint8_t *) "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36, request_buffer_p); /* The SHA-1 key is 20 bytes long but jerry_to_base64 expects * a length divisible by 3 so an extra 0 is appended at the end. */ request_buffer_p[sha1_length] = 0; jerry_to_base64 (request_buffer_p, request_buffer_p + sha1_length + 1, sha1_length + 1); /* Last value must be replaced by equal sign. */ text_p = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; if (!jerry_debugger_send_tcp ((const uint8_t *) text_p, strlen (text_p)) || !jerry_debugger_send_tcp (request_buffer_p + sha1_length + 1, 27)) { return false; } text_p = "=\r\n\r\n"; return jerry_debugger_send_tcp ((const uint8_t *) text_p, strlen (text_p)); } /* jerry_process_handshake */ /** * Initialize the socket connection. * * @return true - if the connection succeeded * false - otherwise */ bool jerry_debugger_accept_connection (void) { int server_socket; struct sockaddr_in addr; socklen_t sin_size = sizeof (struct sockaddr_in); addr.sin_family = AF_INET; addr.sin_port = htons (JERRY_CONTEXT (debugger_port)); addr.sin_addr.s_addr = INADDR_ANY; if ((server_socket = socket (AF_INET, SOCK_STREAM, 0)) == -1) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } int opt_value = 1; if (setsockopt (server_socket, SOL_SOCKET, SO_REUSEADDR, &opt_value, sizeof (int)) == -1) { close (server_socket); jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } if (bind (server_socket, (struct sockaddr *)&addr, sizeof (struct sockaddr)) == -1) { close (server_socket); jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } if (listen (server_socket, 1) == -1) { close (server_socket); jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Waiting for client connection\n"); JERRY_CONTEXT (debugger_connection) = accept (server_socket, (struct sockaddr *)&addr, &sin_size); if (JERRY_CONTEXT (debugger_connection) == -1) { close (server_socket); jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", strerror (errno)); return false; } close (server_socket); JERRY_CONTEXT (debugger_flags) = (uint8_t) (JERRY_CONTEXT (debugger_flags) | JERRY_DEBUGGER_CONNECTED); bool is_handshake_ok = false; JMEM_DEFINE_LOCAL_ARRAY (request_buffer_p, 1024, uint8_t); is_handshake_ok = jerry_process_handshake (JERRY_CONTEXT (debugger_connection), request_buffer_p); JMEM_FINALIZE_LOCAL_ARRAY (request_buffer_p); if (!is_handshake_ok) { jerry_debugger_close_connection (); return false; } if (!jerry_debugger_send_configuration (JERRY_DEBUGGER_MAX_RECEIVE_SIZE)) { return false; } /* Set non-blocking mode. */ int socket_flags = fcntl (JERRY_CONTEXT (debugger_connection), F_GETFL, 0); if (socket_flags < 0) { jerry_debugger_close_connection_tcp (true); return false; } if (fcntl (JERRY_CONTEXT (debugger_connection), F_SETFL, socket_flags | O_NONBLOCK) == -1) { jerry_debugger_close_connection_tcp (true); return false; } jerry_port_log (JERRY_LOG_LEVEL_DEBUG, "Connected from: %s\n", inet_ntoa (addr.sin_addr)); JERRY_CONTEXT (debugger_flags) = (uint8_t) (JERRY_CONTEXT (debugger_flags) | JERRY_DEBUGGER_VM_STOP); JERRY_CONTEXT (debugger_stop_context) = NULL; return true; } /* jerry_debugger_accept_connection */ /** * Close the socket connection to the client. */ inline void __attr_always_inline___ jerry_debugger_close_connection (void) { jerry_debugger_close_connection_tcp (false); } /* jerry_debugger_close_connection */ /** * Send message to the client side * * @return true - if the data was sent successfully to the debugger client, * false - otherwise */ inline bool __attr_always_inline___ jerry_debugger_send (size_t data_size) /**< data size */ { return jerry_debugger_send_tcp (JERRY_CONTEXT (debugger_send_buffer), data_size); } /* jerry_debugger_send */ JERRY_STATIC_ASSERT (JERRY_DEBUGGER_MAX_RECEIVE_SIZE < 126, maximum_debug_message_receive_size_must_be_smaller_than_126); /** * Receive message from the client. * * Note: * If the function returns with true, the value of * JERRY_DEBUGGER_VM_STOP flag should be ignored. * * @return true - if execution should be resumed, * false - otherwise */ bool jerry_debugger_receive (void) { JERRY_ASSERT (JERRY_CONTEXT (debugger_flags) & JERRY_DEBUGGER_CONNECTED); JERRY_CONTEXT (debugger_message_delay) = JERRY_DEBUGGER_MESSAGE_FREQUENCY; uint8_t *recv_buffer_p = JERRY_CONTEXT (debugger_receive_buffer); bool resume_exec = false; uint8_t expected_message_type = 0; void *message_data = NULL; while (true) { uint32_t offset = JERRY_CONTEXT (debugger_receive_buffer_offset); ssize_t byte_recv = recv (JERRY_CONTEXT (debugger_connection), recv_buffer_p + offset, JERRY_DEBUGGER_MAX_BUFFER_SIZE - offset, 0); if (byte_recv < 0) { if (errno != EWOULDBLOCK) { jerry_debugger_close_connection_tcp (true); return true; } byte_recv = 0; } offset += (uint32_t) byte_recv; JERRY_CONTEXT (debugger_receive_buffer_offset) = (uint16_t) offset; if (offset < sizeof (jerry_debugger_receive_header_t)) { if (expected_message_type != 0) { continue; } return resume_exec; } if ((recv_buffer_p[0] & ~JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK) != JERRY_DEBUGGER_WEBSOCKET_FIN_BIT || (recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK) > JERRY_DEBUGGER_MAX_RECEIVE_SIZE || !(recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_MASK_BIT)) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Unsupported Websocket message.\n"); jerry_debugger_close_connection (); return true; } if ((recv_buffer_p[0] & JERRY_DEBUGGER_WEBSOCKET_OPCODE_MASK) != JERRY_DEBUGGER_WEBSOCKET_BINARY_FRAME) { jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Unsupported Websocket opcode.\n"); jerry_debugger_close_connection (); return true; } uint32_t message_size = (uint32_t) (recv_buffer_p[1] & JERRY_DEBUGGER_WEBSOCKET_LENGTH_MASK); uint32_t message_total_size = (uint32_t) (message_size + sizeof (jerry_debugger_receive_header_t)); if (offset < message_total_size) { if (expected_message_type != 0) { continue; } return resume_exec; } /* Unmask data bytes. */ uint8_t *data_p = recv_buffer_p + sizeof (jerry_debugger_receive_header_t); const uint8_t *mask_p = data_p - JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE; const uint8_t *mask_end_p = data_p; const uint8_t *data_end_p = data_p + message_size; while (data_p < data_end_p) { /* Invert certain bits with xor operation. */ *data_p = *data_p ^ *mask_p; data_p++; mask_p++; if (mask_p >= mask_end_p) { mask_p -= JERRY_DEBUGGER_WEBSOCKET_MASK_SIZE; } } /* The jerry_debugger_process_message function is inlined * so passing these arguments is essentially free. */ if (!jerry_debugger_process_message (recv_buffer_p + sizeof (jerry_debugger_receive_header_t), message_size, &resume_exec, &expected_message_type, &message_data)) { return true; } if (message_total_size < offset) { memmove (recv_buffer_p, recv_buffer_p + message_total_size, offset - message_total_size); } JERRY_CONTEXT (debugger_receive_buffer_offset) = (uint16_t) (offset - message_total_size); } } /* jerry_debugger_receive */ #endif /* JERRY_DEBUGGER */