File Coverage

blib/lib/Hypersonic/UA/TLS.pm
Criterion Covered Total %
statement 51 59 86.4
branch 1 4 25.0
condition 1 12 8.3
subroutine 16 18 88.8
pod 0 14 0.0
total 69 107 64.4


line stmt bran cond sub pod time code
1             package Hypersonic::UA::TLS;
2              
3 1     1   236272 use strict;
  1         1  
  1         34  
4 1     1   4 use warnings;
  1         1  
  1         52  
5 1     1   14 use 5.010;
  1         2  
6              
7             our $VERSION = '0.15';
8              
9 1     1   5 use constant MAX_TLS_CONNS => 10000;
  1         2  
  1         1436  
10              
11             # Cache for OpenSSL detection result
12             my $OPENSSL_DETECTION;
13              
14             # Check if OpenSSL is available (uses centralized detection)
15             sub check_openssl {
16 1 50   1 0 1428 return $OPENSSL_DETECTION->{available} if defined $OPENSSL_DETECTION;
17              
18 1         471 require Hypersonic::JIT::Util;
19 1         6 $OPENSSL_DETECTION = Hypersonic::JIT::Util->detect_openssl();
20 1         19 return $OPENSSL_DETECTION->{available};
21             }
22              
23             # Get extra compiler flags for OpenSSL (uses centralized detection)
24             sub get_extra_cflags {
25 0     0 0 0 require Hypersonic::JIT::Util;
26 0   0     0 $OPENSSL_DETECTION //= Hypersonic::JIT::Util->detect_openssl();
27 0   0     0 return $OPENSSL_DETECTION->{cflags} // '';
28             }
29              
30             sub get_extra_ldflags {
31 0     0 0 0 require Hypersonic::JIT::Util;
32 0   0     0 $OPENSSL_DETECTION //= Hypersonic::JIT::Util->detect_openssl();
33             # Need both -lssl and -lcrypto for full OpenSSL
34 0   0     0 my $ldflags = $OPENSSL_DETECTION->{ldflags} // '';
35 0 0       0 $ldflags .= ' -lcrypto' unless $ldflags =~ /-lcrypto/;
36 0         0 return $ldflags;
37             }
38              
39             sub generate_c_code {
40 1     1 0 14526 my ($class, $builder, $opts) = @_;
41              
42 1   50     7 my $max_conns = $opts->{max_tls_conns} // MAX_TLS_CONNS;
43              
44 1         5 $class->gen_tls_registry($builder, $max_conns);
45 1         6 $class->gen_xs_init_context($builder);
46 1         11 $class->gen_xs_connect($builder);
47 1         6 $class->gen_xs_handshake($builder);
48 1         5 $class->gen_xs_send($builder);
49 1         5 $class->gen_xs_recv($builder);
50 1         3 $class->gen_xs_recv_chunk($builder);
51 1         3 $class->gen_xs_close($builder);
52 1         2 $class->gen_xs_get_ssl($builder);
53             }
54              
55             sub get_xs_functions {
56             return {
57 1     1 0 5045 'Hypersonic::UA::TLS::init_context' => { source => 'xs_uatls_init_context', is_xs_native => 1 },
58             'Hypersonic::UA::TLS::tls_connect' => { source => 'xs_uatls_connect', is_xs_native => 1 },
59             'Hypersonic::UA::TLS::tls_handshake' => { source => 'xs_uatls_handshake', is_xs_native => 1 },
60             'Hypersonic::UA::TLS::tls_send' => { source => 'xs_uatls_send', is_xs_native => 1 },
61             'Hypersonic::UA::TLS::tls_recv' => { source => 'xs_uatls_recv', is_xs_native => 1 },
62             'Hypersonic::UA::TLS::tls_recv_chunk' => { source => 'xs_uatls_recv_chunk', is_xs_native => 1 },
63             'Hypersonic::UA::TLS::tls_close' => { source => 'xs_uatls_close', is_xs_native => 1 },
64             'Hypersonic::UA::TLS::get_ssl' => { source => 'xs_uatls_get_ssl', is_xs_native => 1 },
65             };
66             }
67              
68             sub gen_tls_registry {
69 1     1 0 2 my ($class, $builder, $max_conns) = @_;
70              
71 1         12 $builder->line('#include ')
72             ->line('#include ')
73             ->line('#include ')
74             ->blank;
75              
76 1         5 $builder->line("#define UA_MAX_TLS_CONNS $max_conns")
77             ->blank;
78              
79 1         12 $builder->line('typedef struct {')
80             ->line(' int fd;')
81             ->line(' SSL* ssl;')
82             ->line(' int handshake_done;')
83             ->line(' int verify;')
84             ->line('} UATLSClientConn;')
85             ->blank
86             ->line('static SSL_CTX* g_ua_client_ssl_ctx = NULL;')
87             ->line("static UATLSClientConn ua_tls_registry[UA_MAX_TLS_CONNS];")
88             ->blank;
89              
90 1         8 $builder->line('static UATLSClientConn* ua_tls_find(int fd) {')
91             ->line(' int i;')
92             ->line(' for (i = 0; i < UA_MAX_TLS_CONNS; i++) {')
93             ->line(' if (ua_tls_registry[i].fd == fd) {')
94             ->line(' return &ua_tls_registry[i];')
95             ->line(' }')
96             ->line(' }')
97             ->line(' return NULL;')
98             ->line('}')
99             ->blank;
100              
101 1         14 $builder->line('static UATLSClientConn* ua_tls_alloc(int fd) {')
102             ->line(' int i;')
103             ->line(' for (i = 0; i < UA_MAX_TLS_CONNS; i++) {')
104             ->line(' if (ua_tls_registry[i].fd == 0) {')
105             ->line(' UATLSClientConn* c = &ua_tls_registry[i];')
106             ->line(' memset(c, 0, sizeof(UATLSClientConn));')
107             ->line(' c->fd = fd;')
108             ->line(' return c;')
109             ->line(' }')
110             ->line(' }')
111             ->line(' return NULL;')
112             ->line('}')
113             ->blank;
114              
115 1         9 $builder->line('static void ua_tls_free(UATLSClientConn* c) {')
116             ->line(' if (c->ssl) {')
117             ->line(' SSL_shutdown(c->ssl);')
118             ->line(' SSL_free(c->ssl);')
119             ->line(' }')
120             ->line(' c->fd = 0;')
121             ->line(' c->ssl = NULL;')
122             ->line(' c->handshake_done = 0;')
123             ->line('}')
124             ->blank;
125              
126 1         5 $builder->line('static void ua_tls_registry_init(void) {')
127             ->line(' memset(ua_tls_registry, 0, sizeof(ua_tls_registry));')
128             ->line('}')
129             ->blank;
130             }
131              
132             sub gen_xs_init_context {
133 1     1 0 2 my ($class, $builder) = @_;
134              
135 1         64 $builder->comment('Initialize client TLS context')
136             ->xs_function('xs_uatls_init_context')
137             ->xs_preamble
138             ->line('int verify;')
139             ->line('const char* ca_file;')
140             ->blank
141             ->line('if (items > 2) croak("Usage: init_context([verify], [ca_file])");')
142             ->blank
143             ->line('verify = (items > 0) ? (int)SvIV(ST(0)) : 1;')
144             ->line('ca_file = (items > 1 && SvOK(ST(1))) ? SvPV_nolen(ST(1)) : NULL;')
145             ->blank
146             ->line('SSL_library_init();')
147             ->line('SSL_load_error_strings();')
148             ->line('OpenSSL_add_all_algorithms();')
149             ->blank
150             ->line('g_ua_client_ssl_ctx = SSL_CTX_new(TLS_client_method());')
151             ->if('!g_ua_client_ssl_ctx')
152             ->line('ST(0) = sv_2mortal(newSViv(-1));')
153             ->line('XSRETURN(1);')
154             ->endif
155             ->blank
156             ->line('SSL_CTX_set_min_proto_version(g_ua_client_ssl_ctx, TLS1_2_VERSION);')
157             ->blank
158             ->if('verify')
159             ->line('SSL_CTX_set_verify(g_ua_client_ssl_ctx, SSL_VERIFY_PEER, NULL);')
160             ->if('ca_file')
161             ->line('SSL_CTX_load_verify_locations(g_ua_client_ssl_ctx, ca_file, NULL);')
162             ->else
163             ->line('SSL_CTX_set_default_verify_paths(g_ua_client_ssl_ctx);')
164             ->endif
165             ->else
166             ->line('SSL_CTX_set_verify(g_ua_client_ssl_ctx, SSL_VERIFY_NONE, NULL);')
167             ->endif
168             ->blank
169             ->line('ua_tls_registry_init();')
170             ->blank
171             ->line('ST(0) = sv_2mortal(newSViv(0));')
172             ->xs_return('1')
173             ->xs_end
174             ->blank;
175             }
176              
177             sub gen_xs_connect {
178 1     1 0 2 my ($class, $builder) = @_;
179              
180 1         82 $builder->comment('TLS connect with SNI and verification')
181             ->xs_function('xs_uatls_connect')
182             ->xs_preamble
183             ->line('int fd;')
184             ->line('STRLEN host_len;')
185             ->line('const char* hostname;')
186             ->line('int verify;')
187             ->line('SSL* ssl;')
188             ->line('int ret;')
189             ->line('int err;')
190             ->line('UATLSClientConn* c;')
191             ->blank
192             ->line('if (items < 2 || items > 3) croak("Usage: tls_connect(fd, hostname, [verify])");')
193             ->blank
194             ->line('fd = (int)SvIV(ST(0));')
195             ->line('hostname = SvPV(ST(1), host_len);')
196             ->line('verify = (items > 2) ? (int)SvIV(ST(2)) : 1;')
197             ->blank
198             ->if('!g_ua_client_ssl_ctx')
199             ->line('SSL_library_init();')
200             ->line('SSL_load_error_strings();')
201             ->line('g_ua_client_ssl_ctx = SSL_CTX_new(TLS_client_method());')
202             ->line('SSL_CTX_set_min_proto_version(g_ua_client_ssl_ctx, TLS1_2_VERSION);')
203             ->if('verify')
204             ->line('SSL_CTX_set_verify(g_ua_client_ssl_ctx, SSL_VERIFY_PEER, NULL);')
205             ->line('SSL_CTX_set_default_verify_paths(g_ua_client_ssl_ctx);')
206             ->endif
207             ->line('ua_tls_registry_init();')
208             ->endif
209             ->blank
210             ->line('ssl = SSL_new(g_ua_client_ssl_ctx);')
211             ->if('!ssl')
212             ->line('ST(0) = sv_2mortal(newSViv(-1));')
213             ->line('XSRETURN(1);')
214             ->endif
215             ->blank
216             ->line('SSL_set_fd(ssl, fd);')
217             ->line('SSL_set_connect_state(ssl);')
218             ->blank
219             ->comment('Set SNI hostname')
220             ->line('SSL_set_tlsext_host_name(ssl, hostname);')
221             ->blank
222             ->comment('Enable hostname verification')
223             ->if('verify')
224             ->line('SSL_set1_host(ssl, hostname);')
225             ->endif
226             ->blank
227             ->line('ret = SSL_connect(ssl);')
228             ->if('ret != 1')
229             ->line('err = SSL_get_error(ssl, ret);')
230             ->line('SSL_free(ssl);')
231             ->line('ST(0) = sv_2mortal(newSViv(-err));')
232             ->line('XSRETURN(1);')
233             ->endif
234             ->blank
235             ->line('c = ua_tls_alloc(fd);')
236             ->if('!c')
237             ->line('SSL_free(ssl);')
238             ->line('ST(0) = sv_2mortal(newSViv(-999));')
239             ->line('XSRETURN(1);')
240             ->endif
241             ->blank
242             ->line('c->ssl = ssl;')
243             ->line('c->handshake_done = 1;')
244             ->line('c->verify = verify;')
245             ->blank
246             ->line('ST(0) = sv_2mortal(newSViv(0));')
247             ->xs_return('1')
248             ->xs_end
249             ->blank;
250             }
251              
252             sub gen_xs_handshake {
253 1     1 0 2 my ($class, $builder) = @_;
254              
255 1         33 $builder->comment('Continue non-blocking handshake')
256             ->xs_function('xs_uatls_handshake')
257             ->xs_preamble
258             ->line('if (items != 1) croak("Usage: tls_handshake(fd)");')
259             ->line('int fd = (int)SvIV(ST(0));')
260             ->blank
261             ->line('UATLSClientConn* c = ua_tls_find(fd);')
262             ->if('!c || !c->ssl')
263             ->line('ST(0) = sv_2mortal(newSViv(-1));')
264             ->line('XSRETURN(1);')
265             ->endif
266             ->blank
267             ->line('int ret = SSL_connect(c->ssl);')
268             ->if('ret == 1')
269             ->line('c->handshake_done = 1;')
270             ->line('ST(0) = sv_2mortal(newSViv(1));')
271             ->else
272             ->line('int err = SSL_get_error(c->ssl, ret);')
273             ->if('err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE')
274             ->line('ST(0) = sv_2mortal(newSViv(0));')
275             ->else
276             ->line('ST(0) = sv_2mortal(newSViv(-err));')
277             ->endif
278             ->endif
279             ->xs_return('1')
280             ->xs_end
281             ->blank;
282             }
283              
284             sub gen_xs_send {
285 1     1 0 2 my ($class, $builder) = @_;
286              
287 1         18 $builder->comment('TLS send')
288             ->xs_function('xs_uatls_send')
289             ->xs_preamble
290             ->line('if (items != 2) croak("Usage: tls_send(fd, data)");')
291             ->line('int fd = (int)SvIV(ST(0));')
292             ->line('STRLEN data_len;')
293             ->line('const char* data = SvPV(ST(1), data_len);')
294             ->blank
295             ->line('UATLSClientConn* c = ua_tls_find(fd);')
296             ->if('!c || !c->ssl')
297             ->line('ST(0) = sv_2mortal(newSViv(-1));')
298             ->line('XSRETURN(1);')
299             ->endif
300             ->blank
301             ->line('int written = SSL_write(c->ssl, data, data_len);')
302             ->line('ST(0) = sv_2mortal(newSViv(written));')
303             ->xs_return('1')
304             ->xs_end
305             ->blank;
306             }
307              
308             sub gen_xs_recv {
309 1     1 0 2 my ($class, $builder) = @_;
310              
311 1         50 $builder->comment('TLS receive with timeout')
312             ->xs_function('xs_uatls_recv')
313             ->xs_preamble
314             ->line('if (items < 1 || items > 2) croak("Usage: tls_recv(fd, [timeout_ms])");')
315             ->line('int fd = (int)SvIV(ST(0));')
316             ->line('int timeout_ms = (items > 1) ? (int)SvIV(ST(1)) : 30000;')
317             ->blank
318             ->line('UATLSClientConn* c = ua_tls_find(fd);')
319             ->if('!c || !c->ssl')
320             ->line('ST(0) = &PL_sv_undef;')
321             ->line('XSRETURN(1);')
322             ->endif
323             ->blank
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('static char recv_buf[65536];')
330             ->line('size_t total = 0;')
331             ->blank
332             ->line('while (total < sizeof(recv_buf) - 1) {')
333             ->line(' int n = SSL_read(c->ssl, recv_buf + total, sizeof(recv_buf) - 1 - total);')
334             ->line(' if (n <= 0) {')
335             ->line(' int err = SSL_get_error(c->ssl, n);')
336             ->line(' if (err == SSL_ERROR_ZERO_RETURN) break;')
337             ->line(' if (err == SSL_ERROR_WANT_READ) continue;')
338             ->line(' break;')
339             ->line(' }')
340             ->line(' total += n;')
341             ->blank
342             ->line(' recv_buf[total] = \'\\0\';')
343             ->line(' char* headers_end = strstr(recv_buf, "\\r\\n\\r\\n");')
344             ->line(' if (headers_end) {')
345             ->line(' char* cl = strcasestr(recv_buf, "Content-Length:");')
346             ->line(' if (cl) {')
347             ->line(' int content_len = atoi(cl + 15);')
348             ->line(' char* body = headers_end + 4;')
349             ->line(' if ((size_t)(total - (body - recv_buf)) >= (size_t)content_len) break;')
350             ->line(' } else if (strcasestr(recv_buf, "Transfer-Encoding: chunked")) {')
351             ->line(' if (strstr(recv_buf, "\\r\\n0\\r\\n")) break;')
352             ->line(' } else {')
353             ->line(' break;')
354             ->line(' }')
355             ->line(' }')
356             ->line('}')
357             ->blank
358             ->if('total > 0')
359             ->line('ST(0) = sv_2mortal(newSVpvn(recv_buf, total));')
360             ->else
361             ->line('ST(0) = &PL_sv_undef;')
362             ->endif
363             ->xs_return('1')
364             ->xs_end
365             ->blank;
366             }
367              
368             sub gen_xs_recv_chunk {
369 1     1 0 2 my ($class, $builder) = @_;
370              
371 1         30 $builder->comment('TLS receive chunk (non-blocking)')
372             ->xs_function('xs_uatls_recv_chunk')
373             ->xs_preamble
374             ->line('if (items != 1) croak("Usage: tls_recv_chunk(fd)");')
375             ->line('int fd = (int)SvIV(ST(0));')
376             ->blank
377             ->line('UATLSClientConn* c = ua_tls_find(fd);')
378             ->if('!c || !c->ssl')
379             ->line('ST(0) = &PL_sv_undef;')
380             ->line('XSRETURN(1);')
381             ->endif
382             ->blank
383             ->line('static char chunk_buf[65536];')
384             ->line('int n = SSL_read(c->ssl, chunk_buf, sizeof(chunk_buf));')
385             ->blank
386             ->if('n > 0')
387             ->line('ST(0) = sv_2mortal(newSVpvn(chunk_buf, n));')
388             ->elsif('n == 0')
389             ->line('ST(0) = &PL_sv_undef;')
390             ->else
391             ->line('int err = SSL_get_error(c->ssl, n);')
392             ->if('err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE')
393             ->line('ST(0) = sv_2mortal(newSVpvn("", 0));')
394             ->else
395             ->line('ST(0) = &PL_sv_undef;')
396             ->endif
397             ->endif
398             ->xs_return('1')
399             ->xs_end
400             ->blank;
401             }
402              
403             sub gen_xs_close {
404 1     1 0 2 my ($class, $builder) = @_;
405              
406 1         20 $builder->comment('TLS close')
407             ->xs_function('xs_uatls_close')
408             ->xs_preamble
409             ->line('if (items != 1) croak("Usage: tls_close(fd)");')
410             ->line('int fd = (int)SvIV(ST(0));')
411             ->blank
412             ->line('UATLSClientConn* c = ua_tls_find(fd);')
413             ->if('c')
414             ->line('ua_tls_free(c);')
415             ->endif
416             ->blank
417             ->line('close(fd);')
418             ->line('ST(0) = sv_2mortal(newSViv(0));')
419             ->xs_return('1')
420             ->xs_end
421             ->blank;
422             }
423              
424             sub gen_xs_get_ssl {
425 1     1 0 2 my ($class, $builder) = @_;
426              
427 1         48 $builder->comment('Get SSL handle')
428             ->xs_function('xs_uatls_get_ssl')
429             ->xs_preamble
430             ->line('if (items != 1) croak("Usage: get_ssl(fd)");')
431             ->line('int fd = (int)SvIV(ST(0));')
432             ->blank
433             ->line('UATLSClientConn* c = ua_tls_find(fd);')
434             ->if('c && c->ssl')
435             ->line('ST(0) = sv_2mortal(newSViv(PTR2IV(c->ssl)));')
436             ->else
437             ->line('ST(0) = &PL_sv_undef;')
438             ->endif
439             ->xs_return('1')
440             ->xs_end
441             ->blank;
442             }
443              
444             1;