tinydtls 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/tinydtls.rb +16 -0
- data/lib/tinydtls/context.rb +35 -0
- data/lib/tinydtls/security_conf.rb +83 -0
- data/lib/tinydtls/session.rb +47 -0
- data/lib/tinydtls/session_manager.rb +88 -0
- data/lib/tinydtls/udpsocket.rb +206 -0
- data/lib/tinydtls/wrapper.rb +125 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2e326a7a3bf6820d5596c83553eb9d0cd9c2309ffbb00e24d3045e5a2b69e8c5
|
|
4
|
+
data.tar.gz: b446f4a18ca71e313bf5ad06d651e89442ac847526ac181b826fa9fac1d1d99c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3e1a1a8fe929488102db4cdf3d2d0ef3ec9dec8518fb74d00ed29afb868ea60010d7ee0b5fe64300a61f6b0cd7633293ccca4cdef4bb3122f6e2e70a27118319
|
|
7
|
+
data.tar.gz: 5b873f6d31d0ce0ba9cc3f0795bc8a95b078761ceab382c719acb3a19a1de2a3231873b4cdd86bc419db79682ea154532928261b3503d6906bd58e7c810906c1
|
data/lib/tinydtls.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "socket"
|
|
2
|
+
require "ffi"
|
|
3
|
+
|
|
4
|
+
require "tinydtls/wrapper"
|
|
5
|
+
require "tinydtls/context"
|
|
6
|
+
require "tinydtls/session"
|
|
7
|
+
require "tinydtls/security_conf"
|
|
8
|
+
require "tinydtls/session_manager"
|
|
9
|
+
require "tinydtls/udpsocket"
|
|
10
|
+
|
|
11
|
+
module TinyDTLS
|
|
12
|
+
# Map used to map `object_ids` passed as void pointers to the tinydtls
|
|
13
|
+
# callback functions to actually ruby UDPSockets. This is neccessary
|
|
14
|
+
# since we can't pass pointers to ruby objects to C functions.
|
|
15
|
+
CONTEXT_MAP = Hash.new
|
|
16
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# The class Context stores all per-connection information,
|
|
3
|
+
# it is exclusively used in the `TinyDTLS::CONTEXT_MAP`.
|
|
4
|
+
class Context
|
|
5
|
+
# The method used for sending data on the socket.
|
|
6
|
+
attr_reader :sendfn
|
|
7
|
+
|
|
8
|
+
# The queue used for communication with the receive thread.
|
|
9
|
+
attr_reader :queue
|
|
10
|
+
|
|
11
|
+
# An instance of the security configuration class.
|
|
12
|
+
attr_reader :secconf
|
|
13
|
+
|
|
14
|
+
# Create a new instance of this class with a given function to send
|
|
15
|
+
# message on the transport layer, a queue for storing received
|
|
16
|
+
# messages and a security configuration containing a key to identity
|
|
17
|
+
# mapping.
|
|
18
|
+
def initialize(sendfn, queue, secconf)
|
|
19
|
+
@sendfn = sendfn
|
|
20
|
+
@queue = queue
|
|
21
|
+
@secconf = secconf
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Create a new instance of this class from a pointer to a `struct
|
|
25
|
+
# dtls_context_t`. Such a pointer is, for instance, passed to the
|
|
26
|
+
# various tinydtls callback functions.
|
|
27
|
+
#
|
|
28
|
+
# The `struct dtls_context_t` which the given pointer points to must
|
|
29
|
+
# have been created by TinyDTLS::UDPSocket#initialize.
|
|
30
|
+
def self.from_ptr(ptr)
|
|
31
|
+
obj = Wrapper::DTLSContextStruct.new(ptr)
|
|
32
|
+
return CONTEXT_MAP[Wrapper::dtls_get_app_data(obj).to_i]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# This class is used to map user identity for pre-shared keys to their
|
|
3
|
+
# actual keys. It provides an implementation of the `get_psk_info`
|
|
4
|
+
# function pointer used in the `dtls_handler_t` struct which is used
|
|
5
|
+
# by tinydtls to retrieve keys and identities.
|
|
6
|
+
#
|
|
7
|
+
# XXX: Currently this function doesn't map IP address to keys/identities.
|
|
8
|
+
class SecurityConfig
|
|
9
|
+
# Implementation of the `get_psk_info` function pointer as used by
|
|
10
|
+
# the `dtls_handler_t` struct.
|
|
11
|
+
#
|
|
12
|
+
# If tinydtls requests a key for a given identity the key is
|
|
13
|
+
# returned if the identity exists. If no identity was specified the
|
|
14
|
+
# #default_key is returned.
|
|
15
|
+
#
|
|
16
|
+
# If tinydtls requests an id the #default_id is always returned.
|
|
17
|
+
#
|
|
18
|
+
# TODO: It would be nice to return an id depending on the
|
|
19
|
+
# `session_t` passad to this callback.
|
|
20
|
+
GetPSKInfo = Proc.new do |ctx, sess, type, desc, dlen, result, rlen|
|
|
21
|
+
secconf = TinyDTLS::Context.from_ptr(ctx).secconf
|
|
22
|
+
if desc.null?
|
|
23
|
+
key = secconf.default_key
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if type == :DTLS_PSK_KEY
|
|
27
|
+
key ||= secconf.get_key(desc.read_string(dlen))
|
|
28
|
+
if key.nil?
|
|
29
|
+
Wrapper::dtls_alert_fatal_create(
|
|
30
|
+
Wrapper::Alert[:DTLS_ALERT_DECRYPT_ERROR])
|
|
31
|
+
elsif key.bytesize > rlen
|
|
32
|
+
Wrapper::dtls_alert_fatal_create(
|
|
33
|
+
Wrapper::Alert[:DTLS_ALERT_INTERNAL_ERROR])
|
|
34
|
+
else
|
|
35
|
+
result.put_bytes(0, key)
|
|
36
|
+
key.bytesize
|
|
37
|
+
end
|
|
38
|
+
elsif type == :DTLS_PSK_IDENTITY
|
|
39
|
+
identity = secconf.default_id
|
|
40
|
+
result.put_bytes(0, identity)
|
|
41
|
+
identity.bytesize
|
|
42
|
+
else
|
|
43
|
+
0
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Create a new instance of this class. A #default_key and a
|
|
48
|
+
# #default_id can be optionally specified. If they are not specified
|
|
49
|
+
# the first key/identity added is used as the default value.
|
|
50
|
+
def initialize(default_id = nil, default_key = nil)
|
|
51
|
+
@default_id = default_id
|
|
52
|
+
@default_key = default_key
|
|
53
|
+
|
|
54
|
+
@identity_map = Hash.new
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Adds a security configuration for the given identity.
|
|
58
|
+
def add_client(id, key)
|
|
59
|
+
@identity_map[id] = key
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Retrieves the key associated with the given identity.
|
|
63
|
+
def get_key(id)
|
|
64
|
+
@identity_map[id]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def default_id
|
|
68
|
+
if @default_id.nil?
|
|
69
|
+
@identity_map.to_a.first.first
|
|
70
|
+
else
|
|
71
|
+
@default_id
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def default_key
|
|
76
|
+
if @default_key.nil?
|
|
77
|
+
@identity_map.to_a.first.last
|
|
78
|
+
else
|
|
79
|
+
@default_key
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# This class offers a higher-level abstraction for the `session_t` type.
|
|
3
|
+
class Session
|
|
4
|
+
attr_reader :addrinfo
|
|
5
|
+
|
|
6
|
+
# Creates a new instance of this class from the given Addrinfo.
|
|
7
|
+
def initialize(addrinfo)
|
|
8
|
+
@addrinfo = addrinfo
|
|
9
|
+
unless @addrinfo.is_a? Addrinfo
|
|
10
|
+
raise TypeError
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
sockaddr = @addrinfo.to_sockaddr
|
|
14
|
+
@session = Wrapper::dtls_new_session(sockaddr, sockaddr.bytesize)
|
|
15
|
+
if @session.null?
|
|
16
|
+
raise Errno::ENOMEM
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates a new instance of this class from a pointer to a
|
|
21
|
+
# `session_t` tinydtls type. Such a pointer is, for instance, passed
|
|
22
|
+
# to the various tinydtls callback functions.
|
|
23
|
+
def self.from_ptr(ptr)
|
|
24
|
+
lenptr = Wrapper::SocklenPtr.new
|
|
25
|
+
sockaddr = Wrapper::dtls_session_addr(ptr, lenptr)
|
|
26
|
+
|
|
27
|
+
addrinfo = Addrinfo.new(sockaddr.read_string(lenptr[:value]))
|
|
28
|
+
return Session.new(addrinfo)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Converts the object into a C pointer to a `session_t` tinydtls
|
|
32
|
+
# type. This pointer can be passed to various functions provided by
|
|
33
|
+
# TinyDTLS::Wrapper.
|
|
34
|
+
def to_ptr
|
|
35
|
+
@session
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Frees all resources associated with the underlying `session_t`
|
|
39
|
+
# tinydtls type and reset any existing connections.
|
|
40
|
+
def destroy!(ctx)
|
|
41
|
+
peer = Wrapper::dtls_get_peer(ctx, @session)
|
|
42
|
+
unless peer.null?
|
|
43
|
+
Wrapper::dtls_reset_peer(ctx, peer)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# This class is used to manage established tinydtls sessions. It
|
|
3
|
+
# stores instances of the TinyDTLS::Session class.
|
|
4
|
+
#
|
|
5
|
+
# While memory allocated for sessions is automatically freed by
|
|
6
|
+
# tinydtls, if it receive an alert from the peer associated with that
|
|
7
|
+
# session, memory isn't freed if the peer doesn't send an alert.
|
|
8
|
+
# Therefore this class starts a background thread automatically
|
|
9
|
+
# freeing memory associated with sessions which haven't been used
|
|
10
|
+
# since a specified duration.
|
|
11
|
+
class SessionManager
|
|
12
|
+
# Default timeout for the cleanup thread in seconds.
|
|
13
|
+
DEFAULT_TIMEOUT = (5 * 60).freeze
|
|
14
|
+
|
|
15
|
+
attr_accessor :timeout
|
|
16
|
+
|
|
17
|
+
# Creates a new instance of this class. A tinydtls `context_t`
|
|
18
|
+
# pointer is required to free sessions in the background thread.
|
|
19
|
+
def initialize(ctx, timeout = DEFAULT_TIMEOUT)
|
|
20
|
+
@store = {}
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
@timeout = timeout
|
|
23
|
+
|
|
24
|
+
start_thread(ctx)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Retrieve a session from the session manager.
|
|
28
|
+
def [](addrinfo, &f)
|
|
29
|
+
unless addrinfo.is_a? Addrinfo
|
|
30
|
+
raise TypeError
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
key = addrinfo.getnameinfo
|
|
34
|
+
if @store.has_key? key
|
|
35
|
+
sess, _ = @store[key]
|
|
36
|
+
else
|
|
37
|
+
sess = Session.new(addrinfo)
|
|
38
|
+
@store[key] = [sess, true]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
@mutex.synchronize { f.call(sess) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Frees all ressources associated with this class.
|
|
45
|
+
def destroy!
|
|
46
|
+
@mutex.lock
|
|
47
|
+
@store.clear
|
|
48
|
+
@thread.kill
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Creates a thread responsible for freeing ressources assigned to
|
|
54
|
+
# stale connection. This thread implements the clock hand algorithm
|
|
55
|
+
# as described in Modern Operating Systems, p. 212.
|
|
56
|
+
#
|
|
57
|
+
# The thread is only created once.
|
|
58
|
+
def start_thread(ctx)
|
|
59
|
+
@thread ||= Thread.new do
|
|
60
|
+
while true
|
|
61
|
+
# XXX: How does concurrent access to variables work in ruby?
|
|
62
|
+
# as known as: Is this a concurrency problems since the value
|
|
63
|
+
# of @timeout might be changed by a different thread since an
|
|
64
|
+
# attr_accessor for it is declared.
|
|
65
|
+
sleep @timeout
|
|
66
|
+
|
|
67
|
+
@mutex.lock
|
|
68
|
+
@store.transform_values! do |value|
|
|
69
|
+
sess, used = value
|
|
70
|
+
if used
|
|
71
|
+
[sess, !used]
|
|
72
|
+
else # Not used since we've been here last time → free resources
|
|
73
|
+
sess.destroy!(ctx)
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# We can't delete elements from the map in the #transform_values!
|
|
79
|
+
# block, we just assign nil to them. Thus we need to filter
|
|
80
|
+
# the map again here.
|
|
81
|
+
@store.reject! { |_, v| v.nil? }
|
|
82
|
+
|
|
83
|
+
@mutex.unlock
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# This class implements a DTLS socket on top of a ruby UDPSocket. It
|
|
3
|
+
# isn't currently nowhere near being API compatible with the ruby
|
|
4
|
+
# UDPSocket. Being 100% backwards compatible with the ruby UDPSocket
|
|
5
|
+
# is not possible to to tinydtls internals. For instance we can't
|
|
6
|
+
# properly implement IO#select. It should thus be considered if it is
|
|
7
|
+
# really a good idea to extend the ruby UDPSocket in the long run.
|
|
8
|
+
#
|
|
9
|
+
# Basic send and receive methods are implemented and should work.
|
|
10
|
+
class UDPSocket < ::UDPSocket
|
|
11
|
+
Write = Proc.new do |ctx, sess, buf, len|
|
|
12
|
+
addrinfo = Session.from_ptr(sess).addrinfo
|
|
13
|
+
|
|
14
|
+
ctxobj = TinyDTLS::Context.from_ptr(ctx)
|
|
15
|
+
ctxobj.sendfn.call(buf.read_string(len),
|
|
16
|
+
Socket::MSG_DONTWAIT,
|
|
17
|
+
addrinfo.ip_address, addrinfo.ip_port)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Read = Proc.new do |ctx, sess, buf, len|
|
|
21
|
+
addrinfo = Session.from_ptr(sess).addrinfo
|
|
22
|
+
|
|
23
|
+
# We need to perform a reverse lookup here because
|
|
24
|
+
# the #recvfrom function needs to return the DNS
|
|
25
|
+
# hostname.
|
|
26
|
+
sender = Socket.getaddrinfo(addrinfo.ip_address,
|
|
27
|
+
addrinfo.ip_port, nil, :DGRAM,
|
|
28
|
+
0, 0, true).first
|
|
29
|
+
|
|
30
|
+
ctxobj = TinyDTLS::Context.from_ptr(ctx)
|
|
31
|
+
ctxobj.queue.push([buf.read_string(len), sender])
|
|
32
|
+
|
|
33
|
+
# It is unclear to me why this callback even needs a return value,
|
|
34
|
+
# the `tests/dtls-client.c` program in the tinydtls repository
|
|
35
|
+
# simply uses 0 as a return value, so let's do that as well.
|
|
36
|
+
0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize(address_family = Socket::AF_INET, timeout = nil)
|
|
40
|
+
super(address_family)
|
|
41
|
+
Wrapper::dtls_init
|
|
42
|
+
|
|
43
|
+
@timeout = timeout.freeze
|
|
44
|
+
@queue = Queue.new
|
|
45
|
+
@family = address_family
|
|
46
|
+
@sendfn = method(:send).super_method
|
|
47
|
+
@secconf = SecurityConfig.new
|
|
48
|
+
|
|
49
|
+
id = object_id
|
|
50
|
+
CONTEXT_MAP[id] = TinyDTLS::Context.new(@sendfn, @queue, @secconf)
|
|
51
|
+
|
|
52
|
+
cptr = Wrapper::dtls_new_context(FFI::Pointer.new(id))
|
|
53
|
+
@ctx = Wrapper::DTLSContextStruct.new(cptr)
|
|
54
|
+
|
|
55
|
+
if timeout.nil?
|
|
56
|
+
@sessions = SessionManager.new(@ctx)
|
|
57
|
+
else
|
|
58
|
+
@sessions = SessionManager.new(@ctx, timeout)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@handler = Wrapper::DTLSHandlerStruct.new
|
|
62
|
+
@handler[:write] = UDPSocket::Write
|
|
63
|
+
@handler[:read] = UDPSocket::Read
|
|
64
|
+
@handler[:get_psk_info] = SecurityConfig::GetPSKInfo
|
|
65
|
+
Wrapper::dtls_set_handler(@ctx, @handler)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_client(id, key)
|
|
69
|
+
@secconf.add_client(id, key)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def bind(host, port)
|
|
73
|
+
super(host, port)
|
|
74
|
+
start_thread
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# TODO: close_{read,write}
|
|
78
|
+
|
|
79
|
+
def close
|
|
80
|
+
@sessions.destroy!
|
|
81
|
+
@thread.kill unless @thread.nil?
|
|
82
|
+
|
|
83
|
+
# DTLS free context sends messages to peers so we need to
|
|
84
|
+
# call it before we actually close the underlying socket.
|
|
85
|
+
Wrapper::dtls_free_context(@ctx)
|
|
86
|
+
super
|
|
87
|
+
|
|
88
|
+
# Assuming the @thread is already stopped at this point
|
|
89
|
+
# we can safely access the CONTEXT_MAP without running
|
|
90
|
+
# into any kind of concurrency problems.
|
|
91
|
+
CONTEXT_MAP.delete(object_id)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def connect(host, port)
|
|
95
|
+
@defhost = host
|
|
96
|
+
@defport = port
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def recvfrom(len = -1, flags = 0)
|
|
100
|
+
ary = @queue.pop
|
|
101
|
+
return [byteslice(ary.first, len), ary.last]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def recvfrom_nonblock(len = -1, flag = 0, outbuf = nil, exception: true)
|
|
105
|
+
ary = nil
|
|
106
|
+
begin
|
|
107
|
+
ary = @queue.pop(true)
|
|
108
|
+
rescue ThreadError
|
|
109
|
+
if exception
|
|
110
|
+
raise IO::EAGAINWaitReadable
|
|
111
|
+
else
|
|
112
|
+
return :wait_readable
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
pay = byteslice(ary.first, len)
|
|
117
|
+
unless outbuf.nil?
|
|
118
|
+
outbuf << pay
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
return [pay, ary.last]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# TODO: The recvmsg functions only implement a subset of the
|
|
125
|
+
# functionallity of the UDP socket class, e.g. they don't return
|
|
126
|
+
# ancillary data.
|
|
127
|
+
|
|
128
|
+
def recvmsg(maxmesglen = nil, flags = 0, maxcontrollen = nil, opts = {})
|
|
129
|
+
mesg, sender = recvfrom(maxmesglen.nil? ? -1 : maxmesglen, flags)
|
|
130
|
+
return [mesg, to_addrinfo(*sender), 0, nil]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def recvmsg_nonblock(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {})
|
|
134
|
+
mesg, sender = recvfrom_nonblock(maxdatalen.nil? ? -1 : maxdatalen, flags)
|
|
135
|
+
return [mesg, to_addrinfo(*sender), 0, nil]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def send(mesg, flags, host = nil, port = nil)
|
|
139
|
+
start_thread
|
|
140
|
+
|
|
141
|
+
if host.nil? and port.nil?
|
|
142
|
+
if @defport.nil? or @defhost.nil?
|
|
143
|
+
raise Errno::EDESTADDRREQ
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
host = @defhost
|
|
147
|
+
port = @defport
|
|
148
|
+
elsif port.nil? # host is not nil and must be a sockaddr_to
|
|
149
|
+
port, host = Socket.unpack_sockaddr_in(host)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
addr = Addrinfo.getaddrinfo(host, port, nil, :DGRAM).first
|
|
153
|
+
|
|
154
|
+
# If a new thread has been started above a new handshake needs to
|
|
155
|
+
# be performed by it. We need to block here until the handshake
|
|
156
|
+
# was completed.
|
|
157
|
+
#
|
|
158
|
+
# The current approach is calling `Wrapper::dtls_write` until it
|
|
159
|
+
# succeeds which is suboptimal because it doesn't take into
|
|
160
|
+
# account that the handshake may fail.
|
|
161
|
+
until (res = dtls_send(addr, mesg)) > 0
|
|
162
|
+
sleep 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
return res
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def to_addrinfo(*args)
|
|
171
|
+
af, port, _, addr = args
|
|
172
|
+
Addrinfo.getaddrinfo(addr, port, af, :DGRAM).first
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def byteslice(str, len)
|
|
176
|
+
return len >= 0 ? str.byteslice(0, len) : str
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Sends a dtls message to a specified address. It also takes care
|
|
180
|
+
# of locking the session manager and is thus thread-safe.
|
|
181
|
+
def dtls_send(addr, mesg)
|
|
182
|
+
@sessions[addr] do |sess|
|
|
183
|
+
res = Wrapper::dtls_write(@ctx, sess.to_ptr, mesg, mesg.bytesize)
|
|
184
|
+
res == -1 ? raise(Errno::EIO) : res
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Creates a thread responsible for reading from reciving messages
|
|
189
|
+
# from the underlying socket and passing them to tinydtls.
|
|
190
|
+
#
|
|
191
|
+
# The thread is only created once.
|
|
192
|
+
def start_thread
|
|
193
|
+
@thread ||= Thread.new do
|
|
194
|
+
while true
|
|
195
|
+
data, addr = method(:recvfrom).super_method
|
|
196
|
+
.call(Wrapper::DTLS_MAX_BUF)
|
|
197
|
+
addrinfo = to_addrinfo(*addr)
|
|
198
|
+
|
|
199
|
+
@sessions[addrinfo] do |sess|
|
|
200
|
+
Wrapper::dtls_handle_message(@ctx, sess.to_ptr, data, data.bytesize)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module TinyDTLS
|
|
2
|
+
# This module provides a low level FFI wrapper for the relevant
|
|
3
|
+
# tinydtls functions. It might be subject to change thus it is highly
|
|
4
|
+
# recommended to use the high level abstraction layer instead.
|
|
5
|
+
module Wrapper
|
|
6
|
+
extend FFI::Library
|
|
7
|
+
ffi_lib "libtinydtls.so"
|
|
8
|
+
|
|
9
|
+
# Constants defined as macros in the tinydtls header files
|
|
10
|
+
DTLS_COOKIE_SECRET_LENGTH = 12
|
|
11
|
+
DTLS_MAX_BUF = 1400
|
|
12
|
+
|
|
13
|
+
Alert = enum(
|
|
14
|
+
:DTLS_ALERT_CLOSE_NOTIFY, 0,
|
|
15
|
+
:DTLS_ALERT_UNEXPECTED_MESSAGE, 10,
|
|
16
|
+
:DTLS_ALERT_BAD_RECORD_MAC, 20,
|
|
17
|
+
:DTLS_ALERT_RECORD_OVERFLOW, 22,
|
|
18
|
+
:DTLS_ALERT_DECOMPRESSION_FAILURE, 30,
|
|
19
|
+
:DTLS_ALERT_HANDSHAKE_FAILURE, 40,
|
|
20
|
+
:DTLS_ALERT_BAD_CERTIFICATE, 42,
|
|
21
|
+
:DTLS_ALERT_UNSUPPORTED_CERTIFICATE, 43,
|
|
22
|
+
:DTLS_ALERT_CERTIFICATE_REVOKED, 44,
|
|
23
|
+
:DTLS_ALERT_CERTIFICATE_EXPIRED, 45,
|
|
24
|
+
:DTLS_ALERT_CERTIFICATE_UNKNOWN, 46,
|
|
25
|
+
:DTLS_ALERT_ILLEGAL_PARAMETER, 47,
|
|
26
|
+
:DTLS_ALERT_UNKNOWN_CA, 48,
|
|
27
|
+
:DTLS_ALERT_ACCESS_DENIED, 49,
|
|
28
|
+
:DTLS_ALERT_DECODE_ERROR, 50,
|
|
29
|
+
:DTLS_ALERT_DECRYPT_ERROR, 51,
|
|
30
|
+
:DTLS_ALERT_PROTOCOL_VERSION, 70,
|
|
31
|
+
:DTLS_ALERT_INSUFFICIENT_SECURITY, 71,
|
|
32
|
+
:DTLS_ALERT_INTERNAL_ERROR, 80,
|
|
33
|
+
:DTLS_ALERT_USER_CANCELED, 90,
|
|
34
|
+
:DTLS_ALERT_NO_RENEGOTIATION, 100,
|
|
35
|
+
:DTLS_ALERT_UNSUPPORTED_EXTENSION, 110
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
LogLevel = enum(
|
|
39
|
+
:DTLS_LOG_EMERG, 0,
|
|
40
|
+
:DTLS_LOG_ALERT,
|
|
41
|
+
:DTLS_LOG_CRIT,
|
|
42
|
+
:DTLS_LOG_WARN,
|
|
43
|
+
:DTLS_LOG_NOTICE,
|
|
44
|
+
:DTLS_LOG_INFO,
|
|
45
|
+
:DTLS_LOG_DEBUG
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
enum :alert_level, [
|
|
49
|
+
:DTLS_ALERT_LEVEL_WARNING, 1,
|
|
50
|
+
:DTLS_ALERT_LEVEL_FATAL, 2,
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
enum :credential_type, [
|
|
54
|
+
:DTLS_PSK_HINT,
|
|
55
|
+
:DTLS_PSK_IDENTITY,
|
|
56
|
+
:DTLS_PSK_KEY,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
class DTLSContextStruct < FFI::Struct
|
|
60
|
+
layout :cookie_secret, [:uchar, DTLS_COOKIE_SECRET_LENGTH],
|
|
61
|
+
:cookie_secret_age, :uint32,
|
|
62
|
+
:peers, :pointer,
|
|
63
|
+
:sendqueue, :pointer,
|
|
64
|
+
:app, :pointer,
|
|
65
|
+
:h, :pointer,
|
|
66
|
+
:readbuf, [:uchar, DTLS_MAX_BUF]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class DTLSHandlerStruct < FFI::Struct
|
|
70
|
+
layout :write,
|
|
71
|
+
callback([:pointer, :pointer, :pointer, :size_t], :int),
|
|
72
|
+
:read,
|
|
73
|
+
callback([:pointer, :pointer, :pointer, :size_t], :int),
|
|
74
|
+
:event_function,
|
|
75
|
+
callback([:pointer, :pointer, :alert_level, :ushort], :int),
|
|
76
|
+
:get_psk_info,
|
|
77
|
+
callback([:pointer, :pointer, :credential_type,
|
|
78
|
+
:pointer, :size_t, :pointer, :size_t], :int),
|
|
79
|
+
:get_ecdsa_key,
|
|
80
|
+
callback([:pointer, :pointer, :pointer], :int),
|
|
81
|
+
:verify_ecdsa_key,
|
|
82
|
+
callback([:pointer, :pointer, :uchar, :uchar, :size_t], :int)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
attach_function :dtls_init, [], :void
|
|
86
|
+
attach_function :dtls_new_context, [:pointer], :pointer
|
|
87
|
+
attach_function :dtls_free_context, [:pointer], :void
|
|
88
|
+
attach_function :dtls_handle_message,
|
|
89
|
+
[:pointer, :pointer, :pointer, :int], :int
|
|
90
|
+
attach_function :dtls_write,
|
|
91
|
+
[:pointer, :pointer, :pointer, :size_t], :int
|
|
92
|
+
attach_function :dtls_connect,
|
|
93
|
+
[:pointer, :pointer], :int
|
|
94
|
+
attach_function :dtls_set_log_level, [LogLevel], :void
|
|
95
|
+
|
|
96
|
+
attach_function :dtls_get_peer, [:pointer, :pointer], :pointer
|
|
97
|
+
attach_function :dtls_reset_peer, [:pointer, :pointer], :void
|
|
98
|
+
|
|
99
|
+
def self.dtls_alert_fatal_create(desc)
|
|
100
|
+
return -((2 << 8) | desc)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# This type is needed for the `dtls_session_addr` wrapper.
|
|
104
|
+
# See https://github.com/ffi/ffi/wiki/Pointers#passing-by-reference
|
|
105
|
+
class SocklenPtr < FFI::Struct
|
|
106
|
+
layout :value, :socklen_t
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# These functions are not available in vanilla tinydtls.
|
|
110
|
+
# They are required to make interaction with the tinydtls `session_t`
|
|
111
|
+
# type possible without creating a ruby wrapper for `struct
|
|
112
|
+
# sockaddr_in{,6}`.
|
|
113
|
+
attach_function :dtls_new_session,
|
|
114
|
+
[:pointer, :socklen_t], :pointer
|
|
115
|
+
attach_function :dtls_session_addr, [:pointer, SocklenPtr], :pointer
|
|
116
|
+
|
|
117
|
+
def self.dtls_get_app_data(ctx)
|
|
118
|
+
return ctx[:app]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def self.dtls_set_handler(ctx, handler)
|
|
122
|
+
ctx[:h] = handler
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tinydtls
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sören Tempel
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-07-14 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: ffi
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.9'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.9'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: minitest
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '5.11'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '5.11'
|
|
41
|
+
description: tinydtls provides a DTLS implementation
|
|
42
|
+
email:
|
|
43
|
+
- tempel@uni-bremen.de
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- lib/tinydtls.rb
|
|
49
|
+
- lib/tinydtls/context.rb
|
|
50
|
+
- lib/tinydtls/security_conf.rb
|
|
51
|
+
- lib/tinydtls/session.rb
|
|
52
|
+
- lib/tinydtls/session_manager.rb
|
|
53
|
+
- lib/tinydtls/udpsocket.rb
|
|
54
|
+
- lib/tinydtls/wrapper.rb
|
|
55
|
+
homepage: https://github.com/ruby-dtls/rb-tinydtls
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata: {}
|
|
59
|
+
post_install_message:
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 2.2.0
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubyforge_project:
|
|
75
|
+
rubygems_version: 2.7.3
|
|
76
|
+
signing_key:
|
|
77
|
+
specification_version: 4
|
|
78
|
+
summary: It wraps the tinydtls library
|
|
79
|
+
test_files: []
|