Hi,
I’ve had a little trouble getting SSLSocket to behave as I want it to.
It’s #pending method returns 0 until I call #sysread(1)… which throws
EOFError if there is nothing to read, or 1 byte after some bytes have
become available.
I suspect that the implementation does not fill its internal buffer with
raw bytes to decrypt to its output buffer until #sysread is called on an
empty output buffer. Given that the SSLSocket class almost certainly
would not be implementing its own thread to do this automatically, that
seems reasonable.
But my solution seems a bit “hackish”, and I would appreciate if someone
knows a better way to do this?
(Maybe IO:select on the tcp socket to see if it has bytes ready, then
call #sysread)
Following is a simplified code snippet that shows essentially what I’m
doing:
class SslSocketServer
def initialize(port, hostname, cert_fname, key_fname)
@socket_server = Socket.new(AF_INET, SOCK_STREAM, 0)
sockaddr = Socket.sockaddr_in(port, hostname)
@socket_server.bind(sockaddr)
@socket_server.listen(5)
@ssl_context = OpenSSL::SSL::SSLContext.new
cert_file = File.open(cert_fname)
key_file = File.open(key_fname)
@ssl_context.cert = OpenSSL::X509::Certificate.new(cert_file)
@ssl_context.key = OpenSSL::PKey::RSA.new(key_file)
end
def accept
ssl = nil
begin
sock, addr = @socket_server.accept
begin
sslsock = OpenSSL::SSL::SSLSocket.new(sock, @ssl_context)
sslsock.sync_close = true
sslsock.accept
ssl = SslConnection.new(sock, sslsock, addr)
rescue SSLError => ex
sock.close
raise ex
end
rescue IO::WaitReadable
rescue Errno::EINTR => e
STDERR.puts("#{e.class}: #{e.to_s}")
STDERR.puts(e.backtrace.join("\n"))
end
ssl
end
end
class SslConnection
def initialize(sock, sslsock, addr)
@sock = sock
@sslsock = sslsock
@addr = addr
end
def recvfrom_nonblock(size)
available = @sslsock.pending
if available == 0
begin
byte = @sslsock.sysread(1)
[byte, @addr]
rescue EOFError
raise SslNotEnoughPending, “EOFError”
end
else
size = available if available < size
[@sslsock.sysread(size), @addr]
end
end
def close
@sslsock.close
@sock.close
end
def write(bytes)
@sslsock.syswrite(bytes)
end
end
Then I read from the SslConnection as if it were a socket returned from
Socket#accept, using the rcvfrom_nonblock and write methods only.