1 | /* Copyright JS Foundation and other contributors, http://js.foundation |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); |
4 | * you may not use this file except in compliance with the License. |
5 | * You may obtain a copy of the License at |
6 | * |
7 | * http://www.apache.org/licenses/LICENSE-2.0 |
8 | * |
9 | * Unless required by applicable law or agreed to in writing, software |
10 | * distributed under the License is distributed on an "AS IS" BASIS |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | * See the License for the specific language governing permissions and |
13 | * limitations under the License. |
14 | */ |
15 | |
16 | #include "jerryscript-debugger-transport.h" |
17 | #include "jerryscript-ext/debugger.h" |
18 | #include "jext-common.h" |
19 | |
20 | #if defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1) |
21 | |
22 | #include <errno.h> |
23 | |
24 | #ifdef _WIN32 |
25 | #include <BaseTsd.h> |
26 | #include <WS2tcpip.h> |
27 | #include <winsock2.h> |
28 | |
29 | /* On Windows the WSAEWOULDBLOCK value can be returned for non-blocking operations */ |
30 | #define JERRYX_EWOULDBLOCK WSAEWOULDBLOCK |
31 | |
32 | /* On Windows the invalid socket's value of INVALID_SOCKET */ |
33 | #define JERRYX_SOCKET_INVALID INVALID_SOCKET |
34 | |
35 | /* |
36 | * On Windows, socket functions have the following signatures: |
37 | * int send(SOCKET s, const char *buf, int len, int flags); |
38 | * int recv(SOCKET s, char *buf, int len, int flags); |
39 | * int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen); |
40 | */ |
41 | typedef int jerryx_socket_ssize_t; |
42 | typedef SOCKET jerryx_socket_t; |
43 | typedef char jerryx_socket_void_t; |
44 | typedef int jerryx_socket_size_t; |
45 | #else /* !_WIN32 */ |
46 | #include <arpa/inet.h> |
47 | #include <fcntl.h> |
48 | #include <sys/socket.h> |
49 | #include <unistd.h> |
50 | |
51 | /* On *nix the EWOULDBLOCK errno value can be returned for non-blocking operations */ |
52 | #define JERRYX_EWOULDBLOCK EWOULDBLOCK |
53 | |
54 | /* On *nix the invalid socket has a value of -1 */ |
55 | #define JERRYX_SOCKET_INVALID (-1) |
56 | |
57 | /* |
58 | * On *nix, socket functions have the following signatures: |
59 | * ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
60 | * ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
61 | * int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
62 | */ |
63 | typedef ssize_t jerryx_socket_ssize_t; |
64 | typedef int jerryx_socket_t; |
65 | typedef void jerryx_socket_void_t; |
66 | typedef size_t jerryx_socket_size_t; |
67 | #endif /* _WIN32 */ |
68 | |
69 | /** |
70 | * Implementation of transport over tcp/ip. |
71 | */ |
72 | typedef struct |
73 | { |
74 | jerry_debugger_transport_header_t header; /**< transport header */ |
75 | jerryx_socket_t tcp_socket; /**< tcp socket */ |
76 | } jerryx_debugger_transport_tcp_t; |
77 | |
78 | /** |
79 | * Get the network error value. |
80 | * |
81 | * On Windows this returns the result of the `WSAGetLastError ()` call and |
82 | * on any other system the `errno` value. |
83 | * |
84 | * |
85 | * @return last error value. |
86 | */ |
87 | static inline int |
88 | jerryx_debugger_tcp_get_errno (void) |
89 | { |
90 | #ifdef _WIN32 |
91 | return WSAGetLastError (); |
92 | #else /* !_WIN32 */ |
93 | return errno; |
94 | #endif /* _WIN32 */ |
95 | } /* jerryx_debugger_tcp_get_errno */ |
96 | |
97 | /** |
98 | * Correctly close a single socket. |
99 | */ |
100 | static inline void |
101 | jerryx_debugger_tcp_close_socket (jerryx_socket_t socket_id) /**< socket to close */ |
102 | { |
103 | #ifdef _WIN32 |
104 | closesocket (socket_id); |
105 | #else /* !_WIN32 */ |
106 | close (socket_id); |
107 | #endif /* _WIN32 */ |
108 | } /* jerryx_debugger_tcp_close_socket */ |
109 | |
110 | /** |
111 | * Log tcp error message. |
112 | */ |
113 | static void |
114 | jerryx_debugger_tcp_log_error (int errno_value) /**< error value to log */ |
115 | { |
116 | if (errno_value == 0) |
117 | { |
118 | return; |
119 | } |
120 | |
121 | #ifdef _WIN32 |
122 | char *error_message = NULL; |
123 | FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
124 | NULL, |
125 | (DWORD) errno_value, |
126 | MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), |
127 | (LPTSTR) &error_message, |
128 | 0, |
129 | NULL); |
130 | jerry_port_log (JERRY_LOG_LEVEL_ERROR, "TCP Error: %s\n" , error_message); |
131 | LocalFree (error_message); |
132 | #else /* !_WIN32 */ |
133 | jerry_port_log (JERRY_LOG_LEVEL_ERROR, "TCP Error: %s\n" , strerror (errno_value)); |
134 | #endif /* _WIN32 */ |
135 | } /* jerryx_debugger_tcp_log_error */ |
136 | |
137 | /** |
138 | * Close a tcp connection. |
139 | */ |
140 | static void |
141 | jerryx_debugger_tcp_close (jerry_debugger_transport_header_t *header_p) /**< tcp implementation */ |
142 | { |
143 | JERRYX_ASSERT (!jerry_debugger_transport_is_connected ()); |
144 | |
145 | jerryx_debugger_transport_tcp_t *tcp_p = (jerryx_debugger_transport_tcp_t *) header_p; |
146 | |
147 | JERRYX_DEBUG_MSG ("TCP connection closed.\n" ); |
148 | |
149 | jerryx_debugger_tcp_close_socket (tcp_p->tcp_socket); |
150 | |
151 | jerry_heap_free ((void *) header_p, sizeof (jerryx_debugger_transport_tcp_t)); |
152 | } /* jerryx_debugger_tcp_close */ |
153 | |
154 | /** |
155 | * Send data over a tcp connection. |
156 | * |
157 | * @return true - if the data has been sent successfully |
158 | * false - otherwise |
159 | */ |
160 | static bool |
161 | jerryx_debugger_tcp_send (jerry_debugger_transport_header_t *header_p, /**< tcp implementation */ |
162 | uint8_t *message_p, /**< message to be sent */ |
163 | size_t message_length) /**< message length in bytes */ |
164 | { |
165 | JERRYX_ASSERT (jerry_debugger_transport_is_connected ()); |
166 | |
167 | jerryx_debugger_transport_tcp_t *tcp_p = (jerryx_debugger_transport_tcp_t *) header_p; |
168 | jerryx_socket_size_t remaining_bytes = (jerryx_socket_size_t) message_length; |
169 | |
170 | do |
171 | { |
172 | #ifdef __linux__ |
173 | ssize_t is_err = recv (tcp_p->tcp_socket, NULL, 0, MSG_PEEK); |
174 | |
175 | if (is_err == 0 && errno != JERRYX_EWOULDBLOCK) |
176 | { |
177 | int err_val = errno; |
178 | jerry_debugger_transport_close (); |
179 | jerryx_debugger_tcp_log_error (err_val); |
180 | return false; |
181 | } |
182 | #endif /* __linux__ */ |
183 | |
184 | jerryx_socket_ssize_t sent_bytes = send (tcp_p->tcp_socket, |
185 | (jerryx_socket_void_t *) message_p, |
186 | remaining_bytes, |
187 | 0); |
188 | |
189 | if (sent_bytes < 0) |
190 | { |
191 | int err_val = jerryx_debugger_tcp_get_errno (); |
192 | |
193 | if (err_val == JERRYX_EWOULDBLOCK) |
194 | { |
195 | continue; |
196 | } |
197 | |
198 | jerry_debugger_transport_close (); |
199 | jerryx_debugger_tcp_log_error (err_val); |
200 | return false; |
201 | } |
202 | |
203 | message_p += sent_bytes; |
204 | remaining_bytes -= (jerryx_socket_size_t) sent_bytes; |
205 | } |
206 | while (remaining_bytes > 0); |
207 | |
208 | return true; |
209 | } /* jerryx_debugger_tcp_send */ |
210 | |
211 | /** |
212 | * Receive data from a tcp connection. |
213 | */ |
214 | static bool |
215 | jerryx_debugger_tcp_receive (jerry_debugger_transport_header_t *header_p, /**< tcp implementation */ |
216 | jerry_debugger_transport_receive_context_t *receive_context_p) /**< receive context */ |
217 | { |
218 | jerryx_debugger_transport_tcp_t *tcp_p = (jerryx_debugger_transport_tcp_t *) header_p; |
219 | |
220 | jerryx_socket_void_t *buffer_p = (jerryx_socket_void_t *) (receive_context_p->buffer_p |
221 | + receive_context_p->received_length); |
222 | jerryx_socket_size_t buffer_size = (jerryx_socket_size_t) (JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE |
223 | - receive_context_p->received_length); |
224 | |
225 | jerryx_socket_ssize_t length = recv (tcp_p->tcp_socket, buffer_p, buffer_size, 0); |
226 | |
227 | if (length <= 0) |
228 | { |
229 | int err_val = jerryx_debugger_tcp_get_errno (); |
230 | |
231 | if (err_val != JERRYX_EWOULDBLOCK || length == 0) |
232 | { |
233 | jerry_debugger_transport_close (); |
234 | jerryx_debugger_tcp_log_error (err_val); |
235 | return false; |
236 | } |
237 | length = 0; |
238 | } |
239 | |
240 | receive_context_p->received_length += (size_t) length; |
241 | |
242 | if (receive_context_p->received_length > 0) |
243 | { |
244 | receive_context_p->message_p = receive_context_p->buffer_p; |
245 | receive_context_p->message_length = receive_context_p->received_length; |
246 | } |
247 | |
248 | return true; |
249 | } /* jerryx_debugger_tcp_receive */ |
250 | |
251 | /** |
252 | * Utility method to prepare the server socket to accept connections. |
253 | * |
254 | * The following steps are performed: |
255 | * * Configure address re-use. |
256 | * * Bind the socket to the given port |
257 | * * Start listening on the socket. |
258 | * |
259 | * @return true if everything is ok |
260 | * false if there was an error |
261 | */ |
262 | static bool |
263 | jerryx_debugger_tcp_configure_socket (jerryx_socket_t server_socket, /** < socket to configure */ |
264 | uint16_t port) /** < port number to be used for the socket */ |
265 | { |
266 | struct sockaddr_in addr; |
267 | |
268 | addr.sin_family = AF_INET; |
269 | addr.sin_port = htons (port); |
270 | addr.sin_addr.s_addr = INADDR_ANY; |
271 | |
272 | const int opt_value = 1; |
273 | |
274 | if (setsockopt (server_socket, |
275 | SOL_SOCKET, SO_REUSEADDR, |
276 | (const jerryx_socket_void_t *) &opt_value, |
277 | sizeof (int)) != 0) |
278 | { |
279 | return false; |
280 | } |
281 | |
282 | if (bind (server_socket, (struct sockaddr *) &addr, sizeof (struct sockaddr_in)) != 0) |
283 | { |
284 | return false; |
285 | } |
286 | |
287 | if (listen (server_socket, 1) != 0) |
288 | { |
289 | return false; |
290 | } |
291 | |
292 | return true; |
293 | } /* jerryx_debugger_tcp_configure_socket */ |
294 | |
295 | /** |
296 | * Create a tcp connection. |
297 | * |
298 | * @return true if successful, |
299 | * false otherwise |
300 | */ |
301 | bool |
302 | jerryx_debugger_tcp_create (uint16_t port) /**< listening port */ |
303 | { |
304 | #ifdef _WIN32 |
305 | WSADATA wsaData; |
306 | int wsa_init_status = WSAStartup (MAKEWORD (2, 2), &wsaData); |
307 | if (wsa_init_status != NO_ERROR) |
308 | { |
309 | JERRYX_ERROR_MSG ("WSA Error: %d\n" , wsa_init_status); |
310 | return false; |
311 | } |
312 | #endif /* _WIN32*/ |
313 | |
314 | jerryx_socket_t server_socket = socket (AF_INET, SOCK_STREAM, 0); |
315 | if (server_socket == JERRYX_SOCKET_INVALID) |
316 | { |
317 | jerryx_debugger_tcp_log_error (jerryx_debugger_tcp_get_errno ()); |
318 | return false; |
319 | } |
320 | |
321 | if (!jerryx_debugger_tcp_configure_socket (server_socket, port)) |
322 | { |
323 | int error = jerryx_debugger_tcp_get_errno (); |
324 | jerryx_debugger_tcp_close_socket (server_socket); |
325 | jerryx_debugger_tcp_log_error (error); |
326 | return false; |
327 | } |
328 | |
329 | JERRYX_DEBUG_MSG ("Waiting for client connection\n" ); |
330 | |
331 | struct sockaddr_in addr; |
332 | socklen_t sin_size = sizeof (struct sockaddr_in); |
333 | |
334 | jerryx_socket_t tcp_socket = accept (server_socket, (struct sockaddr *) &addr, &sin_size); |
335 | |
336 | jerryx_debugger_tcp_close_socket (server_socket); |
337 | |
338 | if (tcp_socket == JERRYX_SOCKET_INVALID) |
339 | { |
340 | jerryx_debugger_tcp_log_error (jerryx_debugger_tcp_get_errno ()); |
341 | return false; |
342 | } |
343 | |
344 | /* Set non-blocking mode. */ |
345 | #ifdef _WIN32 |
346 | u_long nonblocking_enabled = 1; |
347 | if (ioctlsocket (tcp_socket, (long) FIONBIO, &nonblocking_enabled) != NO_ERROR) |
348 | { |
349 | jerryx_debugger_tcp_close_socket (tcp_socket); |
350 | return false; |
351 | } |
352 | #else /* !_WIN32 */ |
353 | int socket_flags = fcntl (tcp_socket, F_GETFL, 0); |
354 | |
355 | if (socket_flags < 0) |
356 | { |
357 | close (tcp_socket); |
358 | return false; |
359 | } |
360 | |
361 | if (fcntl (tcp_socket, F_SETFL, socket_flags | O_NONBLOCK) == -1) |
362 | { |
363 | close (tcp_socket); |
364 | return false; |
365 | } |
366 | #endif /* _WIN32 */ |
367 | |
368 | JERRYX_DEBUG_MSG ("Connected from: %s\n" , inet_ntoa (addr.sin_addr)); |
369 | |
370 | size_t size = sizeof (jerryx_debugger_transport_tcp_t); |
371 | |
372 | jerry_debugger_transport_header_t *header_p; |
373 | header_p = (jerry_debugger_transport_header_t *) jerry_heap_alloc (size); |
374 | |
375 | if (!header_p) |
376 | { |
377 | jerryx_debugger_tcp_close_socket (tcp_socket); |
378 | return false; |
379 | } |
380 | |
381 | header_p->close = jerryx_debugger_tcp_close; |
382 | header_p->send = jerryx_debugger_tcp_send; |
383 | header_p->receive = jerryx_debugger_tcp_receive; |
384 | |
385 | ((jerryx_debugger_transport_tcp_t *) header_p)->tcp_socket = tcp_socket; |
386 | |
387 | jerry_debugger_transport_add (header_p, |
388 | 0, |
389 | JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE, |
390 | 0, |
391 | JERRY_DEBUGGER_TRANSPORT_MAX_BUFFER_SIZE); |
392 | |
393 | return true; |
394 | } /* jerryx_debugger_tcp_create */ |
395 | |
396 | #else /* !(defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1)) */ |
397 | |
398 | /** |
399 | * Dummy function when debugger is disabled. |
400 | * |
401 | * @return false |
402 | */ |
403 | bool |
404 | jerryx_debugger_tcp_create (uint16_t port) |
405 | { |
406 | JERRYX_UNUSED (port); |
407 | return false; |
408 | } /* jerryx_debugger_tcp_create */ |
409 | |
410 | #endif /* defined (JERRY_DEBUGGER) && (JERRY_DEBUGGER == 1) */ |
411 | |