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.
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
@@ -0,0 +1,69 @@
1
+ module CoRE
2
+ module CoAP
3
+ module Types
4
+ # Arrays that describe a specific option type:
5
+ # [default value, length range, repeatable?, decoder, encoder]
6
+
7
+ # We only care about presence or absence, always empty.
8
+ def presence_once
9
+ [false, (0..0), false, ->(a) { true }, ->(v) { v ? [''] : []}]
10
+ end
11
+
12
+ # Token-style, goedelized in o256.
13
+ def o256_once(min, max, default = nil)
14
+ [default, (min..max), false,
15
+ ->(a) { CoAP.o256_decode(a[0]) },
16
+ ->(v) { v == default ? [] : [CoAP.o256_encode(v)] }
17
+ ]
18
+ end
19
+
20
+ # Unused, for now.
21
+ def o256_many(min, max)
22
+ [nil, (min..max), true,
23
+ ->(a) { a.map{ |x| CoAP.o256_decode(x)} },
24
+ ->(v) { Array(v).map{ |x| CoAP.o256_encode(x)} }
25
+ ]
26
+ end
27
+
28
+ # vlb as in core-coap Annex A
29
+ def uint_once(min, max, default = nil)
30
+ [default, (min..max), false,
31
+ ->(a) { CoAP.vlb_decode(a[0]) },
32
+ ->(v) { v == default ? [] : [CoAP.vlb_encode(v)] }
33
+ ]
34
+ end
35
+
36
+ def uint_many(min, max)
37
+ [nil, (min..max), true,
38
+ ->(a) { a.map{ |x| CoAP.vlb_decode(x)} },
39
+ ->(v) { Array(v).map{ |x| CoAP.vlb_encode(x)} }
40
+ ]
41
+ end
42
+
43
+ # Any other opaque byte sequence (once).
44
+ def opaq_once(min, max, default = nil)
45
+ [default, (min..max), false,
46
+ ->(a) { a[0] },
47
+ ->(v) { v == default ? [] : Array(v) }
48
+ ]
49
+ end
50
+
51
+ # Any other opaque byte sequence (many).
52
+ def opaq_many(min, max)
53
+ [nil, (min..max), true, ->(a) { a }, ->(v) { Array(v) }]
54
+ end
55
+
56
+ # Same, but interpreted as UTF-8
57
+ def str_once(min, max, default = nil)
58
+ [default, (min..max), false,
59
+ ->(a) { a[0] },
60
+ ->(v) { v == default ? [] : Array(v) }
61
+ ]
62
+ end
63
+
64
+ def str_many(min, max)
65
+ [nil, (min..max), true, ->(a) { a }, ->(v) { Array(v) }]
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,34 @@
1
+ module CoRE
2
+ module CoAP
3
+ module Utility
4
+ def empty_buffer
5
+ String.new # Was: ''.encode(BIN)
6
+ end
7
+
8
+ def invert_into_hash(a)
9
+ a.each_with_index.each_with_object({}) { |k, h| h[k[0]] = k[1] if k[0] }
10
+ end
11
+
12
+ def critical?(option)
13
+ if oi = CoAP::OPTIONS_I[option] # this is really an option name symbol
14
+ option = oi[0] # -> to option number
15
+ end
16
+ option.odd?
17
+ end
18
+
19
+ def unsafe?(option)
20
+ if oi = CoAP::OPTIONS_I[option] # this is really an option name symbol
21
+ option = oi[0] # -> to option number
22
+ end
23
+ option & 2 == 2
24
+ end
25
+
26
+ def no_cache_key?(option)
27
+ if oi = CoAP::OPTIONS_I[option] # this is really an option name symbol
28
+ option = oi[0] # -> to option number
29
+ end
30
+ option & 0x1e == 0x1c
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module CoRE
2
+ module CoAP
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ class Socket
2
+ def self.if_nametoindex(name)
3
+ ifaddr_by_name(name).ifindex
4
+ rescue NoMethodError
5
+ 0
6
+ end
7
+
8
+ def self.if_up?(name)
9
+ ifaddr_by_name(name).flags & Socket::IFF_UP == 1
10
+ rescue NoMethodError
11
+ false
12
+ end
13
+
14
+ private
15
+
16
+ def self.ifaddr_by_name(name)
17
+ Socket.getifaddrs.select { |x| x.name == name }.first
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # hexdump.rb
2
+ # Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
3
+
4
+ class String
5
+ def hexdump(prefix = '', prepend_newline = true)
6
+ a, i = [], 0
7
+ a << '' if prepend_newline
8
+ while i < length
9
+ slice = self.byteslice(i, 16)
10
+ a << '%s%-48s |%-16s|' %
11
+ [prefix,
12
+ slice.bytes.map { |b| '%02x' % b.ord }.join(' '),
13
+ slice.gsub(/[^ -~]/mn, ".")]
14
+ i += 16
15
+ end
16
+ a.join("\n")
17
+ end
18
+ end
data/lib/core/link.rb ADDED
@@ -0,0 +1,97 @@
1
+ # encoding: utf-8
2
+
3
+ module CoRE
4
+ # Implements CoRE Link Format (RFC6690)
5
+ # http://tools.ietf.org/html/rfc6690
6
+ # TODO Handle repeated attributes
7
+ class Link
8
+ VALID_ATTRS = [
9
+ :anchor, :ct, :exp, :hreflang, :if, :ins, :media, :obs, :rt, :rel, :rev,
10
+ :sz, :title, :type
11
+ ]
12
+
13
+ attr_accessor :uri
14
+ attr_reader :attrs
15
+
16
+ def initialize(uri, attrs = {})
17
+ @uri = uri || raise(ArgumentError.new('URI can not be unset.'))
18
+ @attrs = attrs
19
+
20
+ validate_attrs!(@attrs)
21
+ end
22
+
23
+ def merge!(hash)
24
+ a = @attrs.merge(hash)
25
+ validate_attrs!(a)
26
+ @attrs = a
27
+ end
28
+
29
+ def method_missing(symbol, *args)
30
+ attr = symbol.to_s.delete('=').to_sym
31
+
32
+ if !VALID_ATTRS.include?(attr)
33
+ raise ArgumentError.new("Invalid attribute «#{attr}».")
34
+ end
35
+
36
+ if symbol[-1] == '='
37
+ @attrs[attr] = args.first.to_s
38
+ else
39
+ @attrs[attr]
40
+ end
41
+ end
42
+
43
+ def to_s
44
+ s = "<#{@uri}>"
45
+
46
+ @attrs.sort.each do |attr, value|
47
+ s += ";#{attr}=\"#{value}\""
48
+ end
49
+
50
+ s
51
+ end
52
+
53
+ def self.parse(data)
54
+ parts = data.split(';')
55
+
56
+ uri = parts.shift
57
+ uri = uri.match(/\A\<(.+)\>\z/)
58
+
59
+ if uri.nil?
60
+ raise ArgumentError.new("Invalid URI in '#{data}'.")
61
+ end
62
+
63
+ link = Link.new(uri[1])
64
+
65
+ parts.each do |part|
66
+ attr, value = part.split('=')
67
+
68
+ attr = (attr + '=').to_sym
69
+ value = value.delete('"') unless value.nil?
70
+
71
+ link.send(attr, value)
72
+ end
73
+
74
+ link
75
+ end
76
+
77
+ # TODO Handle misplaced commas
78
+ def self.parse_multiple(data)
79
+ results = []
80
+
81
+ data.split(',').each do |part|
82
+ results << self.parse(part)
83
+ end
84
+
85
+ results
86
+ end
87
+
88
+ private
89
+
90
+ def validate_attrs!(attrs)
91
+ if (VALID_ATTRS | attrs.keys).size > VALID_ATTRS.size
92
+ invalid = (VALID_ATTRS | attrs.keys) - VALID_ATTRS
93
+ raise ArgumentError.new("Invalid attributes: #{invalid.join(', ')}.")
94
+ end
95
+ end
96
+ end
97
+ end
data/lib/core/os.rb ADDED
@@ -0,0 +1,15 @@
1
+ module CoRE
2
+ module OS
3
+ def self.linux?
4
+ @@linux ||= !!(os =~ /^linux/)
5
+ end
6
+
7
+ def self.os
8
+ RbConfig::CONFIG['host_os']
9
+ end
10
+
11
+ def self.osx?
12
+ @@osx ||= !!(os =~ /^darwin/)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+ require 'benchmark'
3
+
4
+ describe Block do
5
+ subject { Block.new(0, false, 16) }
6
+ let(:data1) { '+' * 42 }
7
+ let(:data2) { '+' * 32 }
8
+
9
+ describe '#chunk' do
10
+ it 'should return chunks' do
11
+ a = [0, 1, 2, 3].map do |i|
12
+ subject.num = i
13
+ subject.chunk(data1)
14
+ end
15
+
16
+ expect(a).to eq(['+' * 16, '+' * 16, '+' * 10, nil])
17
+ end
18
+ end
19
+
20
+ describe '#chunk_count' do
21
+ it 'should return correct count' do
22
+ expect(subject.chunk_count(data1)).to eq(3)
23
+ expect(subject.chunk_count(data2)).to eq(2)
24
+ end
25
+ end
26
+
27
+ describe '#included_by?' do
28
+ it 'should return true if body empty and index 0' do
29
+ expect(subject.included_by?('')).to be(true)
30
+ expect(subject.included_by?(nil)).to be(true)
31
+ end
32
+
33
+ it 'should return false if body empty and index > 0' do
34
+ subject.num = 1
35
+ expect(subject.included_by?('')).to be(false)
36
+ expect(subject.included_by?(nil)).to be(false)
37
+ end
38
+
39
+ it 'should return true if chunk index exists' do
40
+ [0, 1, 2].each do |num|
41
+ subject.num = num
42
+ expect(subject.included_by?(data1)).to be(true)
43
+ end
44
+
45
+ [0, 1].each do |num|
46
+ subject.num = num
47
+ expect(subject.included_by?(data2)).to be(true)
48
+ end
49
+ end
50
+
51
+ it 'should return false if chunk index does not exists' do
52
+ [3, 4, 5].each do |num|
53
+ subject.num = num
54
+ expect(subject.included_by?(data1)).to be(false)
55
+ end
56
+
57
+ [2, 3].each do |num|
58
+ subject.num = num
59
+ expect(subject.included_by?(data2)).to be(false)
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#last?' do
65
+ it 'should return false unless last chunk' do
66
+ [0, 1, 3].each do |num|
67
+ subject.num = num
68
+ expect(subject.last?(data1)).to be(false)
69
+ end
70
+
71
+ [0, 2, 3].each do |num|
72
+ subject.num = num
73
+ expect(subject.last?(data2)).to be(false)
74
+ end
75
+
76
+ [0, 1, 2].each do |num|
77
+ subject.num = num
78
+ expect(subject.last?('')).to be(true)
79
+ expect(subject.last?(nil)).to be(true)
80
+ end
81
+ end
82
+
83
+ it 'should return true if last chunk' do
84
+ subject.num = 2
85
+ expect(subject.last?(data1)).to be(true)
86
+
87
+ subject.num = 1
88
+ expect(subject.last?(data2)).to be(true)
89
+ end
90
+ end
91
+
92
+ describe '#more?' do
93
+ it 'should return true unless last chunk or bigger' do
94
+ [0, 1].each do |num|
95
+ subject.num = num
96
+ expect(subject.more?(data1)).to be(true)
97
+ end
98
+
99
+ [2, 3].each do |num|
100
+ subject.num = num
101
+ expect(subject.more?(data1)).to be(false)
102
+ end
103
+
104
+ [0].each do |num|
105
+ subject.num = num
106
+ expect(subject.more?(data2)).to be(true)
107
+ end
108
+
109
+ [1, 2].each do |num|
110
+ subject.num = num
111
+ expect(subject.more?(data2)).to be(false)
112
+ end
113
+
114
+ [0, 1, 2].each do |num|
115
+ subject.num = num
116
+ expect(subject.more?('')).to be(false)
117
+ expect(subject.more?(nil)).to be(false)
118
+ end
119
+ end
120
+ end
121
+
122
+ describe '#encode' do
123
+ it 'should work with examples' do
124
+ expect(subject.encode).to eq(0)
125
+
126
+ block = Block.new(0, true, 16)
127
+ expect(block.encode).to eq(8)
128
+
129
+ (1..6).each do |i|
130
+ block = Block.new(0, false, 2**(i+4))
131
+ expect(block.encode).to eq(i)
132
+ end
133
+ end
134
+ end
135
+
136
+ describe '#encode and #decode' do
137
+ it 'should be reversible (encode -> decode)' do
138
+ num = rand(Block::MAX_NUM + 1)
139
+ more = [true, false].sample
140
+ size = Block::VALID_SIZE.sample
141
+
142
+ a = Block.new(num, more, size).encode
143
+ b = Block.new(a).decode
144
+
145
+ expect(b.num).to eq(num)
146
+ expect(b.more).to eq(more)
147
+ expect(b.size).to eq(size)
148
+ end
149
+
150
+ it 'should be reversible (decode -> encode)' do
151
+ i = 7
152
+ i = rand(2**24) until (i & 7) != 7
153
+
154
+ a = Block.new(i).decode
155
+ b = Block.new(a.num, a.more, a.size).encode
156
+
157
+ expect(b).to eq(i)
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Client do
6
+ subject { Client.new }
7
+
8
+ describe '#get' do
9
+ describe 'with separated answer' do
10
+ it 'should return correct mcode and payload' do
11
+ answer = subject.get('/separate', 'coap.me')
12
+ expect(answer.mcode).to eq([2, 5])
13
+ expect(answer.payload).to eq('That took a long time')
14
+ end
15
+ end
16
+ end
17
+
18
+ describe 'modifying methods' do
19
+ subject { Client.new(max_payload: 512) }
20
+ let(:payload) { Faker::Lorem.paragraphs(5).join("\n") }
21
+ let(:payload_utf8) { '♥' + payload }
22
+
23
+ describe '#post' do
24
+ describe 'with block1 option' do
25
+ describe 'creating resource' do
26
+ it 'should work with ASCII payload' do
27
+ answer = subject.post('/large-create', 'coap.me', nil, payload)
28
+ expect(answer.mcode).to eq([2, 1])
29
+
30
+ answer = subject.get('/large-create', 'coap.me')
31
+ expect(answer.mcode).to eq([2, 5])
32
+ expect(answer.payload).to eq(payload)
33
+ end
34
+
35
+ it 'should work with UTF8 payload' do
36
+ answer = subject.post('/large-create', 'coap.me', nil, payload_utf8)
37
+ expect(answer.mcode).to eq([2, 1])
38
+
39
+ answer = subject.get('/large-create', 'coap.me')
40
+ expect(answer.mcode).to eq([2, 5])
41
+ expect(answer.payload.force_encoding('utf-8')).to eq(payload_utf8)
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#put' do
48
+ describe 'with block1 option' do
49
+ describe 'updating resource' do
50
+ it 'should work with ASCII payload' do
51
+ answer = subject.put('/large-update', 'coap.me', nil, payload)
52
+ expect(answer.mcode).to eq([2, 4])
53
+
54
+ answer = subject.get('/large-update', 'coap.me')
55
+ expect(answer.mcode).to eq([2, 5])
56
+ expect(answer.payload).to eq(payload)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#observe' do
64
+ before do
65
+ @answers = []
66
+
67
+ @t1 = Thread.start do
68
+ subject.observe \
69
+ '/obs', 'vs0.inf.ethz.ch', nil,
70
+ ->(s, m) { @answers << m }
71
+ end
72
+
73
+ Timeout.timeout(12) do
74
+ sleep 0.25 while !(@answers.size > 2)
75
+ end
76
+ end
77
+
78
+ it 'should receive updates' do
79
+ expect(@answers.size).to be > 2
80
+ end
81
+
82
+ after do
83
+ @t1.kill
84
+ end
85
+ end
86
+ end