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,98 @@
|
|
1
|
+
module CoRE
|
2
|
+
module CoAP
|
3
|
+
class Block
|
4
|
+
VALID_SIZE = [16, 32, 64, 128, 256, 512, 1024].freeze
|
5
|
+
MAX_NUM = (1048576 - 1).freeze
|
6
|
+
|
7
|
+
attr_reader :num, :more, :size
|
8
|
+
|
9
|
+
def initialize(*args)
|
10
|
+
if args.size == 1
|
11
|
+
@encoded = args.first.to_i
|
12
|
+
else
|
13
|
+
@decoded = []
|
14
|
+
self.num, self.more, self.size = args
|
15
|
+
@decoded = [self.num, self.more, self.size]
|
16
|
+
end
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def chunk(data)
|
22
|
+
data[@size * @num, @size]
|
23
|
+
end
|
24
|
+
|
25
|
+
def chunk_count(data)
|
26
|
+
return 0 if data.nil? || data.empty?
|
27
|
+
i = data.size % self.size == 0 ? 0 : 1
|
28
|
+
data.size / self.size + i
|
29
|
+
end
|
30
|
+
|
31
|
+
def chunkify(data)
|
32
|
+
Block.chunkify(data, self.size)
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode
|
36
|
+
if @encoded == 0
|
37
|
+
@decoded = [0, false, 16]
|
38
|
+
else
|
39
|
+
@decoded = [@encoded >> 4, (@encoded & 8) == 8, 16 << (@encoded & 7)]
|
40
|
+
end
|
41
|
+
|
42
|
+
self.num, self.more, self.size = @decoded
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def encode
|
48
|
+
@encoded = @num << 4 | (@more ? 1 : 0) << 3 | CoAP.number_of_bits_up_to(@size) - 4
|
49
|
+
end
|
50
|
+
|
51
|
+
def included_by?(body)
|
52
|
+
return true if self.num == 0 && (body.nil? || body.empty?)
|
53
|
+
self.num < chunk_count(body)
|
54
|
+
end
|
55
|
+
|
56
|
+
def last?(data)
|
57
|
+
return true if data.nil? || data.empty?
|
58
|
+
self.num == chunk_count(data) - 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def more=(v)
|
62
|
+
if @num > MAX_NUM
|
63
|
+
raise ArgumentError, 'num MUST be < 1048576'
|
64
|
+
end
|
65
|
+
|
66
|
+
@more = !!v
|
67
|
+
@decoded[1] = @more
|
68
|
+
end
|
69
|
+
|
70
|
+
def more?(data)
|
71
|
+
return false if data.nil? || data.empty?
|
72
|
+
data.bytesize > (self.num + 1) * self.size
|
73
|
+
end
|
74
|
+
|
75
|
+
def num=(v)
|
76
|
+
@num = v.to_i
|
77
|
+
@decoded[0] = @num
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_more!(body)
|
81
|
+
self.more = self.more?(body)
|
82
|
+
end
|
83
|
+
|
84
|
+
def size=(v)
|
85
|
+
unless VALID_SIZE.include?(v.to_i)
|
86
|
+
raise ArgumentError, 'size MUST be power of 2 between 16 and 1024.'
|
87
|
+
end
|
88
|
+
|
89
|
+
@size = v.to_i
|
90
|
+
@decoded[2] = @size
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.chunkify(data, size)
|
94
|
+
data.bytes.each_slice(size).map { |c| c.pack('C*') }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CoRE
|
4
|
+
module CoAP
|
5
|
+
# CoAP client library
|
6
|
+
class Client
|
7
|
+
attr_accessor :max_payload, :host, :port
|
8
|
+
|
9
|
+
# @param options Valid options are (all optional): max_payload
|
10
|
+
# (maximum payload size, default 256), max_retransmit
|
11
|
+
# (maximum retransmission count, default 4),
|
12
|
+
# recv_timeout (timeout for ACK responses, default: 2),
|
13
|
+
# host (destination host), post (destination port,
|
14
|
+
# default 5683).
|
15
|
+
def initialize(options = {})
|
16
|
+
@max_payload = options[:max_payload] || 256
|
17
|
+
|
18
|
+
@host = options[:host]
|
19
|
+
@port = options[:port] || CoAP::PORT
|
20
|
+
|
21
|
+
@options = options
|
22
|
+
|
23
|
+
@logger = CoAP.logger
|
24
|
+
end
|
25
|
+
|
26
|
+
# Enable DTLS socket.
|
27
|
+
def use_dtls
|
28
|
+
require 'CoDTLS'
|
29
|
+
@options[:socket] = CoDTLS::SecureSocket
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# GET
|
34
|
+
#
|
35
|
+
# @param path Path
|
36
|
+
# @param host Destination host
|
37
|
+
# @param port Destination port
|
38
|
+
# @param payload Payload
|
39
|
+
# @param options Options
|
40
|
+
#
|
41
|
+
# @return CoAP::Message
|
42
|
+
def get(*args)
|
43
|
+
client(:get, *args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# GET by URI
|
47
|
+
#
|
48
|
+
# @param uri URI
|
49
|
+
# @param payload Payload
|
50
|
+
# @param options Options
|
51
|
+
#
|
52
|
+
# @return CoAP::Message
|
53
|
+
def get_by_uri(uri, *args)
|
54
|
+
get(*decode_uri(uri), *args)
|
55
|
+
end
|
56
|
+
|
57
|
+
# POST
|
58
|
+
#
|
59
|
+
# @param host Destination host
|
60
|
+
# @param port Destination port
|
61
|
+
# @param path Path
|
62
|
+
# @param payload Payload
|
63
|
+
# @param options Options
|
64
|
+
#
|
65
|
+
# @return CoAP::Message
|
66
|
+
def post(*args)
|
67
|
+
client(:post, *args)
|
68
|
+
end
|
69
|
+
|
70
|
+
# POST by URI
|
71
|
+
#
|
72
|
+
# @param uri URI
|
73
|
+
# @param payload Payload
|
74
|
+
# @param options Options
|
75
|
+
#
|
76
|
+
# @return CoAP::Message
|
77
|
+
def post_by_uri(uri, *args)
|
78
|
+
post(*decode_uri(uri), *args)
|
79
|
+
end
|
80
|
+
|
81
|
+
# PUT
|
82
|
+
#
|
83
|
+
# @param host Destination host
|
84
|
+
# @param port Destination port
|
85
|
+
# @param path Path
|
86
|
+
# @param payload Payload
|
87
|
+
# @param options Options
|
88
|
+
#
|
89
|
+
# @return CoAP::Message
|
90
|
+
def put(*args)
|
91
|
+
client(:put, *args)
|
92
|
+
end
|
93
|
+
|
94
|
+
# PUT by URI
|
95
|
+
#
|
96
|
+
# @param uri URI
|
97
|
+
# @param payload Payload
|
98
|
+
# @param options Options
|
99
|
+
#
|
100
|
+
# @return CoAP::Message
|
101
|
+
def put_by_uri(uri, *args)
|
102
|
+
put(*decode_uri(uri), *args)
|
103
|
+
end
|
104
|
+
|
105
|
+
# DELETE
|
106
|
+
#
|
107
|
+
# @param host Destination host
|
108
|
+
# @param port Destination port
|
109
|
+
# @param path Path
|
110
|
+
# @param payload Payload
|
111
|
+
# @param options Options
|
112
|
+
#
|
113
|
+
# @return CoAP::Message
|
114
|
+
def delete(*args)
|
115
|
+
client(:delete, *args)
|
116
|
+
end
|
117
|
+
|
118
|
+
# DELETE by URI
|
119
|
+
#
|
120
|
+
# @param uri URI
|
121
|
+
# @param payload Payload
|
122
|
+
# @param options Options
|
123
|
+
#
|
124
|
+
# @return CoAP::Message
|
125
|
+
def delete_by_uri(uri, *args)
|
126
|
+
delete(*decode_uri(uri), *args)
|
127
|
+
end
|
128
|
+
|
129
|
+
# OBSERVE
|
130
|
+
#
|
131
|
+
# @param host Destination host
|
132
|
+
# @param port Destination port
|
133
|
+
# @param path Path
|
134
|
+
# @param callback Method to call with the observe data. Must provide
|
135
|
+
# arguments payload and socket.
|
136
|
+
# @param payload Payload
|
137
|
+
# @param options Options
|
138
|
+
#
|
139
|
+
# @return CoAP::Message
|
140
|
+
def observe(path, host, port, callback, payload = nil, options = {})
|
141
|
+
options[:observe] = 0
|
142
|
+
client(:get, path, host, port, payload, options, callback)
|
143
|
+
end
|
144
|
+
|
145
|
+
# OBSERVE by URI
|
146
|
+
#
|
147
|
+
# @param uri URI
|
148
|
+
# @param callback Method to call with the observe data. Must provide
|
149
|
+
# arguments payload and socket.
|
150
|
+
# @param payload Payload
|
151
|
+
# @param options Options
|
152
|
+
#
|
153
|
+
# @return CoAP::Message
|
154
|
+
def observe_by_uri(uri, *args)
|
155
|
+
observe(*decode_uri(uri), *args)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def client(method, path, host = nil, port = nil, payload = nil, options = {}, observe_callback = nil)
|
161
|
+
# Set host and port only one time on multiple requests
|
162
|
+
host.nil? ? (host = @host unless @host.nil?) : @host = host
|
163
|
+
port.nil? ? (port = @port unless @port.nil?) : @port = port
|
164
|
+
|
165
|
+
path, query = path.split('?')
|
166
|
+
|
167
|
+
validate_arguments!(host, port, path, payload)
|
168
|
+
|
169
|
+
szx = 2 ** CoAP.number_of_bits_up_to(@max_payload)
|
170
|
+
|
171
|
+
# Initialize block2 with payload size.
|
172
|
+
block2 = Block.new(0, false, szx)
|
173
|
+
|
174
|
+
# Initialize block1.
|
175
|
+
block1 = if options[:block1].nil?
|
176
|
+
Block.new(0, false, szx)
|
177
|
+
else
|
178
|
+
Block.new(options[:block1]).decode
|
179
|
+
end
|
180
|
+
|
181
|
+
# Initialize chunks if payload size > max_payload.
|
182
|
+
if !payload.nil? && payload.bytesize > @max_payload
|
183
|
+
chunks = Block.chunkify(payload, @max_payload)
|
184
|
+
else
|
185
|
+
chunks = [payload]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Create CoAP message struct.
|
189
|
+
message = initialize_message(method, path, query, payload)
|
190
|
+
message.mid = options.delete(:mid) if options[:mid]
|
191
|
+
|
192
|
+
# Set message type to non if chosen in global or local options.
|
193
|
+
if options.delete(:tt) == :non || @options.delete(:tt) == :non
|
194
|
+
message.tt = :non
|
195
|
+
end
|
196
|
+
|
197
|
+
# If more than 1 chunk, we need to use block1.
|
198
|
+
if !payload.nil? && chunks.size > 1
|
199
|
+
# Increase block number.
|
200
|
+
block1.num += 1 unless options[:block1].nil?
|
201
|
+
|
202
|
+
# More chunks?
|
203
|
+
if chunks.size > block1.num + 1
|
204
|
+
block1.more = true
|
205
|
+
message.options.delete(:block2)
|
206
|
+
else
|
207
|
+
block1.more = false
|
208
|
+
end
|
209
|
+
|
210
|
+
# Set final payload.
|
211
|
+
message.payload = chunks[block1.num]
|
212
|
+
|
213
|
+
# Set block1 message option.
|
214
|
+
message.options[:block1] = block1.encode
|
215
|
+
end
|
216
|
+
|
217
|
+
# Preserve user options.
|
218
|
+
message.options[:block2] = options[:block2] unless options[:block2] == nil
|
219
|
+
message.options[:observe] = options[:observe] unless options[:observe] == nil
|
220
|
+
message.options.merge!(options)
|
221
|
+
|
222
|
+
log_message(:sending_message, message)
|
223
|
+
|
224
|
+
# Wait for answer and retry sending message if timeout reached.
|
225
|
+
@transmission, recv_parsed = Transmission.request(message, host, port, @options)
|
226
|
+
|
227
|
+
log_message(:received_message, recv_parsed)
|
228
|
+
|
229
|
+
# Payload is not fully transmitted.
|
230
|
+
# TODO Get rid of nasty recursion.
|
231
|
+
if block1.more
|
232
|
+
return client(method, path, host, port, payload, message.options)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Test for more block2 payload.
|
236
|
+
block2 = Block.new(recv_parsed.options[:block2]).decode
|
237
|
+
|
238
|
+
if block2.more
|
239
|
+
block2.num += 1
|
240
|
+
|
241
|
+
options.delete(:block1) # end block1
|
242
|
+
options[:block2] = block2.encode
|
243
|
+
|
244
|
+
local_recv_parsed = client(method, path, host, port, nil, options)
|
245
|
+
|
246
|
+
unless local_recv_parsed.nil?
|
247
|
+
recv_parsed.payload << local_recv_parsed.payload
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Do we need to observe?
|
252
|
+
if recv_parsed.options[:observe]
|
253
|
+
CoAP::Observer.new.observe(recv_parsed, observe_callback, @transmission)
|
254
|
+
end
|
255
|
+
|
256
|
+
recv_parsed
|
257
|
+
end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
# Decode CoAP URIs.
|
262
|
+
def decode_uri(uri)
|
263
|
+
uri = CoAP.scheme_and_authority_decode(uri.to_s)
|
264
|
+
|
265
|
+
@logger.debug 'URI decoded: ' + uri.inspect
|
266
|
+
fail ArgumentError, 'Invalid URI' if uri.nil?
|
267
|
+
|
268
|
+
uri
|
269
|
+
end
|
270
|
+
|
271
|
+
def initialize_message(method, path, query = nil, payload = nil)
|
272
|
+
mid = SecureRandom.random_number(0xffff)
|
273
|
+
token = SecureRandom.random_number(0xff)
|
274
|
+
|
275
|
+
options = {
|
276
|
+
uri_path: CoAP.path_decode(path),
|
277
|
+
token: token
|
278
|
+
}
|
279
|
+
|
280
|
+
unless query.nil?
|
281
|
+
options[:uri_query] = CoAP.query_decode(query)
|
282
|
+
end
|
283
|
+
|
284
|
+
Message.new(:con, method, mid, payload, options)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Log message to debug log.
|
288
|
+
def log_message(text, message)
|
289
|
+
@logger.debug '### ' + text.to_s.upcase.gsub('_', ' ')
|
290
|
+
@logger.debug message.inspect
|
291
|
+
@logger.debug message.to_s.hexdump if $DEBUG
|
292
|
+
end
|
293
|
+
|
294
|
+
# Raise ArgumentError exceptions on wrong client method arguments.
|
295
|
+
def validate_arguments!(host, port, path, payload)
|
296
|
+
if host.nil? || host.empty?
|
297
|
+
fail ArgumentError, 'Argument «host» missing.'
|
298
|
+
end
|
299
|
+
|
300
|
+
if port.nil? || !port.is_a?(Integer)
|
301
|
+
fail ArgumentError, 'Argument «port» missing or not an Integer.'
|
302
|
+
end
|
303
|
+
|
304
|
+
if path.nil? || path.empty?
|
305
|
+
fail ArgumentError, 'Argument «path» missing.'
|
306
|
+
end
|
307
|
+
|
308
|
+
if !payload.nil? && (payload.empty? || !payload.is_a?(String))
|
309
|
+
fail ArgumentError, 'Argument «payload» must be a non-emtpy String'
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coapmessage.rb
|
2
|
+
# Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
|
3
|
+
|
4
|
+
module CoRE
|
5
|
+
module CoAP
|
6
|
+
extend Utility
|
7
|
+
|
8
|
+
include Coding
|
9
|
+
include Options
|
10
|
+
|
11
|
+
EMPTY = empty_buffer.freeze
|
12
|
+
|
13
|
+
TTYPES = [:con, :non, :ack, :rst]
|
14
|
+
TTYPES_I = invert_into_hash(TTYPES)
|
15
|
+
|
16
|
+
METHODS = [nil, :get, :post, :put, :delete]
|
17
|
+
METHODS_I = invert_into_hash(METHODS)
|
18
|
+
|
19
|
+
PORT = 5683
|
20
|
+
|
21
|
+
# Shortcut: CoRE::CoAP::parse == CoRE::CoAP::Message.parse
|
22
|
+
def self.parse(*args)
|
23
|
+
Message.parse(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# coapmessage.rb
|
2
|
+
# Copyright (C) 2010..2013 Carsten Bormann <cabo@tzi.org>
|
3
|
+
|
4
|
+
module CoRE
|
5
|
+
module CoAP
|
6
|
+
module Coding
|
7
|
+
BIN = Encoding::BINARY
|
8
|
+
UTF8 = Encoding::UTF_8
|
9
|
+
|
10
|
+
UNRESERVED = '\w\-\.~' # Alphanumerics, '_', '-', '.', '~'
|
11
|
+
SUB_DELIM = "!$&'()*+,;="
|
12
|
+
SUB_DELIM_NO_AMP = SUB_DELIM.delete('&')
|
13
|
+
|
14
|
+
PATH_UNENCODED = "#{UNRESERVED}#{SUB_DELIM}:@"
|
15
|
+
PATH_ENCODED_RE = /[^#{PATH_UNENCODED}]/mn
|
16
|
+
|
17
|
+
QUERY_UNENCODED = "#{UNRESERVED}#{SUB_DELIM_NO_AMP}:@/?"
|
18
|
+
QUERY_ENCODED_RE = /[^#{QUERY_UNENCODED}]/mn
|
19
|
+
|
20
|
+
# Also extend on include.
|
21
|
+
def self.included(base)
|
22
|
+
base.send :extend, Coding
|
23
|
+
end
|
24
|
+
|
25
|
+
# The variable-length binary (vlb) numbers defined in CoRE-CoAP Appendix A.
|
26
|
+
def vlb_encode(n)
|
27
|
+
n = Integer(n)
|
28
|
+
v = empty_buffer
|
29
|
+
|
30
|
+
if n < 0
|
31
|
+
raise ArgumentError, "Can't encode negative number #{n}."
|
32
|
+
end
|
33
|
+
|
34
|
+
while n > 0
|
35
|
+
v << (n & 0xFF)
|
36
|
+
n >>= 8
|
37
|
+
end
|
38
|
+
|
39
|
+
v.reverse!
|
40
|
+
end
|
41
|
+
|
42
|
+
def vlb_decode(s)
|
43
|
+
n = 0
|
44
|
+
s.each_byte { |b| n <<= 8; n += b }
|
45
|
+
n
|
46
|
+
end
|
47
|
+
|
48
|
+
# Byte strings lexicographically goedelized as numbers (one+256 coding)
|
49
|
+
def o256_encode(num)
|
50
|
+
str = empty_buffer
|
51
|
+
|
52
|
+
while num > 0
|
53
|
+
num -= 1
|
54
|
+
str << (num & 0xFF)
|
55
|
+
num >>= 8
|
56
|
+
end
|
57
|
+
|
58
|
+
str.reverse
|
59
|
+
end
|
60
|
+
|
61
|
+
def o256_decode(str)
|
62
|
+
num = 0
|
63
|
+
|
64
|
+
str.each_byte do |b|
|
65
|
+
num <<= 8
|
66
|
+
num += b + 1
|
67
|
+
end
|
68
|
+
|
69
|
+
num
|
70
|
+
end
|
71
|
+
|
72
|
+
# n must be 2**k
|
73
|
+
# Returns k
|
74
|
+
def number_of_bits_up_to(n)
|
75
|
+
# Math.frexp(n-1)[1]
|
76
|
+
Math.log2(n).floor
|
77
|
+
end
|
78
|
+
|
79
|
+
def scheme_and_authority_encode(host, port)
|
80
|
+
unless host =~ /\[.*\]/
|
81
|
+
host = "[#{host}]" if host =~ /:/
|
82
|
+
end
|
83
|
+
|
84
|
+
port = Integer(port)
|
85
|
+
|
86
|
+
scheme_and_authority = "coap://#{host}"
|
87
|
+
scheme_and_authority << ":#{port}" unless port == CoAP::PORT
|
88
|
+
scheme_and_authority
|
89
|
+
end
|
90
|
+
|
91
|
+
def scheme_and_authority_decode(s)
|
92
|
+
if s =~ %r{\A(?:coap://)((?:\[|%5B)([^\]]*)(?:\]|%5D)|([^:/]*))(:(\d+))?(/.*)?\z}i
|
93
|
+
host = $2 || $3 # Should check syntax...
|
94
|
+
port = $5 || CoAP::PORT
|
95
|
+
[$6, host, port.to_i]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def path_encode(uri_elements)
|
100
|
+
return '/' if uri_elements.nil? || uri_elements.empty?
|
101
|
+
'/' + uri_elements.map { |el| uri_encode_element(el, PATH_ENCODED_RE) }.join('/')
|
102
|
+
end
|
103
|
+
|
104
|
+
def query_encode(query_elements)
|
105
|
+
return '' if query_elements.nil? || query_elements.empty?
|
106
|
+
'?' + query_elements.map { |el| uri_encode_element(el, QUERY_ENCODED_RE) }.join('&')
|
107
|
+
end
|
108
|
+
|
109
|
+
def uri_encode_element(el, re)
|
110
|
+
el.dup.force_encoding(BIN).gsub(re) { |x| "%%%02X" % x.ord }
|
111
|
+
end
|
112
|
+
|
113
|
+
def percent_decode(el)
|
114
|
+
el.gsub(/%(..)/) { $1.to_i(16).chr(BIN) }.force_encoding(UTF8)
|
115
|
+
end
|
116
|
+
|
117
|
+
def path_decode(path)
|
118
|
+
# Needs -1 to avoid eating trailing slashes!
|
119
|
+
a = path.split('/', -1)
|
120
|
+
|
121
|
+
return a if a.empty?
|
122
|
+
|
123
|
+
if a[0] != ''
|
124
|
+
raise ArgumentError, "Path #{path.inspect} not starting with /"
|
125
|
+
end
|
126
|
+
|
127
|
+
# Special case for '/'
|
128
|
+
return [] if a[1] == ''
|
129
|
+
|
130
|
+
a[1..-1].map { |el| percent_decode(el) }
|
131
|
+
end
|
132
|
+
|
133
|
+
def query_decode(query)
|
134
|
+
return [] if query.empty?
|
135
|
+
|
136
|
+
query = query[1..-1] if query[0] == '?'
|
137
|
+
|
138
|
+
a = query.split('&', -1).map do |el|
|
139
|
+
el.gsub(/%(..)/) { $1.to_i(16).chr(BIN) }.force_encoding(UTF8)
|
140
|
+
end
|
141
|
+
|
142
|
+
a
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|