coap 0.0.16 → 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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +13 -2
- data/Gemfile +0 -1
- data/LICENSE +2 -2
- data/README.md +37 -33
- data/Rakefile +12 -3
- data/bin/coap +111 -0
- data/coap.gemspec +34 -29
- data/lib/coap.rb +3 -34
- data/lib/core.rb +11 -0
- data/lib/core/coap.rb +42 -0
- data/lib/core/coap/block.rb +98 -0
- data/lib/core/coap/client.rb +314 -0
- data/lib/core/coap/coap.rb +26 -0
- data/lib/core/coap/coding.rb +146 -0
- data/lib/core/coap/fsm.rb +82 -0
- data/lib/core/coap/message.rb +203 -0
- data/lib/core/coap/observer.rb +40 -0
- data/lib/core/coap/options.rb +44 -0
- data/lib/core/coap/registry.rb +32 -0
- data/lib/core/coap/registry/content_formats.yml +7 -0
- data/lib/core/coap/resolver.rb +17 -0
- data/lib/core/coap/transmission.rb +165 -0
- data/lib/core/coap/types.rb +69 -0
- data/lib/core/coap/utility.rb +34 -0
- data/lib/core/coap/version.rb +5 -0
- data/lib/core/core_ext/socket.rb +19 -0
- data/lib/core/hexdump.rb +18 -0
- data/lib/core/link.rb +97 -0
- data/lib/core/os.rb +15 -0
- data/spec/block_spec.rb +160 -0
- data/spec/client_spec.rb +86 -0
- data/spec/fixtures/coap.me.link +1 -0
- data/spec/link_spec.rb +98 -0
- data/spec/registry_spec.rb +39 -0
- data/spec/resolver_spec.rb +19 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/transmission_spec.rb +70 -0
- data/test/helper.rb +15 -0
- data/test/test_client.rb +99 -228
- data/test/test_message.rb +99 -71
- metadata +140 -37
- data/bin/client +0 -42
- data/lib/coap/block.rb +0 -45
- data/lib/coap/client.rb +0 -364
- data/lib/coap/coap.rb +0 -273
- data/lib/coap/message.rb +0 -187
- data/lib/coap/mysocket.rb +0 -81
- data/lib/coap/observer.rb +0 -41
- data/lib/coap/version.rb +0 -3
- data/lib/misc/hexdump.rb +0 -17
- data/test/coap_test_helper.rb +0 -2
- data/test/disabled_econotag_blck.rb +0 -33
@@ -0,0 +1,82 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
class FSM
|
4
|
+
# CoAP Message Layer FSM
|
5
|
+
# https://tools.ietf.org/html/draft-ietf-lwig-coap-01#section-2.5.2
|
6
|
+
class Message
|
7
|
+
include Celluloid::FSM
|
8
|
+
|
9
|
+
default_state :closed
|
10
|
+
|
11
|
+
# Receive CON
|
12
|
+
# Send ACK (accept) -> :closed
|
13
|
+
state :ack_pending, to: [:closed]
|
14
|
+
|
15
|
+
# Sending and receiving
|
16
|
+
# Send NON (unreliable_send)
|
17
|
+
# Receive NON
|
18
|
+
# Receive ACK
|
19
|
+
# Receive CON -> :ack_pending
|
20
|
+
# Send CON (reliable_send) -> :reliable_tx
|
21
|
+
state :closed, to: [:reliable_tx, :ack_pending]
|
22
|
+
|
23
|
+
# Send CON
|
24
|
+
# Retransmit until
|
25
|
+
# Failure
|
26
|
+
# Timeout (fail) -> :closed
|
27
|
+
# Receive matching RST (fail) -> :closed
|
28
|
+
# Cancel (cancel) -> :closed
|
29
|
+
# Success
|
30
|
+
# Receive matching ACK, NON (rx) -> :closed
|
31
|
+
# Receive matching CON (rx) -> :ack_pending
|
32
|
+
state :reliable_tx, to: [:closed, :ack_pending]
|
33
|
+
end
|
34
|
+
|
35
|
+
# CoAP Client Request/Response Layer FSM
|
36
|
+
# https://tools.ietf.org/html/draft-ietf-lwig-coap-01#section-2.5.1
|
37
|
+
class Client
|
38
|
+
include Celluloid::FSM
|
39
|
+
|
40
|
+
default_state :idle
|
41
|
+
|
42
|
+
# Idle
|
43
|
+
# Outgoing request ((un)reliable_send) -> :waiting
|
44
|
+
state :idle, to: [:waiting]
|
45
|
+
|
46
|
+
# Waiting for response
|
47
|
+
# Response received (accept, rx) -> :idle
|
48
|
+
# Failure (fail) -> :idle
|
49
|
+
# Cancel (cancel) -> :idle
|
50
|
+
state :waiting, to: [:idle]
|
51
|
+
end
|
52
|
+
|
53
|
+
# CoAP Server Request/Response Layer FSM
|
54
|
+
# https://tools.ietf.org/html/draft-ietf-lwig-coap-01#section-2.5.1
|
55
|
+
class Server
|
56
|
+
include Celluloid::FSM
|
57
|
+
|
58
|
+
default_state :idle
|
59
|
+
|
60
|
+
# Idle
|
61
|
+
# On NON (rx) -> :separate
|
62
|
+
# On CON (rx) -> :serving
|
63
|
+
state :idle, to: [:separate, :serving]
|
64
|
+
|
65
|
+
# Separate
|
66
|
+
# Respond ((un)reliable_send) -> :idle
|
67
|
+
state :separate, to: [:idle]
|
68
|
+
|
69
|
+
# Serving
|
70
|
+
# Respond (accept) -> :idle
|
71
|
+
# Empty ACK (accept) -> :separate
|
72
|
+
state :serving, to: [:idle, :separate]
|
73
|
+
end
|
74
|
+
|
75
|
+
attr_reader :fsm
|
76
|
+
|
77
|
+
def initialize
|
78
|
+
@fsm = FSM::Message.new
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# coapmessage.rb
|
2
|
+
# Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
|
3
|
+
|
4
|
+
module CoRE
|
5
|
+
module CoAP
|
6
|
+
class Message < Struct.new(:ver, :tt, :mcode, :mid, :options, :payload)
|
7
|
+
def initialize(*args) # convenience: .new(tt?, mcode?, mid?, payload?, hash?)
|
8
|
+
if args.size < 6
|
9
|
+
h = {}
|
10
|
+
h = args.pop.dup if args.last.is_a? Hash
|
11
|
+
|
12
|
+
tt = h.delete(:tt) || args.shift
|
13
|
+
|
14
|
+
mcode = h.delete(:mcode) || args.shift
|
15
|
+
|
16
|
+
# TODO Allow later setting of mcode by Float.
|
17
|
+
case mcode
|
18
|
+
when Integer
|
19
|
+
mcode = METHODS[mcode] || [mcode >> 5, mcode & 0x1f]
|
20
|
+
when Float
|
21
|
+
mcode = [mcode.to_i, (mcode * 100 % 100).round] # Accept 2.05 and such
|
22
|
+
end
|
23
|
+
|
24
|
+
mid = h.delete(:mid) || args.shift
|
25
|
+
|
26
|
+
payload = h.delete(:payload) || args.shift || EMPTY
|
27
|
+
|
28
|
+
unless args.empty?
|
29
|
+
raise 'CoRE::CoAP::Message.new: Either specify Hash or all arguments.'
|
30
|
+
end
|
31
|
+
|
32
|
+
super(1, tt, mcode, mid, h, payload)
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def klausify(n)
|
39
|
+
if n < 13
|
40
|
+
[n, '']
|
41
|
+
else
|
42
|
+
n -= 13
|
43
|
+
if n < 256
|
44
|
+
[13, [n].pack("C")]
|
45
|
+
else
|
46
|
+
[14, [n-256].pack("n")]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def mcode_readable
|
52
|
+
return "#{mcode[0]}.#{"%02d" % mcode[1]}" if mcode.is_a? Array
|
53
|
+
mcode.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def prepare_options
|
57
|
+
prepared_options = {}
|
58
|
+
options.each do |k, v|
|
59
|
+
if oinfo_i = CoAP::OPTIONS_I[k]
|
60
|
+
onum, oname, defv, minmax, rep, _, encoder = *oinfo_i
|
61
|
+
prepared_options[onum] = a = encoder.call(v)
|
62
|
+
rep or a.size <= 1 or raise "repeated option #{oname} #{a.inspect}"
|
63
|
+
a.each do |v1|
|
64
|
+
unless minmax === v1.bytesize
|
65
|
+
raise ArgumentError, "#{v1.inspect} out of #{minmax} for #{oname}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
raise ArgumentError, "#{k.inspect}: unknown option" unless Integer === k
|
70
|
+
prepared_options[k] = Array(v) # store raw option
|
71
|
+
end
|
72
|
+
end
|
73
|
+
prepared_options
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
path = CoAP.path_encode(self.options[:uri_path])
|
78
|
+
query = CoAP.query_encode(self.options[:uri_query])
|
79
|
+
|
80
|
+
[mcode_readable, path, query].join(' ')
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_wire
|
84
|
+
# check and encode option values
|
85
|
+
prepared_options = prepare_options
|
86
|
+
# puts "prepared_options: #{prepared_options}"
|
87
|
+
|
88
|
+
token = (prepared_options.delete(CoAP::TOKEN_ON) || [nil])[0] || ''
|
89
|
+
puts "TOKEN: #{token.inspect}" unless token
|
90
|
+
|
91
|
+
b1 = 0x40 | TTYPES_I[tt] << 4 | token.bytesize
|
92
|
+
b2 = METHODS_I[mcode] || (mcode[0] << 5) + mcode[1]
|
93
|
+
result = [b1, b2, mid].pack("CCn")
|
94
|
+
result << token
|
95
|
+
|
96
|
+
# stuff options in packet
|
97
|
+
onumber = 0
|
98
|
+
num_encoded_options = 0
|
99
|
+
prepared_options.keys.sort.each do |k|
|
100
|
+
raise "Bad Option Type #{k.inspect}" unless Integer === k && k >= 0
|
101
|
+
a = prepared_options[k]
|
102
|
+
a.each do |v|
|
103
|
+
# result << frob(k, v)
|
104
|
+
odelta = k - onumber
|
105
|
+
onumber = k
|
106
|
+
|
107
|
+
odelta1, odelta2 = klausify(odelta)
|
108
|
+
odelta1 <<= 4
|
109
|
+
|
110
|
+
length1, length2 = klausify(v.bytesize)
|
111
|
+
result << [odelta1 | length1].pack("C")
|
112
|
+
result << odelta2
|
113
|
+
result << length2
|
114
|
+
result << v.dup.force_encoding(CoAP::BIN) # value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if payload != '' && !payload.nil?
|
119
|
+
result << 0xFF
|
120
|
+
result << payload.dup.force_encoding(CoAP::BIN)
|
121
|
+
end
|
122
|
+
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.decode_options_and_put_together(b1, tt, mcode, mid, options, payload)
|
127
|
+
# check and decode option values
|
128
|
+
decoded_options = CoAP::DEFAULTING_OPTIONS.dup
|
129
|
+
options.each_pair do |k, v|
|
130
|
+
if oinfo = CoAP::OPTIONS[k]
|
131
|
+
oname, _, minmax, repeatable, decoder, _ = *oinfo
|
132
|
+
repeatable or v.size <= 1 or
|
133
|
+
raise ArgumentError, "repeated unrepeatable option #{oname}"
|
134
|
+
v.each do |v1|
|
135
|
+
unless minmax === v1.bytesize
|
136
|
+
raise ArgumentError, "#{v1.inspect} out of #{minmax} for #{oname}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
decoded_options[oname] = decoder.call(v)
|
140
|
+
else
|
141
|
+
decoded_options[k] = v # we don't know what that is -- keep it in raw
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
new(b1, tt, mcode, mid, Hash[decoded_options], payload) # XXX: why Hash[] again?
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.deklausify(d, dpos, len)
|
149
|
+
case len
|
150
|
+
when 0..12
|
151
|
+
[len, dpos]
|
152
|
+
when 13
|
153
|
+
[d.getbyte(dpos) + 13, dpos += 1]
|
154
|
+
when 14
|
155
|
+
[d.byteslice(dpos, 2).unpack("n")[0] + 269, dpos += 2]
|
156
|
+
else
|
157
|
+
raise "[#{d.inspect}] Bad delta/length nibble #{len} at #{dpos}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.parse(d)
|
162
|
+
# dpos keeps our current position in parsing d
|
163
|
+
b1, mcode, mid = d.unpack("CCn"); dpos = 4
|
164
|
+
toklen = b1 & 0xf
|
165
|
+
token = d.byteslice(dpos, toklen); dpos += toklen
|
166
|
+
b1 >>= 4
|
167
|
+
tt = TTYPES[b1 & 0x3]
|
168
|
+
b1 >>= 2
|
169
|
+
raise ArgumentError, "unknown CoAP version #{b1}" unless b1 == 1
|
170
|
+
mcode = METHODS[mcode] || [mcode>>5, mcode&0x1F]
|
171
|
+
|
172
|
+
# collect options
|
173
|
+
onumber = 0 # current option number
|
174
|
+
options = Hash.new { |h, k| h[k] = [] }
|
175
|
+
dlen = d.bytesize
|
176
|
+
while dpos < dlen
|
177
|
+
tl1 = d.getbyte(dpos); dpos += 1
|
178
|
+
raise ArgumentError, "option is not there at #{dpos} with oc #{orig_numopt}" unless tl1 # XXX
|
179
|
+
|
180
|
+
break if tl1 == 0xff
|
181
|
+
|
182
|
+
odelta, dpos = deklausify(d, dpos, tl1 >> 4)
|
183
|
+
olen, dpos = deklausify(d, dpos, tl1 & 0xF)
|
184
|
+
|
185
|
+
onumber += odelta
|
186
|
+
|
187
|
+
if dpos + olen > dlen
|
188
|
+
raise ArgumentError, "#{olen}-byte option at #{dpos} -- not enough data in #{dlen} total"
|
189
|
+
end
|
190
|
+
|
191
|
+
oval = d.byteslice(dpos, olen); dpos += olen
|
192
|
+
options[onumber] << oval
|
193
|
+
end
|
194
|
+
|
195
|
+
options[CoAP::TOKEN_ON] = [token] if token != ''
|
196
|
+
|
197
|
+
# d.bytesize = more than all the rest...
|
198
|
+
decode_options_and_put_together(b1, tt, mcode, mid, options,
|
199
|
+
d.byteslice(dpos, d.bytesize))
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
class Observer
|
4
|
+
MAX_OBSERVE_OPTION_VALUE = 8_388_608
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@logger = CoAP.logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def observe(message, callback, socket)
|
11
|
+
n = message.options[:observe]
|
12
|
+
|
13
|
+
callback.call(socket, message)
|
14
|
+
|
15
|
+
# This does not seem to be able to cope with concurrency.
|
16
|
+
loop do
|
17
|
+
answer = socket.receive(timeout: 0)
|
18
|
+
|
19
|
+
next unless answer.options[:observe]
|
20
|
+
|
21
|
+
if update?(n, answer.options[:observe])
|
22
|
+
callback.call(socket, answer)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def update?(old, new)
|
30
|
+
if new > old
|
31
|
+
new - old < MAX_OBSERVE_OPTION_VALUE
|
32
|
+
elsif new < old
|
33
|
+
old - new > MAX_OBSERVE_OPTION_VALUE
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
module Options
|
4
|
+
extend Types
|
5
|
+
|
6
|
+
TOKEN_ON = 19
|
7
|
+
|
8
|
+
# 14 => :user, default, length range, replicable?, decoder, encoder
|
9
|
+
OPTIONS = { # name minlength, maxlength, [default] defined where:
|
10
|
+
1 => [:if_match, *o256_many(0, 8)], # core-coap-12
|
11
|
+
3 => [:uri_host, *str_once(1, 255)], # core-coap-12
|
12
|
+
4 => [:etag, *o256_many(1, 8)], # core-coap-12 !! once in rp
|
13
|
+
5 => [:if_none_match, *presence_once], # core-coap-12
|
14
|
+
6 => [:observe, *uint_once(0, 3)], # core-observe-07
|
15
|
+
7 => [:uri_port, *uint_once(0, 2)], # core-coap-12
|
16
|
+
8 => [:location_path, *str_many(0, 255)], # core-coap-12
|
17
|
+
11 => [:uri_path, *str_many(0, 255)], # core-coap-12
|
18
|
+
12 => [:content_format, *uint_once(0, 2)], # core-coap-12
|
19
|
+
14 => [:max_age, *uint_once(0, 4, 60)], # core-coap-12
|
20
|
+
15 => [:uri_query, *str_many(0, 255)], # core-coap-12
|
21
|
+
17 => [:accept, *uint_once(0, 2)], # core-coap-18!
|
22
|
+
TOKEN_ON => [:token, *o256_once(1, 8, 0)], # core-coap-12 -> opaq_once(1, 8, EMPTY)
|
23
|
+
20 => [:location_query, *str_many(0, 255)], # core-coap-12
|
24
|
+
23 => [:block2, *uint_once(0, 3)], # core-block-10
|
25
|
+
27 => [:block1, *uint_once(0, 3)], # core-block-10
|
26
|
+
28 => [:size2, *uint_once(0, 4)], # core-block-10
|
27
|
+
35 => [:proxy_uri, *str_once(1, 1034)], # core-coap-12
|
28
|
+
39 => [:proxy_scheme, *str_once(1, 255)], # core-coap-13
|
29
|
+
60 => [:size1, *uint_once(0, 4)], # core-block-10
|
30
|
+
}
|
31
|
+
|
32
|
+
# :user => 14, :user, def, range, rep, deco, enco
|
33
|
+
OPTIONS_I =
|
34
|
+
Hash[OPTIONS.map { |k, v| [v[0], [k, *v]] }]
|
35
|
+
|
36
|
+
DEFAULTING_OPTIONS =
|
37
|
+
Hash[
|
38
|
+
OPTIONS
|
39
|
+
.map { |k, v| [v[0].freeze, v[1].freeze] }
|
40
|
+
.select { |k, v| v }
|
41
|
+
].freeze
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
module Registry
|
4
|
+
REGISTRY_PATH = File.join(File.dirname(__FILE__), 'registry').freeze
|
5
|
+
|
6
|
+
def self.load_yaml(registry)
|
7
|
+
registry = "#{registry}.yml"
|
8
|
+
YAML.load_file(File.join(REGISTRY_PATH, registry))
|
9
|
+
end
|
10
|
+
|
11
|
+
CONTENT_FORMATS = load_yaml(:content_formats).freeze
|
12
|
+
CONTENT_FORMATS_INVERTED = CONTENT_FORMATS.invert.freeze
|
13
|
+
|
14
|
+
def self.convert_content_format(cf)
|
15
|
+
if cf.is_a? String
|
16
|
+
CONTENT_FORMATS_INVERTED[cf] || without_charset(cf)
|
17
|
+
else
|
18
|
+
CONTENT_FORMATS[cf]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def self.without_charset(cf)
|
25
|
+
cf = cf.split(';').first
|
26
|
+
CONTENT_FORMATS_INVERTED.select { |k| k =~ /^#{cf}/ }.first[1]
|
27
|
+
rescue NoMethodError
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
class Resolver
|
4
|
+
def self.address(host)
|
5
|
+
a = if ENV['IPv4'].nil?
|
6
|
+
IPv6FavorResolv.getaddress(host).to_s
|
7
|
+
else
|
8
|
+
Resolv.getaddress(host).to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
raise Resolv::ResolvError if a.empty?
|
12
|
+
|
13
|
+
a
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
# Socket abstraction.
|
4
|
+
class Transmission
|
5
|
+
DEFAULT_RECV_TIMEOUT = 2
|
6
|
+
|
7
|
+
attr_accessor :max_retransmit, :recv_timeout
|
8
|
+
attr_reader :address_family, :socket
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
@max_retransmit = options[:max_retransmit] || 4
|
12
|
+
@recv_timeout = options[:recv_timeout] || DEFAULT_RECV_TIMEOUT
|
13
|
+
@socket = options[:socket]
|
14
|
+
|
15
|
+
@retransmit = if options[:retransmit].nil?
|
16
|
+
true
|
17
|
+
else
|
18
|
+
!!options[:retransmit]
|
19
|
+
end
|
20
|
+
|
21
|
+
if @socket
|
22
|
+
@socket_class = @socket.class
|
23
|
+
@address_family = @socket.addr.first
|
24
|
+
else
|
25
|
+
@socket_class = options[:socket_class] || Celluloid::IO::UDPSocket
|
26
|
+
@address_family = options[:address_family] || Socket::AF_INET6
|
27
|
+
@socket = @socket_class.new(@address_family)
|
28
|
+
end
|
29
|
+
|
30
|
+
# http://lists.apple.com/archives/darwin-kernel/2014/Mar/msg00012.html
|
31
|
+
if OS.osx? && ipv6?
|
32
|
+
ifname = Socket.if_up?('en1') ? 'en1' : 'en0'
|
33
|
+
ifindex = Socket.if_nametoindex(ifname)
|
34
|
+
|
35
|
+
s = @socket.to_io rescue @socket
|
36
|
+
s.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF, [ifindex].pack('i_'))
|
37
|
+
end
|
38
|
+
|
39
|
+
@socket
|
40
|
+
end
|
41
|
+
|
42
|
+
def ipv6?
|
43
|
+
@address_family == Socket::AF_INET6
|
44
|
+
end
|
45
|
+
|
46
|
+
# Receive from socket and return parsed CoAP message. (ACK is sent on CON
|
47
|
+
# messages.)
|
48
|
+
def receive(options = {})
|
49
|
+
retry_count = options[:retry_count] || 0
|
50
|
+
timeout = (options[:timeout] || @recv_timeout) ** (retry_count + 1)
|
51
|
+
|
52
|
+
mid = options[:mid]
|
53
|
+
flags = mid.nil? ? 0 : Socket::MSG_PEEK
|
54
|
+
|
55
|
+
data = Timeout.timeout(timeout) do
|
56
|
+
@socket.recvfrom(1152, flags)
|
57
|
+
end
|
58
|
+
|
59
|
+
answer = CoAP.parse(data[0].force_encoding('BINARY'))
|
60
|
+
|
61
|
+
if mid == answer.mid
|
62
|
+
Timeout.timeout(1) { @socket.recvfrom(1152) }
|
63
|
+
end
|
64
|
+
|
65
|
+
return if answer.nil?
|
66
|
+
|
67
|
+
if answer.tt == :con
|
68
|
+
message = Message.new(:ack, 0, answer.mid, nil,
|
69
|
+
{token: answer.options[:token]})
|
70
|
+
|
71
|
+
send(message, data[1][3])
|
72
|
+
end
|
73
|
+
|
74
|
+
answer
|
75
|
+
end
|
76
|
+
|
77
|
+
# Send +message+ (retransmit if necessary) and wait for answer. Returns
|
78
|
+
# answer.
|
79
|
+
def request(message, host, port = CoAP::PORT)
|
80
|
+
retry_count = 0
|
81
|
+
retransmit = @retransmit && message.tt == :con
|
82
|
+
|
83
|
+
begin
|
84
|
+
send(message, host, port)
|
85
|
+
response = receive(retry_count: retry_count, mid: message.mid)
|
86
|
+
rescue Timeout::Error
|
87
|
+
raise unless retransmit
|
88
|
+
|
89
|
+
retry_count += 1
|
90
|
+
|
91
|
+
if retry_count > @max_retransmit
|
92
|
+
raise "Maximum retransmission count of #{@max_retransmit} reached."
|
93
|
+
end
|
94
|
+
|
95
|
+
retry unless message.tt == :non
|
96
|
+
end
|
97
|
+
|
98
|
+
if seperate?(response)
|
99
|
+
response = receive(timeout: 10, mid: message.mid)
|
100
|
+
end
|
101
|
+
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
# Send +message+.
|
106
|
+
def send(message, host, port = CoAP::PORT)
|
107
|
+
message = message.to_wire if message.respond_to?(:to_wire)
|
108
|
+
|
109
|
+
# In MRI and Rubinius, the Socket::MSG_DONTWAIT option is 64.
|
110
|
+
# It is not defined by JRuby.
|
111
|
+
# TODO Is it really necessary?
|
112
|
+
@socket.send(message, 64, host, port)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Check if answer is seperated.
|
118
|
+
def seperate?(response)
|
119
|
+
r = response
|
120
|
+
r.tt == :ack && r.payload.empty? && r.mcode == [0, 0]
|
121
|
+
end
|
122
|
+
|
123
|
+
class << self
|
124
|
+
# Return Transmission instance with socket matching address family.
|
125
|
+
def from_host(host, options = {})
|
126
|
+
if IPAddr.new(host).ipv6?
|
127
|
+
new(options)
|
128
|
+
else
|
129
|
+
new(options.merge(address_family: Socket::AF_INET))
|
130
|
+
end
|
131
|
+
# MRI throws IPAddr::InvalidAddressError, JRuby an ArgumentError
|
132
|
+
rescue ArgumentError
|
133
|
+
host = Resolver.address(host)
|
134
|
+
retry
|
135
|
+
end
|
136
|
+
|
137
|
+
# Instanciate matching Transmission and send message.
|
138
|
+
def send(*args)
|
139
|
+
invoke(:send, *args)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Instanciate matching Transmission and perform request.
|
143
|
+
def request(*args)
|
144
|
+
invoke(:request, *args)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
# Instanciate matching Transmission and invoke +method+ on instance.
|
150
|
+
def invoke(method, *args)
|
151
|
+
options = {}
|
152
|
+
options = args.pop if args.last.is_a? Hash
|
153
|
+
|
154
|
+
if options[:socket]
|
155
|
+
transmission = Transmission.new(options)
|
156
|
+
else
|
157
|
+
transmission = from_host(args[1], options)
|
158
|
+
end
|
159
|
+
|
160
|
+
[transmission, transmission.__send__(method, *args)]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|