Skip to content

Commit 0d7140a

Browse files
Summary of changes in this commit: (#18)
* Summary of changes in this commit: * Factor ERR_clear_error into its own function and call in the geterror macro right before we do SSL IO operations; the openssl docs recommend this and trying to keep your error stack as clean as possible * Add :acquire/:release memory orderings to our atomic operations; shouldn't make that big of a difference, but more semantically correct with how we define our atomicget/atomicset macros * Instead of call `eof(ssl)` before each SSL_read_ex call, we instead only check that the ssl is open, then optimistically call SSL_read_ex; if there are buffered bytes, they'll be processed and we saved ourselves an `eof` call; if SSL_read_ex returns SSL_WANT_READ, then we'll call `eof` anyway afterwards * Remove the finalizer on our SSL struct; @kpamnany recommended avoiding finalizers and we already have really well defined create/destroy moments for SSLs, so we don't need to be messing w/ the global finalizer list * Remove 2 `update_tls_error_state` calls in SSL_pending and SSL_has_pending; these aren't needed because the openssl documentation mentions specifically these don't use the same error reporting as IO functions * Also tweaked our eof call to avoid allocating a Ref{UInt8} each time it is called * Update src/ssl.jl Co-authored-by: Nick Robinson <[email protected]> * Update src/ssl.jl Co-authored-by: Nick Robinson <[email protected]> * Update src/ssl.jl Co-authored-by: Nick Robinson <[email protected]> --------- Co-authored-by: Nick Robinson <[email protected]>
1 parent e03e6c4 commit 0d7140a

File tree

2 files changed

+64
-42
lines changed

2 files changed

+64
-42
lines changed

src/OpenSSL.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2931,17 +2931,21 @@ function get_error()::String
29312931
# Read the formatted error messages from the memory BIO.
29322932

29332933
# Ensure the queue is clear (if ERR_print_errors fails).
2934-
ccall(
2935-
(:ERR_clear_error, libcrypto),
2936-
Cvoid,
2937-
())
2934+
clear_errors!()
29382935

29392936
# Free bio.
29402937
free(bio)
29412938

29422939
return error_msg
29432940
end
29442941

2942+
function clear_errors!()
2943+
ccall(
2944+
(:ERR_clear_error, libcrypto),
2945+
Cvoid,
2946+
())
2947+
end
2948+
29452949
function OpenSSLError(ret::Integer)
29462950
ptr = ccall(
29472951
(:ERR_error_string, libcrypto),

src/ssl.jl

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ mutable struct SSL
288288
end
289289

290290
ssl = new(ssl)
291-
finalizer(free, ssl)
292291

293292
ccall(
294293
(:SSL_set_bio, libssl),
@@ -375,7 +374,7 @@ macro atomicget(ex)
375374
@static if VERSION < v"1.7"
376375
return esc(Expr(:ref, ex))
377376
else
378-
return esc(:(@atomic $ex))
377+
return esc(:(@atomic :acquire $ex))
379378
end
380379
end
381380

@@ -384,7 +383,7 @@ macro atomicset(ex)
384383
ex.args[1] = Expr(:ref, ex.args[1])
385384
return esc(ex)
386385
else
387-
return esc(:(@atomic $ex))
386+
return esc(:(@atomic :release $ex))
388387
end
389388
end
390389

@@ -399,6 +398,7 @@ mutable struct SSLStream <: IO
399398
io::TCPSocket
400399
lock::ReentrantLock
401400
readbytes::Base.RefValue{Csize_t}
401+
writebytes::Base.RefValue{Csize_t}
402402
@static if VERSION < v"1.7"
403403
close_notify_received::Threads.Atomic{Bool}
404404
closed::Threads.Atomic{Bool}
@@ -414,9 +414,9 @@ end
414414
ssl = SSL(ssl_context, bio_read, bio_write)
415415

416416
@static if VERSION < v"1.7"
417-
return new(ssl, ssl_context, bio_read, bio_write, io, ReentrantLock(), Ref{Csize_t}(0), Threads.Atomic{Bool}(false), Threads.Atomic{Bool}(false))
417+
return new(ssl, ssl_context, bio_read, bio_write, io, ReentrantLock(), Ref{Csize_t}(0), Ref{Csize_t}(0), Threads.Atomic{Bool}(false), Threads.Atomic{Bool}(false))
418418
else
419-
return new(ssl, ssl_context, bio_read, bio_write, io, ReentrantLock(), Ref{Csize_t}(0), false, false)
419+
return new(ssl, ssl_context, bio_read, bio_write, io, ReentrantLock(), Ref{Csize_t}(0), Ref{Csize_t}(0), false, false)
420420
end
421421
end
422422
end
@@ -432,10 +432,14 @@ Base.isopen(ssl::SSLStream)::Bool = !@atomicget(ssl.closed)
432432
Base.iswritable(ssl::SSLStream)::Bool = isopen(ssl) && isopen(ssl.io)
433433
check_isopen(ssl::SSLStream, op) = isopen(ssl) || throw(Base.IOError("$op requires ssl to be open", 0))
434434

435-
macro geterror(expr)
436-
esc(quote
437-
ret = $expr
438-
if ret <= 0
435+
macro geterror(ssl, expr)
436+
quote
437+
clear_errors!()
438+
ret = $(esc(expr))
439+
if ret == 1
440+
ret = SSL_ERROR_NONE
441+
else
442+
ssl = $(esc(ssl))
439443
err = get_error(ssl.ssl, ret)
440444
if err == SSL_ERROR_ZERO_RETURN
441445
@atomicset ssl.close_notify_received = true
@@ -450,27 +454,35 @@ macro geterror(expr)
450454
throw(Base.IOError(OpenSSLError(err).msg, 0))
451455
end
452456
end
453-
end)
457+
ret
458+
end
454459
end
455460

456461
function Base.unsafe_write(ssl::SSLStream, in_buffer::Ptr{UInt8}, in_length::UInt)
457462
check_isopen(ssl, "unsafe_write")
458-
@geterror ccall(
459-
(:SSL_write, libssl),
460-
Cint,
461-
(SSL, Ptr{Cvoid}, Cint),
462-
ssl.ssl,
463-
in_buffer,
464-
in_length
465-
)
466-
return ret
463+
nwritten = 0
464+
while nwritten < in_length
465+
ret = @geterror ssl ccall(
466+
(:SSL_write_ex, libssl),
467+
Cint,
468+
(SSL, Ptr{Cvoid}, Cint, Ptr{Csize_t}),
469+
ssl.ssl,
470+
in_buffer,
471+
in_length,
472+
ssl.writebytes
473+
)
474+
if ret == SSL_ERROR_NONE
475+
nwritten += ssl.writebytes[]
476+
end
477+
end
478+
return Base.bitcast(Int, in_length)
467479
end
468480

469481
function Sockets.connect(ssl::SSLStream; require_ssl_verification::Bool=true)
470482
while true
471483
check_isopen(ssl, "connect")
472-
@geterror ssl_connect(ssl.ssl)
473-
if (ret == 1 || ret == SSL_ERROR_NONE)
484+
ret = @geterror ssl ssl_connect(ssl.ssl)
485+
if ret == SSL_ERROR_NONE
474486
break
475487
elseif ret == SSL_ERROR_WANT_READ
476488
if eof(ssl.io)
@@ -534,18 +546,24 @@ end
534546
function Base.unsafe_read(ssl::SSLStream, buf::Ptr{UInt8}, nbytes::UInt)
535547
nread = 0
536548
while nread < nbytes
537-
(!isopen(ssl) || eof(ssl)) && throw(EOFError())
549+
# If open, optimistically call `SSL_read_ex` to try to save an `eof` call;
550+
# if that returns `SSL_WANT_READ` we will call `eof` anyway afterwards.
551+
!isopen(ssl) && throw(EOFError())
538552
readbytes = ssl.readbytes
539-
@geterror ccall(
553+
ret = @geterror ssl ccall(
540554
(:SSL_read_ex, libssl),
541555
Cint,
542-
(SSL, Ptr{Int8}, Csize_t, Ptr{Csize_t}),
556+
(SSL, Ptr{UInt8}, Csize_t, Ptr{Csize_t}),
543557
ssl.ssl,
544558
buf + nread,
545559
nbytes - nread,
546560
readbytes
547561
)
548-
nread += Int(readbytes[])
562+
if ret == SSL_ERROR_NONE
563+
nread += Base.bitcast(Int, readbytes[])
564+
else
565+
eof(ssl.io) && throw(EOFError())
566+
end
549567
end
550568
return nread
551569
end
@@ -564,7 +582,6 @@ function Base.bytesavailable(ssl::SSLStream)::Cint
564582
Cint,
565583
(SSL,),
566584
ssl.ssl)
567-
update_tls_error_state()
568585
return pending_count
569586
end
570587

@@ -575,12 +592,9 @@ function haspending(s::SSLStream)
575592
Cint,
576593
(SSL,),
577594
s.ssl)
578-
update_tls_error_state()
579595
return has_pending == 1
580596
end
581597

582-
const PEEK_REF = Ref{UInt8}(0x00)
583-
584598
function Base.eof(ssl::SSLStream)::Bool
585599
bytesavailable(ssl) > 0 && return false
586600
Base.@lock ssl.lock begin
@@ -598,14 +612,18 @@ function Base.eof(ssl::SSLStream)::Bool
598612
end
599613
# if we're here, we know there are unprocessed bytes,
600614
# so we call peek to force processing
601-
@geterror ccall(
602-
(:SSL_peek, libssl),
603-
Cint,
604-
(SSL, Ref{UInt8}, Cint),
605-
ssl.ssl,
606-
PEEK_REF,
607-
1
608-
)
615+
byte = Ref{UInt8}(0x00)
616+
ptr = Base.unsafe_convert(Ptr{UInt8}, byte)
617+
GC.@preserve byte begin
618+
ret = @geterror ssl ccall(
619+
(:SSL_peek, libssl),
620+
Cint,
621+
(SSL, Ptr{UInt8}, Cint),
622+
ssl.ssl,
623+
ptr,
624+
1
625+
)
626+
end
609627
# if we get WANT_READ back, that means there were pending bytes
610628
# to be processed, but not a full record, so we need to wait
611629
# for additional bytes to come in before we can process
@@ -637,7 +655,7 @@ function Base.close(ssl::SSLStream, shutdown::Bool=true)
637655
catch e
638656
e isa Base.IOError || rethrow()
639657
end
640-
finalize(ssl.ssl)
658+
free(ssl.ssl)
641659
end
642660
return
643661
end

0 commit comments

Comments
 (0)