File Coverage

blib/lib/Hypersonic/UA/Socket.pm
Criterion Covered Total %
statement 15 53 28.3
branch n/a
condition 0 2 0.0
subroutine 6 19 31.5
pod 0 14 0.0
total 21 88 23.8


line stmt bran cond sub pod time code
1             package Hypersonic::UA::Socket;
2              
3 1     1   1117 use strict;
  1         2  
  1         31  
4 1     1   4 use warnings;
  1         1  
  1         36  
5 1     1   12 use 5.010;
  1         2  
6              
7             our $VERSION = '0.15';
8              
9 1     1   4 use constant MAX_SOCKETS => 65536;
  1         2  
  1         45  
10 1     1   3 use constant RECV_BUF_SIZE => 65536;
  1         1  
  1         1434  
11              
12             sub generate_c_code {
13 0     0 0 0 my ($class, $builder, $opts) = @_;
14              
15 0   0     0 my $max = $opts->{max_sockets} // MAX_SOCKETS;
16              
17 0         0 $class->gen_includes($builder);
18 0         0 $class->gen_socket_registry($builder, $max);
19 0         0 $class->gen_xs_connect_to_host($builder);
20 0         0 $class->gen_xs_connect_nonblocking($builder);
21 0         0 $class->gen_xs_check_connect($builder);
22 0         0 $class->gen_xs_send($builder);
23 0         0 $class->gen_xs_send_nonblocking($builder);
24 0         0 $class->gen_xs_recv($builder);
25 0         0 $class->gen_xs_recv_nonblocking($builder);
26 0         0 $class->gen_xs_recv_chunk($builder);
27 0         0 $class->gen_xs_wait_readable($builder);
28 0         0 $class->gen_xs_close($builder);
29             }
30              
31             sub get_xs_functions {
32             return {
33 1     1 0 2843 'Hypersonic::UA::Socket::connect_to_host' => { source => 'xs_socket_connect_to_host', is_xs_native => 1 },
34             'Hypersonic::UA::Socket::connect_nonblocking' => { source => 'xs_socket_connect_nonblocking', is_xs_native => 1 },
35             'Hypersonic::UA::Socket::check_connect' => { source => 'xs_socket_check_connect', is_xs_native => 1 },
36             'Hypersonic::UA::Socket::send' => { source => 'xs_socket_send', is_xs_native => 1 },
37             'Hypersonic::UA::Socket::send_nonblocking' => { source => 'xs_socket_send_nonblocking', is_xs_native => 1 },
38             'Hypersonic::UA::Socket::recv' => { source => 'xs_socket_recv', is_xs_native => 1 },
39             'Hypersonic::UA::Socket::recv_nonblocking' => { source => 'xs_socket_recv_nonblocking', is_xs_native => 1 },
40             'Hypersonic::UA::Socket::recv_chunk' => { source => 'xs_socket_recv_chunk', is_xs_native => 1 },
41             'Hypersonic::UA::Socket::wait_readable' => { source => 'xs_socket_wait_readable', is_xs_native => 1 },
42             'Hypersonic::UA::Socket::close' => { source => 'xs_socket_close', is_xs_native => 1 },
43             };
44             }
45              
46             sub gen_includes {
47 0     0 0   my ($class, $builder) = @_;
48              
49 0           $builder->line('#include ')
50             ->line('#include ')
51             ->line('#include ')
52             ->line('#include ')
53             ->line('#include ')
54             ->line('#include ')
55             ->line('#include ')
56             ->line('#include ')
57             ->line('#include ')
58             ->line('#include ')
59             ->line('#include ')
60             ->line('#include ')
61             ->line('#include ')
62             ->line('#include ')
63             ->blank;
64             }
65              
66             sub gen_socket_registry {
67 0     0 0   my ($class, $builder, $max) = @_;
68              
69 0           $builder->line("#define SOCKET_MAX $max")
70             ->line("#define SOCKET_RECV_BUF_SIZE " . RECV_BUF_SIZE)
71             ->blank
72             ->line('static char g_socket_recv_buf[SOCKET_RECV_BUF_SIZE];')
73             ->blank;
74             }
75              
76             sub gen_xs_connect_to_host {
77 0     0 0   my ($class, $builder) = @_;
78              
79 0           $builder->comment('Connect to host with DNS resolution and timeout')
80             ->xs_function('xs_socket_connect_to_host')
81             ->xs_preamble
82             ->line('STRLEN host_len;')
83             ->line('const char* host;')
84             ->line('int port;')
85             ->line('int timeout_ms;')
86             ->line('struct addrinfo hints, *res, *rp;')
87             ->line('char port_str[8];')
88             ->line('int fd = -1;')
89             ->blank
90             ->line('if (items != 3) croak("Usage: connect_to_host(host, port, timeout_ms)");')
91             ->blank
92             ->line('host = SvPV(ST(0), host_len);')
93             ->line('port = (int)SvIV(ST(1));')
94             ->line('timeout_ms = (int)SvIV(ST(2));')
95             ->blank
96             ->comment('DNS lookup')
97             ->line('memset(&hints, 0, sizeof(hints));')
98             ->line('hints.ai_family = AF_UNSPEC;')
99             ->line('hints.ai_socktype = SOCK_STREAM;')
100             ->blank
101             ->line('snprintf(port_str, sizeof(port_str), "%d", port);')
102             ->blank
103             ->if('getaddrinfo(host, port_str, &hints, &res) != 0')
104             ->line('ST(0) = sv_2mortal(newSViv(-1));')
105             ->line('XSRETURN(1);')
106             ->endif
107             ->blank
108             ->comment('Try each address')
109             ->line('for (rp = res; rp != NULL; rp = rp->ai_next) {')
110             ->line(' fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);')
111             ->line(' if (fd < 0) continue;')
112             ->blank
113             ->comment(' Set non-blocking for connect timeout')
114             ->line(' int flags = fcntl(fd, F_GETFL, 0);')
115             ->line(' fcntl(fd, F_SETFL, flags | O_NONBLOCK);')
116             ->blank
117             ->comment(' Disable Nagle')
118             ->line(' int opt = 1;')
119             ->line(' setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));')
120             ->blank
121             ->comment(' Non-blocking connect')
122             ->line(' int ret = connect(fd, rp->ai_addr, rp->ai_addrlen);')
123             ->line(' if (ret == 0) break;')
124             ->line(' if (errno == EINPROGRESS) {')
125             ->line(' fd_set wfds;')
126             ->line(' FD_ZERO(&wfds);')
127             ->line(' FD_SET(fd, &wfds);')
128             ->line(' struct timeval tv;')
129             ->line(' tv.tv_sec = timeout_ms / 1000;')
130             ->line(' tv.tv_usec = (timeout_ms % 1000) * 1000;')
131             ->line(' if (select(fd + 1, NULL, &wfds, NULL, &tv) > 0) {')
132             ->line(' int error;')
133             ->line(' socklen_t len = sizeof(error);')
134             ->line(' getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len);')
135             ->line(' if (error == 0) break;')
136             ->line(' }')
137             ->line(' }')
138             ->line(' close(fd);')
139             ->line(' fd = -1;')
140             ->line('}')
141             ->blank
142             ->line('freeaddrinfo(res);')
143             ->blank
144             ->comment('Set back to blocking')
145             ->if('fd >= 0')
146             ->line('int flags = fcntl(fd, F_GETFL, 0);')
147             ->line('fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);')
148             ->endif
149             ->blank
150             ->line('ST(0) = sv_2mortal(newSViv(fd));')
151             ->xs_return('1')
152             ->xs_end
153             ->blank;
154             }
155              
156             sub gen_xs_connect_nonblocking {
157 0     0 0   my ($class, $builder) = @_;
158              
159 0           $builder->comment('Non-blocking connect - returns fd immediately')
160             ->xs_function('xs_socket_connect_nonblocking')
161             ->xs_preamble
162             ->line('STRLEN host_len;')
163             ->line('const char* host;')
164             ->line('int port;')
165             ->line('struct addrinfo hints, *res;')
166             ->line('char port_str[8];')
167             ->line('int fd;')
168             ->line('int flags;')
169             ->line('int opt;')
170             ->line('int ret;')
171             ->blank
172             ->line('if (items != 2) croak("Usage: connect_nonblocking(host, port)");')
173             ->blank
174             ->line('host = SvPV(ST(0), host_len);')
175             ->line('port = (int)SvIV(ST(1));')
176             ->blank
177             ->comment('DNS lookup')
178             ->line('memset(&hints, 0, sizeof(hints));')
179             ->line('hints.ai_family = AF_UNSPEC;')
180             ->line('hints.ai_socktype = SOCK_STREAM;')
181             ->blank
182             ->line('snprintf(port_str, sizeof(port_str), "%d", port);')
183             ->blank
184             ->if('getaddrinfo(host, port_str, &hints, &res) != 0')
185             ->line('ST(0) = sv_2mortal(newSViv(-1));')
186             ->line('XSRETURN(1);')
187             ->endif
188             ->blank
189             ->line('fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);')
190             ->if('fd < 0')
191             ->line('freeaddrinfo(res);')
192             ->line('ST(0) = sv_2mortal(newSViv(-1));')
193             ->line('XSRETURN(1);')
194             ->endif
195             ->blank
196             ->comment('Set non-blocking')
197             ->line('flags = fcntl(fd, F_GETFL, 0);')
198             ->line('fcntl(fd, F_SETFL, flags | O_NONBLOCK);')
199             ->blank
200             ->comment('Disable Nagle')
201             ->line('opt = 1;')
202             ->line('setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));')
203             ->blank
204             ->comment('Start non-blocking connect')
205             ->line('ret = connect(fd, res->ai_addr, res->ai_addrlen);')
206             ->line('freeaddrinfo(res);')
207             ->blank
208             ->if('ret == 0 || errno == EINPROGRESS')
209             ->line('ST(0) = sv_2mortal(newSViv(fd));')
210             ->else
211             ->line('close(fd);')
212             ->line('ST(0) = sv_2mortal(newSViv(-1));')
213             ->endif
214             ->xs_return('1')
215             ->xs_end
216             ->blank;
217             }
218              
219             sub gen_xs_check_connect {
220 0     0 0   my ($class, $builder) = @_;
221              
222 0           $builder->comment('Check if non-blocking connect completed: 1=done, 0=pending, <0=error')
223             ->xs_function('xs_socket_check_connect')
224             ->xs_preamble
225             ->line('if (items != 1) croak("Usage: check_connect(fd)");')
226             ->line('int fd = (int)SvIV(ST(0));')
227             ->blank
228             ->line('fd_set wfds;')
229             ->line('FD_ZERO(&wfds);')
230             ->line('FD_SET(fd, &wfds);')
231             ->blank
232             ->line('struct timeval tv = {0, 0};')
233             ->blank
234             ->line('int ret = select(fd + 1, NULL, &wfds, NULL, &tv);')
235             ->if('ret == 0')
236             ->line('ST(0) = sv_2mortal(newSViv(0));')
237             ->line('XSRETURN(1);')
238             ->endif
239             ->blank
240             ->if('ret < 0')
241             ->line('ST(0) = sv_2mortal(newSViv(-1));')
242             ->line('XSRETURN(1);')
243             ->endif
244             ->blank
245             ->line('int error;')
246             ->line('socklen_t len = sizeof(error);')
247             ->line('getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len);')
248             ->blank
249             ->if('error != 0')
250             ->line('ST(0) = sv_2mortal(newSViv(-error));')
251             ->else
252             ->line('ST(0) = sv_2mortal(newSViv(1));')
253             ->endif
254             ->xs_return('1')
255             ->xs_end
256             ->blank;
257             }
258              
259             sub gen_xs_send {
260 0     0 0   my ($class, $builder) = @_;
261              
262 0           $builder->comment('Send data (blocking)')
263             ->xs_function('xs_socket_send')
264             ->xs_preamble
265             ->line('if (items != 2) croak("Usage: send(fd, data)");')
266             ->line('int fd = (int)SvIV(ST(0));')
267             ->line('STRLEN data_len;')
268             ->line('const char* data = SvPV(ST(1), data_len);')
269             ->blank
270             ->line('ssize_t total = 0;')
271             ->line('while (total < (ssize_t)data_len) {')
272             ->line(' ssize_t sent = send(fd, data + total, data_len - total, 0);')
273             ->line(' if (sent < 0) {')
274             ->line(' if (errno == EINTR) continue;')
275             ->line(' break;')
276             ->line(' }')
277             ->line(' total += sent;')
278             ->line('}')
279             ->blank
280             ->line('ST(0) = sv_2mortal(newSViv((IV)total));')
281             ->xs_return('1')
282             ->xs_end
283             ->blank;
284             }
285              
286             sub gen_xs_send_nonblocking {
287 0     0 0   my ($class, $builder) = @_;
288              
289 0           $builder->comment('Non-blocking send: returns bytes sent, -2=EAGAIN, -1=error')
290             ->xs_function('xs_socket_send_nonblocking')
291             ->xs_preamble
292             ->line('if (items != 2) croak("Usage: send_nonblocking(fd, data)");')
293             ->line('int fd = (int)SvIV(ST(0));')
294             ->line('STRLEN data_len;')
295             ->line('const char* data = SvPV(ST(1), data_len);')
296             ->blank
297             ->line('ssize_t sent = send(fd, data, data_len, 0);')
298             ->blank
299             ->if('sent < 0')
300             ->if('errno == EAGAIN || errno == EWOULDBLOCK')
301             ->line('ST(0) = sv_2mortal(newSViv(-2));')
302             ->else
303             ->line('ST(0) = sv_2mortal(newSViv(-1));')
304             ->endif
305             ->else
306             ->line('ST(0) = sv_2mortal(newSViv((IV)sent));')
307             ->endif
308             ->xs_return('1')
309             ->xs_end
310             ->blank;
311             }
312              
313             sub gen_xs_recv {
314 0     0 0   my ($class, $builder) = @_;
315              
316 0           $builder->comment('Receive HTTP response with timeout')
317             ->xs_function('xs_socket_recv')
318             ->xs_preamble
319             ->line('if (items < 1 || items > 2) croak("Usage: recv(fd, [timeout_ms])");')
320             ->line('int fd = (int)SvIV(ST(0));')
321             ->line('int timeout_ms = (items > 1) ? (int)SvIV(ST(1)) : 30000;')
322             ->blank
323             ->comment('Set receive timeout')
324             ->line('struct timeval tv;')
325             ->line('tv.tv_sec = timeout_ms / 1000;')
326             ->line('tv.tv_usec = (timeout_ms % 1000) * 1000;')
327             ->line('setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));')
328             ->blank
329             ->line('size_t total = 0;')
330             ->line('int headers_complete = 0;')
331             ->line('int content_length = -1;')
332             ->line('const char* body_start = NULL;')
333             ->blank
334             ->comment('Read until headers complete')
335             ->line('while (!headers_complete && total < SOCKET_RECV_BUF_SIZE - 1) {')
336             ->line(' ssize_t n = recv(fd, g_socket_recv_buf + total, SOCKET_RECV_BUF_SIZE - 1 - total, 0);')
337             ->line(' if (n <= 0) break;')
338             ->line(' total += n;')
339             ->line(' g_socket_recv_buf[total] = \'\\0\';')
340             ->blank
341             ->line(' const char* hdr_end = strstr(g_socket_recv_buf, "\\r\\n\\r\\n");')
342             ->line(' if (hdr_end) {')
343             ->line(' headers_complete = 1;')
344             ->line(' body_start = hdr_end + 4;')
345             ->blank
346             ->line(' const char* cl = strcasestr(g_socket_recv_buf, "\\r\\nContent-Length:");')
347             ->line(' if (cl) content_length = atoi(cl + 17);')
348             ->line(' }')
349             ->line('}')
350             ->blank
351             ->if('!headers_complete')
352             ->line('ST(0) = &PL_sv_undef;')
353             ->line('XSRETURN(1);')
354             ->endif
355             ->blank
356             ->comment('Read body if Content-Length known')
357             ->line('size_t body_received = total - (body_start - g_socket_recv_buf);')
358             ->if('content_length > 0')
359             ->line('while (body_received < (size_t)content_length && total < SOCKET_RECV_BUF_SIZE - 1) {')
360             ->line(' ssize_t n = recv(fd, g_socket_recv_buf + total, SOCKET_RECV_BUF_SIZE - 1 - total, 0);')
361             ->line(' if (n <= 0) break;')
362             ->line(' total += n;')
363             ->line(' body_received += n;')
364             ->line('}')
365             ->endif
366             ->blank
367             ->line('g_socket_recv_buf[total] = \'\\0\';')
368             ->line('ST(0) = sv_2mortal(newSVpvn(g_socket_recv_buf, total));')
369             ->xs_return('1')
370             ->xs_end
371             ->blank;
372             }
373              
374             sub gen_xs_recv_nonblocking {
375 0     0 0   my ($class, $builder) = @_;
376              
377 0           $builder->comment('Non-blocking recv: returns data, empty string=closed, undef=EAGAIN')
378             ->xs_function('xs_socket_recv_nonblocking')
379             ->xs_preamble
380             ->line('if (items != 1) croak("Usage: recv_nonblocking(fd)");')
381             ->line('int fd = (int)SvIV(ST(0));')
382             ->blank
383             ->line('ssize_t n = recv(fd, g_socket_recv_buf, SOCKET_RECV_BUF_SIZE - 1, 0);')
384             ->blank
385             ->if('n < 0')
386             ->if('errno == EAGAIN || errno == EWOULDBLOCK')
387             ->line('ST(0) = &PL_sv_undef;')
388             ->else
389             ->line('ST(0) = sv_2mortal(newSVpvn("", 0));')
390             ->endif
391             ->elsif('n == 0')
392             ->line('ST(0) = sv_2mortal(newSVpvn("", 0));')
393             ->else
394             ->line('ST(0) = sv_2mortal(newSVpvn(g_socket_recv_buf, n));')
395             ->endif
396             ->xs_return('1')
397             ->xs_end
398             ->blank;
399             }
400              
401             sub gen_xs_recv_chunk {
402 0     0 0   my ($class, $builder) = @_;
403              
404 0           $builder->comment('Receive a chunk of data (non-blocking)')
405             ->xs_function('xs_socket_recv_chunk')
406             ->xs_preamble
407             ->line('if (items != 1) croak("Usage: recv_chunk(fd)");')
408             ->line('int fd = (int)SvIV(ST(0));')
409             ->blank
410             ->line('ssize_t n = recv(fd, g_socket_recv_buf, SOCKET_RECV_BUF_SIZE, MSG_DONTWAIT);')
411             ->blank
412             ->if('n < 0')
413             ->if('errno == EAGAIN || errno == EWOULDBLOCK')
414             ->line('ST(0) = sv_2mortal(newSVpvn("", 0));')
415             ->else
416             ->line('ST(0) = &PL_sv_undef;')
417             ->endif
418             ->line('XSRETURN(1);')
419             ->endif
420             ->blank
421             ->if('n == 0')
422             ->line('ST(0) = &PL_sv_undef;')
423             ->line('XSRETURN(1);')
424             ->endif
425             ->blank
426             ->line('ST(0) = sv_2mortal(newSVpvn(g_socket_recv_buf, n));')
427             ->xs_return('1')
428             ->xs_end
429             ->blank;
430             }
431              
432             sub gen_xs_wait_readable {
433 0     0 0   my ($class, $builder) = @_;
434              
435 0           $builder->comment('Wait for socket to be readable')
436             ->xs_function('xs_socket_wait_readable')
437             ->xs_preamble
438             ->line('if (items != 2) croak("Usage: wait_readable(fd, timeout_ms)");')
439             ->line('int fd = (int)SvIV(ST(0));')
440             ->line('int timeout_ms = (int)SvIV(ST(1));')
441             ->blank
442             ->line('fd_set rfds;')
443             ->line('FD_ZERO(&rfds);')
444             ->line('FD_SET(fd, &rfds);')
445             ->blank
446             ->line('struct timeval tv;')
447             ->line('tv.tv_sec = timeout_ms / 1000;')
448             ->line('tv.tv_usec = (timeout_ms % 1000) * 1000;')
449             ->blank
450             ->line('int ret = select(fd + 1, &rfds, NULL, NULL, &tv);')
451             ->line('ST(0) = sv_2mortal(newSViv(ret));')
452             ->xs_return('1')
453             ->xs_end
454             ->blank;
455             }
456              
457             sub gen_xs_close {
458 0     0 0   my ($class, $builder) = @_;
459              
460 0           $builder->comment('Close file descriptor')
461             ->xs_function('xs_socket_close')
462             ->xs_preamble
463             ->line('if (items != 1) croak("Usage: close(fd)");')
464             ->line('int fd = (int)SvIV(ST(0));')
465             ->line('int ret = close(fd);')
466             ->line('ST(0) = sv_2mortal(newSViv(ret));')
467             ->xs_return('1')
468             ->xs_end
469             ->blank;
470             }
471              
472             1;