coap 0.0.16 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +13 -2
  4. data/Gemfile +0 -1
  5. data/LICENSE +2 -2
  6. data/README.md +37 -33
  7. data/Rakefile +12 -3
  8. data/bin/coap +111 -0
  9. data/coap.gemspec +34 -29
  10. data/lib/coap.rb +3 -34
  11. data/lib/core.rb +11 -0
  12. data/lib/core/coap.rb +42 -0
  13. data/lib/core/coap/block.rb +98 -0
  14. data/lib/core/coap/client.rb +314 -0
  15. data/lib/core/coap/coap.rb +26 -0
  16. data/lib/core/coap/coding.rb +146 -0
  17. data/lib/core/coap/fsm.rb +82 -0
  18. data/lib/core/coap/message.rb +203 -0
  19. data/lib/core/coap/observer.rb +40 -0
  20. data/lib/core/coap/options.rb +44 -0
  21. data/lib/core/coap/registry.rb +32 -0
  22. data/lib/core/coap/registry/content_formats.yml +7 -0
  23. data/lib/core/coap/resolver.rb +17 -0
  24. data/lib/core/coap/transmission.rb +165 -0
  25. data/lib/core/coap/types.rb +69 -0
  26. data/lib/core/coap/utility.rb +34 -0
  27. data/lib/core/coap/version.rb +5 -0
  28. data/lib/core/core_ext/socket.rb +19 -0
  29. data/lib/core/hexdump.rb +18 -0
  30. data/lib/core/link.rb +97 -0
  31. data/lib/core/os.rb +15 -0
  32. data/spec/block_spec.rb +160 -0
  33. data/spec/client_spec.rb +86 -0
  34. data/spec/fixtures/coap.me.link +1 -0
  35. data/spec/link_spec.rb +98 -0
  36. data/spec/registry_spec.rb +39 -0
  37. data/spec/resolver_spec.rb +19 -0
  38. data/spec/spec_helper.rb +17 -0
  39. data/spec/transmission_spec.rb +70 -0
  40. data/test/helper.rb +15 -0
  41. data/test/test_client.rb +99 -228
  42. data/test/test_message.rb +99 -71
  43. metadata +140 -37
  44. data/bin/client +0 -42
  45. data/lib/coap/block.rb +0 -45
  46. data/lib/coap/client.rb +0 -364
  47. data/lib/coap/coap.rb +0 -273
  48. data/lib/coap/message.rb +0 -187
  49. data/lib/coap/mysocket.rb +0 -81
  50. data/lib/coap/observer.rb +0 -41
  51. data/lib/coap/version.rb +0 -3
  52. data/lib/misc/hexdump.rb +0 -17
  53. data/test/coap_test_helper.rb +0 -2
  54. data/test/disabled_econotag_blck.rb +0 -33
