mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
All public headers should follow the pattern `jerryscript[-*].h`. The `jerry-api.h` to `jerryscript.h` renaming has already happened a while ago, now it's time for the port API header to follow. This patch * renames the public header file, * updates all includes to use the new file name (in `jerry-main`, in all the targets, and in the docs), and * keeps `jerry-port.h` as a deprecated forwarding header to leave some time for external users to follow up with this change. As a related change, the header of the default port implementation is also changed to `jerryscript-port-default.h`. JerryScript-DCO-1.0-Signed-off-by: Akos Kiss akiss@inf.u-szeged.hu
561 lines
15 KiB
C
561 lines
15 KiB
C
/* 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.
|
|
*/
|
|
|
|
#ifdef JERRY_DEBUGGER
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
#include "jcontext.h"
|
|
#include "jerry-debugger.h"
|
|
#include "jerryscript-port.h"
|
|
|
|
/**
|
|
* Debugger socket communication port.
|
|
*/
|
|
#ifndef JERRY_DEBUGGER_PORT
|
|
#define JERRY_DEBUGGER_PORT 5001
|
|
#endif
|
|
|
|
/**
|
|
* 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);
|
|
|
|
JERRY_ASSERT (JERRY_CONTEXT (jerry_init_flags) & JERRY_INIT_DEBUGGER);
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons (JERRY_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 */
|