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

new(address_family = Socket::AF_INET, timeout = nil) click to toggle source
Calls superclass method
# 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

add_client(id, key, default = false) click to toggle source

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
bind(host, port) click to toggle source
Calls superclass method
# File lib/tinydtls/udpsocket.rb, line 82
def bind(host, port)
  super(host, port)
  start_thread
end
close() click to toggle source

TODO: close_{read,write}

Calls superclass method
# 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
connect(host, port) click to toggle source
# File lib/tinydtls/udpsocket.rb, line 114
def connect(host, port)
  @defhost = host
  @defport = port
end
recvfrom(len = -1, flags = 0) click to toggle source
# File lib/tinydtls/udpsocket.rb, line 119
def recvfrom(len = -1, flags = 0)
  ary = @queue.pop
  return [byteslice(ary.first, len), ary.last]
end
recvfrom_nonblock(len = -1, flag = 0, outbuf = nil, exception: true) click to toggle source
# 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
recvmsg(maxmesglen = nil, flags = 0, maxcontrollen = nil, opts = {}) click to toggle source

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
recvmsg_nonblock(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {}) click to toggle source
# 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
send(mesg, flags, host = nil, port = nil) click to toggle source
# 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

byteslice(str, len) click to toggle source
# File lib/tinydtls/udpsocket.rb, line 200
def byteslice(str, len)
  return len >= 0 ? str.byteslice(0, len) : str
end
dtls_send(addr, mesg) click to toggle source

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
start_thread() click to toggle source

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
to_addrinfo(*args) click to toggle source
# File lib/tinydtls/udpsocket.rb, line 195
def to_addrinfo(*args)
  af, port, _, addr = args
  Addrinfo.getaddrinfo(addr, port, af, :DGRAM).first
end