data/lib/coap/coap.rb DELETED
@@ -1,273 +0,0 @@
1
- # coapmessage.rb
2
- # Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
3
-
4
- module CoAP
5
- BIN = Encoding::BINARY
6
- UTF8 = Encoding::UTF_8
7
-
8
- class << self
9
- def empty_buffer
10
- ''.encode(BIN)
11
- end
12
-
13
- def invert_into_hash(a)
14
- a.each_with_index.each_with_object({}) { |k, h| h[k[0]] = k[1] if k[0] }
15
- end
16
-
17
- # Arrays that describe a specific option type:
18
- # [default value, length range, repeatable?, decoder, encoder]
19
-
20
- # we only care about presence or absence, always empty
21
- def presence_once
22
- [false, (0..0), false,
23
- ->(a) { true },
24
- ->(v) { v ? [''] : [] }
25
- ]
26
- end
27
-
28
- # token-style, goedelized in o256 here
29
- def o256_once(min, max, default = nil)
30
- [default, (min..max), false,
31
- ->(a) { o256_decode(a[0]) },
32
- ->(v) { v == default ? [] : [o256_encode(v)] }
33
- ]
34
- end
35
-
36
- def o256_many(min, max) # unused
37
- [nil, (min..max), true,
38
- ->(a) { a.map { |x| o256_decode(x) } },
39
- ->(v) { Array(v).map { |x| o256_encode(x) } }
40
- ]
41
- end
42
-
43
- # vlb as in core-coap Annex A
44
- def uint_once(min, max, default = nil)
45
- [default, (min..max), false,
46
- ->(a) { vlb_decode(a[0]) },
47
- ->(v) { v == default ? [] : [vlb_encode(v)] }
48
- ]
49
- end
50
-
51
- def uint_many(min, max)
52
- [nil, (min..max), true,
53
- ->(a) { a.map { |x| vlb_decode(x) } },
54
- ->(v) { Array(v).map { |x| vlb_encode(x) } }
55
- ]
56
- end
57
-
58
- # Any other opaque byte sequence
59
- def opaq_once(min, max, default = nil)
60
- [default, (min..max), false,
61
- ->(a) { a[0] },
62
- ->(v) { v == default ? [] : Array(v) }
63
- ]
64
- end
65
-
66
- def opaq_many(min, max)
67
- [nil, (min..max), true,
68
- ->(a) { a },
69
- ->(v) { Array(v) }
70
- ]
71
- end
72
-
73
- # same, but interpreted as UTF-8
74
- def str_once(min, max, default = nil)
75
- [default, (min..max), false,
76
- ->(a) { a[0].force_encoding('utf-8') }, # XXX needed?
77
- ->(v) { v == default ? [] : Array(v) }
78
- ]
79
- end
80
-
81
- def str_many(min, max)
82
- [nil, (min..max), true,
83
- ->(a) { a.map { |s| s.force_encoding('utf-8') } }, # XXX needed?
84
- ->(v) { Array(v) }
85
- ]
86
- end
87
- end
88
-
89
- EMPTY = empty_buffer.freeze
90
-
91
- TTYPES = [:con, :non, :ack, :rst]
92
- TTYPES_I = invert_into_hash(TTYPES)
93
- METHODS = [nil, :get, :post, :put, :delete]
94
- METHODS_I = invert_into_hash(METHODS)
95
-
96
- # for now, keep 19
97
- # TOKEN_ON = -1 # handled specially
98
- TOKEN_ON = 19
99
-
100
- # 14 => :user, default, length range, replicable?, decoder, encoder
101
- OPTIONS = { # name minlength, maxlength, [default] defined where:
102
- 1 => [:if_match, *o256_many(0, 8)], # core-coap-12
103
- 3 => [:uri_host, *str_once(1, 255)], # core-coap-12
104
- 4 => [:etag, *o256_many(1, 8)], # core-coap-12 !! once in rp
105
- 5 => [:if_none_match, *presence_once], # core-coap-12
106
- 6 => [:observe, *uint_once(0, 3)], # core-observe-07
107
- 7 => [:uri_port, *uint_once(0, 2)], # core-coap-12
108
- 8 => [:location_path, *str_many(0, 255)], # core-coap-12
109
- 11 => [:uri_path, *str_many(0, 255)], # core-coap-12
110
- 12 => [:content_format, *uint_once(0, 2)], # core-coap-12
111
- 14 => [:max_age, *uint_once(0, 4, 60)], # core-coap-12
112
- 15 => [:uri_query, *str_many(0, 255)], # core-coap-12
113
- 17 => [:accept, *uint_once(0, 2)], # core-coap-18!
114
- TOKEN_ON => [:token, *o256_once(1, 8, 0)], # core-coap-12 -> opaq_once(1, 8, EMPTY)
115
- 20 => [:location_query, *str_many(0, 255)], # core-coap-12
116
- 23 => [:block2, *uint_once(0, 3)], # core-block-10
117
- 27 => [:block1, *uint_once(0, 3)], # core-block-10
118
- 28 => [:size2, *uint_once(0, 4)], # core-block-10
119
- 35 => [:proxy_uri, *str_once(1, 1034)], # core-coap-12
120
- 39 => [:proxy_scheme, *str_once(1, 255)], # core-coap-13
121
- 60 => [:size1, *uint_once(0, 4)], # core-block-10
122
- }
123
- # :user => 14, :user, def, range, rep, deco, enco
124
- OPTIONS_I = Hash[OPTIONS.map { |k, v| [v[0], [k, *v]] }]
125
- DEFAULTING_OPTIONS = Hash[OPTIONS.map { |k, v| [v[0].freeze, v[1].freeze] }
126
- .select { |k, v| v }].freeze
127
-
128
- class << self
129
- def critical?(option)
130
- oi = OPTIONS_I[option] # this is really an option name symbol
131
- if oi
132
- option = oi[0] # -> to option number
133
- end
134
- option.odd?
135
- end
136
-
137
- def unsafe?(option)
138
- oi = OPTIONS_I[option] # this is really an option name symbol
139
- if oi
140
- option = oi[0] # -> to option number
141
- end
142
- option & 2 == 2
143
- end
144
-
145
- def no_cache_key?(option)
146
- oi = OPTIONS_I[option] # this is really an option name symbol
147
- if oi
148
- option = oi[0] # -> to option number
149
- end
150
- option & 0x1e == 0x1c
151
- end
152
-
153
- # The variable-length binary (vlb) numbers defined in CoRE-CoAP Appendix A.
154
- def vlb_encode(n)
155
- # on = n
156
- n = Integer(n)
157
- fail ArgumentError, "Can't encode negative number #{n}" if n < 0
158
- v = empty_buffer
159
- while n > 0
160
- v << (n & 0xFF)
161
- n >>= 8
162
- end
163
- v.reverse!
164
- # warn "Encoded #{on} as #{v.inspect}"
165
- v
166
- end
167
-
168
- def vlb_decode(s)
169
- n = 0
170
- s.each_byte do |b|
171
- n <<= 8
172
- n += b
173
- end
174
- n
175
- end
176
-
177
- # byte strings lexicographically goedelized as numbers (one+256 coding)
178
- def o256_encode(num)
179
- str = empty_buffer
180
- while num > 0
181
- num -= 1
182
- str << (num & 0xFF)
183
- num >>= 8
184
- end
185
- str.reverse
186
- end
187
-
188
- def o256_decode(str)
189
- num = 0
190
- str.each_byte do |b|
191
- num <<= 8
192
- num += b + 1
193
- end
194
- num
195
- end
196
-
197
- # n must be 2**k
198
- # returns k
199
- def number_of_bits_up_to(n)
200
- Math.frexp(n - 1)[1]
201
- end
202
-
203
- def scheme_and_authority_encode(host, port)
204
- unless host =~ /\[.*\]/
205
- host = "[#{host}]" if host =~ /:/
206
- end
207
- scheme_and_authority = "coap://#{host}"
208
- port = Integer(port)
209
- scheme_and_authority << ":#{port}" unless port == 5683
210
- scheme_and_authority
211
- end
212
-
213
- def scheme_and_authority_decode(s)
214
- if s =~ %r{\A(?:coap://)((?:\[|%5B)([^\]]*)(?:\]|%5D)|([^:/]*))(:(\d+))?(/.*)?\z}i
215
- host = Regexp.last_match[2] || Regexp.last_match[3] # should check syntax...
216
- port = Regexp.last_match[5] || 5683
217
- [host, port.to_i, Regexp.last_match[6]]
218
- end
219
- end
220
-
221
- UNRESERVED = 'A-Za-z0-9\\-\\._~' # ALPHA / DIGIT / "-" / "." / "_" / "~"
222
- SUB_DELIM = "!$&'()*+,;=" # "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
223
- PATH_UNENCODED = "#{UNRESERVED}#{SUB_DELIM}:@"
224
- PATH_ENCODED_RE = /[^#{PATH_UNENCODED}]/mn
225
- def path_encode(uri_elements)
226
- '/' << uri_elements.map do |el|
227
- el.dup.force_encoding(BIN).gsub(PATH_ENCODED_RE) { |x| '%%%02X' % x.ord }
228
- end.join('/')
229
- end
230
-
231
- SUB_DELIM_NO_AMP = SUB_DELIM.gsub('&', '')
232
- QUERY_UNENCODED = "#{UNRESERVED}#{SUB_DELIM_NO_AMP}:@/?"
233
- QUERY_ENCODED_RE = /[^#{QUERY_UNENCODED}]/mn
234
- def query_encode(query_elements)
235
- if query_elements.empty?
236
- ''
237
- else
238
- '?' << query_elements.map do |el|
239
- el.dup.force_encoding(BIN).gsub(QUERY_ENCODED_RE) { |x| '%%%02X' % x.ord }
240
- end.join('&')
241
- end
242
- end
243
-
244
- def percent_decode(el)
245
- el.gsub(/%(..)/) { Regexp.last_match[1].to_i(16).chr(BIN) }.force_encoding(UTF8)
246
- end
247
-
248
- def path_decode(path)
249
- a = path.split('/', -1) # needs -1 to avoid eating trailing slashes!
250
- return a if a.empty?
251
- fail ArgumentError, 'path #{path.inspect} did not start with /' unless a[0] == ''
252
- return [] if a[1] == '' # special case for "/"
253
- a[1..-1].map do |el|
254
- percent_decode(el)
255
- end
256
- end
257
-
258
- def query_decode(query)
259
- return [] if query.empty?
260
- fail ArgumentError, 'query #{query.inspect} did not start with ?' unless query[0] == '?'
261
- a = query.split('&', -1).map do |el|
262
- el.gsub(/%(..)/) { Regexp.last_match[1].to_i(16).chr(BIN) }.force_encoding(UTF8)
263
- end
264
- a[0] = a[0][1..-1] # remove "?"
265
- a
266
- end
267
-
268
- # Shortcut: CoRE::CoAP::parse == CoRE::CoAP::Message.parse
269
- def parse(*args)
270
- Message.parse(*args)
271
- end
272
- end
273
- end
data/lib/coap/message.rb DELETED
@@ -1,187 +0,0 @@
1
- # coapmessage.rb
2
- # Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
3
-
4
- module CoAP
5
- class Message < Struct.new(:ver, :tt, :mcode, :mid, :options, :payload)
6
- def initialize(*args) # convenience: .new(tt?, mcode?, mid?, payload?, hash)
7
- if args.size < 6
8
- h = args.pop.dup
9
- tt = h.delete(:tt) || args.shift
10
- mcode = h.delete(:mcode) || args.shift
11
- case mcode
12
- when Integer then mcode = METHODS[mcode] || [mcode >> 5, mcode & 0x1f]
13
- when Float then mcode = [mcode.to_i, (mcode * 100 % 100).round] # accept 2.05 and such
14
- end
15
- mid = h.delete(:mid) || args.shift
16
- payload = h.delete(:payload) || args.shift || EMPTY # no payload = empty payload
17
- fail 'CoRE::CoAPMessage.new: hash or all args' unless args.empty?
18
- super(1, tt, mcode, mid, h, payload)
19
- else
20
- super
21
- end
22
- end
23
-
24
- def mcode_readable
25
- case mcode
26
- when Array
27
- "#{mcode[0]}.#{"%02d" % mcode[1]}"
28
- else
29
- mcode.to_s
30
- end
31
- end
32
-
33
- def self.deklausify(d, dpos, len)
34
- case len
35
- when 0..12
36
- [len, dpos]
37
- when 13
38
- [d.getbyte(dpos) + 13, dpos += 1]
39
- when 14
40
- [d.byteslice(dpos, 2).unpack('n')[0] + 269, dpos += 2]
41
- else
42
- fail "[#{d.inspect}] Bad delta/length nibble #{len} at #{dpos}"
43
- end
44
- end
45
-
46
- def self.parse(d)
47
- # dpos keeps our current position in parsing d
48
- b1, mcode, mid = d.unpack('CCn'); dpos = 4
49
- toklen = b1 & 0xf
50
- token = d.byteslice(dpos, toklen); dpos += toklen
51
- b1 >>= 4
52
- tt = TTYPES[b1 & 0x3]
53
- b1 >>= 2
54
- fail ArgumentError, "unknown CoAP version #{b1}" unless b1 == 1
55
- mcode = METHODS[mcode] || [mcode >> 5, mcode & 0x1F]
56
-
57
- # collect options
58
- onumber = 0 # current option number
59
- options = Hash.new { |h, k| h[k] = [] }
60
- dlen = d.bytesize
61
- while dpos < dlen
62
- tl1 = d.getbyte(dpos); dpos += 1
63
- fail ArgumentError, "option is not there at #{dpos} with oc #{orig_numopt}" unless tl1 # XXX
64
-
65
- break if tl1 == 0xff
66
-
67
- odelta, dpos = deklausify(d, dpos, tl1 >> 4)
68
- olen, dpos = deklausify(d, dpos, tl1 & 0xF)
69
-
70
- onumber += odelta
71
-
72
- if dpos + olen > dlen
73
- fail ArgumentError, "#{olen}-byte option at #{dpos} -- not enough data in #{dlen} total"
74
- end
75
-
76
- oval = d.byteslice(dpos, olen); dpos += olen
77
- options[onumber] << oval
78
- end
79
-
80
- options[TOKEN_ON] = [token] if token != ''
81
-
82
- # d.bytesize = more than all the rest...
83
- decode_options_and_put_together(b1, tt, mcode, mid, options,
84
- d.byteslice(dpos, d.bytesize))
85
- end
86
-
87
- def self.decode_options_and_put_together(b1, tt, mcode, mid, options, payload)
88
- # check and decode option values
89
- decoded_options = DEFAULTING_OPTIONS.dup
90
- options.each_pair do |k, v|
91
- if oinfo = OPTIONS[k]
92
- oname, _, minmax, repeatable, decoder, _ = *oinfo
93
- repeatable or v.size <= 1 or
94
- fail ArgumentError, "repeated unrepeatable option #{oname}"
95
- v.each do |v1|
96
- unless minmax === v1.bytesize
97
- fail ArgumentError, "#{v1.inspect} out of #{minmax} for #{oname}"
98
- end
99
- end
100
- decoded_options[oname] = decoder.call(v)
101
- else
102
- decoded_options[k] = v # we don't know what that is -- keep it in raw
103
- end
104
- end
105
-
106
- new(b1, tt, mcode, mid, Hash[decoded_options], payload) # XXX: why Hash[] again?
107
- end
108
-
109
- def prepare_options
110
- prepared_options = {}
111
- options.each do |k, v|
112
- # puts "k = #{k.inspect}, oinfo_i = #{OPTIONS_I[k].inspect}"
113
- if oinfo_i = OPTIONS_I[k]
114
- onum, oname, defv, minmax, rep, _, encoder = *oinfo_i
115
- prepared_options[onum] = a = encoder.call(v)
116
- rep or a.size <= 1 or fail "repeated option #{oname} #{a.inspect}"
117
- a.each do |v1|
118
- unless minmax === v1.bytesize
119
- fail ArgumentError, "#{v1.inspect} out of #{minmax} for #{oname}"
120
- end
121
- end
122
- else
123
- fail ArgumentError, "#{k.inspect}: unknown option" unless Integer === k
124
- prepared_options[k] = Array(v) # store raw option
125
- end
126
- end
127
- prepared_options
128
- end
129
-
130
- def klausify(n)
131
- if n < 13
132
- [n, '']
133
- else
134
- n -= 13
135
- if n < 256
136
- [13, [n].pack('C')]
137
- else
138
- [14, [n - 256].pack('n')]
139
- end
140
- end
141
- end
142
-
143
- def to_wire
144
- # check and encode option values
145
- prepared_options = prepare_options
146
- # puts "prepared_options: #{prepared_options}"
147
-
148
- token = (prepared_options.delete(TOKEN_ON) || [nil])[0] || ''
149
- # puts "TOKEN: #{token.inspect}" unless token
150
-
151
- b1 = 0x40 | TTYPES_I[tt] << 4 | token.bytesize
152
- b2 = METHODS_I[mcode] || (mcode[0] << 5) + mcode[1]
153
- result = [b1, b2, mid].pack('CCn')
154
- result << token
155
-
156
- # stuff options in packet
157
- onumber = 0
158
- num_encoded_options = 0
159
-
160
- prepared_options.keys.sort.each do |k|
161
- fail "Bad Option Type #{k.inspect}" unless Integer === k && k >= 0
162
- a = prepared_options[k]
163
- a.each do |v|
164
- # result << frob(k, v)
165
- odelta = k - onumber
166
- onumber = k
167
-
168
- odelta1, odelta2 = klausify(odelta)
169
- odelta1 <<= 4
170
-
171
- length1, length2 = klausify(v.bytesize)
172
- result << [odelta1 | length1].pack('C')
173
- result << odelta2
174
- result << length2
175
- result << v.dup.force_encoding(BIN) # value
176
- end
177
-
178
- end
179
-
180
- if payload != ''
181
- result << 0xFF
182
- result << payload.dup.force_encoding(BIN)
183
- end
184
- result
185
- end
186
- end
187
- end