class TinyDTLS::UDPSocket
This class implements a DTLS socket on top of a ruby UDPSocket
. It isn't currently nowhere near being API compatible with the ruby UDPSocket
. Being 100% backwards compatible with the ruby UDPSocket
is not possible to to tinydtls internals. For instance we can't properly implement IO#select. It should thus be considered if it is really a good idea to extend the ruby UDPSocket
in the long run.
Basic send and receive methods are implemented and should work.
Constants
- MAX_RETRY
Maximum of times a
dtls_send
is retried.- Read
- Write
Public Class Methods
# File lib/tinydtls/udpsocket.rb, line 43 def initialize(address_family = Socket::AF_INET, timeout = nil) super(address_family) Wrapper::dtls_init @timeout = timeout.freeze @queue = Queue.new @family = address_family @sendfn = method(:send).super_method @secconf = SecurityConfig.new @context = TinyDTLS::Context.new(@sendfn, @queue, @secconf) CONTEXT_MAP[@context.key] = @context if timeout.nil? @sessions = SessionManager.new(@context) else @sessions = SessionManager.new(@context, timeout) end @handler = Wrapper::DTLSHandlerStruct.new @handler[:write] = UDPSocket::Write @handler[:read] = UDPSocket::Read @handler[:get_psk_info] = SecurityConfig::GetPSKInfo Wrapper::dtls_set_handler(@context.to_ffi, @handler) end
Public Instance Methods
Adds a new identity/key pair to the underlying TinyDTLS::SessionManager
. By default the first pair added to the store will be used for establishing new handshakes, this behaviour can be changed using the optional default argument.
# File lib/tinydtls/udpsocket.rb, line 73 def add_client(id, key, default = false) @secconf.add_client(id, key) if default @secconf.default_id = id @secconf.default_key = key end end
# File lib/tinydtls/udpsocket.rb, line 82 def bind(host, port) super(host, port) start_thread end
TODO: close_{read,write}
# File lib/tinydtls/udpsocket.rb, line 89 def close # This method can't be called twice since we actually free memory # allocated by ruby-ffi in this function. Since we can't free it # twice we ensure that this function is only called once. return unless CONTEXT_MAP.has_key? @context.key @sessions.close unless @thread.nil? @thread.kill while @thread.alive? @thread.join end end # dtls_free_context sends messages to peers so we need to # explicitly free the dtls_context_t before closing the socket. Wrapper::dtls_free_context(@context.to_ffi) super # Assuming the @thread is already stopped at this point # we can safely access the CONTEXT_MAP without running # into any kind of concurrency problems. CONTEXT_MAP.delete(@context.key) end
# File lib/tinydtls/udpsocket.rb, line 114 def connect(host, port) @defhost = host @defport = port end
# File lib/tinydtls/udpsocket.rb, line 119 def recvfrom(len = -1, flags = 0) ary = @queue.pop return [byteslice(ary.first, len), ary.last] end
# File lib/tinydtls/udpsocket.rb, line 124 def recvfrom_nonblock(len = -1, flag = 0, outbuf = nil, exception: true) ary = nil begin ary = @queue.pop(true) rescue ThreadError if exception raise IO::EAGAINWaitReadable else return :wait_readable end end pay = byteslice(ary.first, len) unless outbuf.nil? outbuf << pay end return [pay, ary.last] end
TODO: The recvmsg functions only implement a subset of the functionallity of the UDP socket class, e.g. they don't return ancillary data.
# File lib/tinydtls/udpsocket.rb, line 148 def recvmsg(maxmesglen = nil, flags = 0, maxcontrollen = nil, opts = {}) mesg, sender = recvfrom(maxmesglen.nil? ? -1 : maxmesglen, flags) return [mesg, to_addrinfo(*sender), 0, nil] end
# File lib/tinydtls/udpsocket.rb, line 153 def recvmsg_nonblock(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {}) mesg, sender = recvfrom_nonblock(maxdatalen.nil? ? -1 : maxdatalen, flags) return [mesg, to_addrinfo(*sender), 0, nil] end
# File lib/tinydtls/udpsocket.rb, line 158 def send(mesg, flags, host = nil, port = nil) start_thread if host.nil? and port.nil? if @defport.nil? or @defhost.nil? raise Errno::EDESTADDRREQ end host = @defhost port = @defport elsif port.nil? # host is not nil and must be a sockaddr_to port, host = Socket.unpack_sockaddr_in(host) end addr = Addrinfo.getaddrinfo(host, port, @family, :DGRAM).first # If a new thread has been started above a new handshake needs to # be performed by it. We need to block here until the handshake # was completed. # # The current approach is calling `Wrapper::dtls_write` up to # MAX_RETRY times. If we didn't manage to send our data to the # peer after MAX_RETRY times an exception is raised. MAX_RETRY.times do res = dtls_send(addr, mesg) if res > 0 return res end sleep 1 end raise Errno::ECONNREFUSED.new("DTLS handshake failed") end
Private Instance Methods
# File lib/tinydtls/udpsocket.rb, line 200 def byteslice(str, len) return len >= 0 ? str.byteslice(0, len) : str end
Sends a dtls message to a specified address. It also takes care of locking the session manager and is thus thread-safe.
# File lib/tinydtls/udpsocket.rb, line 206 def dtls_send(addr, mesg) @sessions[addr] do |sess| res = Wrapper::dtls_write(@context.to_ffi, sess.to_ptr, mesg, mesg.bytesize) res == -1 ? raise(Errno::EIO) : res end end
Creates a thread responsible for reading from reciving messages from the underlying socket and passing them to tinydtls.
The thread is only created once.
# File lib/tinydtls/udpsocket.rb, line 218 def start_thread @thread ||= Thread.new do loop do data, addr = method(:recvfrom).super_method .call(Wrapper::DTLS_MAX_BUF) addrinfo = to_addrinfo(*addr) @sessions[addrinfo] do |sess| Wrapper::dtls_handle_message(@context.to_ffi, sess.to_ptr, data, data.bytesize) end end end end
# File lib/tinydtls/udpsocket.rb, line 195 def to_addrinfo(*args) af, port, _, addr = args Addrinfo.getaddrinfo(addr, port, af, :DGRAM).first end