├── .gitignore
├── .travis.yml
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── bin
└── client
├── coap.gemspec
├── lib
├── coap.rb
├── coap
│ ├── block.rb
│ ├── client.rb
│ ├── coap.rb
│ ├── message.rb
│ ├── mysocket.rb
│ ├── observer.rb
│ └── version.rb
└── misc
│ └── hexdump.rb
└── test
├── coap_test_helper.rb
├── disabled_econotag_blck.rb
├── test_client.rb
└── test_message.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | /.config
4 | /coverage/
5 | /InstalledFiles
6 | /pkg/
7 | /spec/reports/
8 | /test/tmp/
9 | /test/version_tmp/
10 | /tmp/
11 |
12 | ## Specific to RubyMotion:
13 | .dat*
14 | .repl_history
15 | build/
16 |
17 | ## Documentation cache and generated files:
18 | /.yardoc/
19 | /_yardoc/
20 | /doc/
21 | /rdoc/
22 |
23 | ## Environment normalisation:
24 | /.bundle/
25 | /lib/bundler/man/
26 |
27 | # for a library or gem, you might want to ignore these files since the code is
28 | # intended to run in multiple environments; otherwise, check them in:
29 | # Gemfile.lock
30 | # .ruby-version
31 | # .ruby-gemset
32 |
33 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34 | .rvmrc
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.1.0
4 | script:
5 | - "bundle exec rake build"
6 | - "bundle exec rake test"
7 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in coap.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Simon Frerichs, Carsten Bormann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://badge.fury.io/rb/coap)
2 | [](https://gemnasium.com/SmallLars/coap)
3 | [](https://travis-ci.org/SmallLars/coap)
4 | [](https://coveralls.io/r/SmallLars/coap)
5 | [](https://codeclimate.com/github/SmallLars/coap)
6 |
7 | # Gem CoAP
8 |
9 | Ruby Gem for RFC 7252 - Constrained Application Protocol (CoAP)
10 | Based on code by Carsten Bormann
11 |
12 | The Constrained Application Protocol (CoAP) is a specialized web
13 | transfer protocol for use with constrained nodes and constrained
14 | (e.g., low-power, lossy) networks. The nodes often have 8-bit
15 | microcontrollers with small amounts of ROM and RAM, while constrained
16 | networks such as 6LoWPAN often have high packet error rates and a
17 | typical throughput of 10s of kbit/s. The protocol is designed for
18 | machine-to-machine (M2M) applications such as smart energy and
19 | building automation.
20 |
21 |
22 | Implemented Version: RFC (http://tools.ietf.org/html/rfc7252)
23 |
24 | Additionally implemented:
25 |
26 | - Blockwise Transfer http://tools.ietf.org/html/draft-ietf-core-block-14
27 | - Observe http://tools.ietf.org/html/draft-ietf-core-observe-13
28 |
29 | ## Install
30 | Add this line to your application's Gemfile:
31 |
32 | gem 'coap'
33 |
34 | And then execute:
35 |
36 | $ bundle
37 |
38 | Or install it yourself as:
39 |
40 | $ gem install coap
41 |
42 | ## Usage
43 |
44 | ### In your ruby/rails application
45 |
46 |
47 | client = CoAP::Client.new
48 | answer = client.get('coap.me', 5683, '/hello')
49 | p answer.payload
50 |
51 | Lot's of examples are stored in test/test_client.rb
52 | Code is commented in rdoc format
53 |
54 | ### Command Line Client (very limited)
55 |
56 | ./client get coap://coap.me:5683/.well-known/core
57 |
58 | ## Testing
59 |
60 | rake test
61 |
62 | ## Copyright
63 | Copyright (C) 2014 Simon Frerichs. See LICENSE.txt for further details.
64 | Based on code by Carsten Bormann
65 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require 'rake/testtask'
3 |
4 | Rake::TestTask.new do |t|
5 | t.libs << 'test'
6 | end
7 |
8 | desc "Run tests"
9 | task :default => :test
10 |
--------------------------------------------------------------------------------
/bin/client:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Usage: client get coap://coap.me:5683/.well-known/core
3 | # Usage: client observe coap://vs0.inf.ethz.ch:5683/obs
4 |
5 | $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
6 |
7 | require 'coap'
8 |
9 | def observe_callback(data, socket)
10 | pp socket.inspect if $DEBUG
11 | pp data.inspect if $DEBUG
12 | end
13 |
14 | PAYLOAD = 'CLILorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nuncLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nuncCLI'
15 |
16 | METHODS = %w(get post put delete observe)
17 |
18 | if ARGV.size >= 2 && METHODS.include?(ARGV[0])
19 | client = CoAP::Client.new
20 | $DEBUG = true
21 | $WIREDUMP = true
22 | require 'pp'
23 |
24 | case ARGV[0]
25 | when 'get'
26 | pp client.get_by_url(ARGV[1])
27 |
28 | when 'post'
29 | pp client.post_by_url(ARGV[1], PAYLOAD)
30 |
31 | when 'put'
32 | pp client.put_by_url(ARGV[1], PAYLOAD)
33 |
34 | when 'delete'
35 | pp client.delete_by_url(ARGV[1])
36 |
37 | when 'observe'
38 | pp client.observe_by_url(ARGV[1], {}, method(:observe_callback))
39 |
40 | end
41 | end
42 | exit
43 |
--------------------------------------------------------------------------------
/coap.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'coap/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "coap"
8 | spec.version = Coap::VERSION
9 | spec.authors = ["Carsten Bormann","Simon Frerichs"]
10 | spec.email = ["morpheus@morphhome.net"]
11 | spec.summary = %q{Ruby Gem for RFC 7252 - Constrained Application Protocol (CoAP)}
12 | spec.description = %q{Ruby Gem for RFC 7252 - Constrained Application Protocol (CoAP)
13 | The Constrained Application Protocol (CoAP) is a specialized web
14 | transfer protocol for use with constrained nodes and constrained
15 | (e.g., low-power, lossy) networks. The nodes often have 8-bit
16 | microcontrollers with small amounts of ROM and RAM, while constrained
17 | networks such as 6LoWPAN often have high packet error rates and a
18 | typical throughput of 10s of kbit/s. The protocol is designed for
19 | machine-to-machine (M2M) applications such as smart energy and
20 | building automation}
21 | spec.homepage = "https://github.com/SmallLars/coap"
22 | spec.license = "MIT"
23 |
24 | spec.files = `git ls-files`.split($/)
25 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
26 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
27 | spec.require_paths = ["lib"]
28 |
29 | spec.add_development_dependency "bundler", "~> 1.5"
30 | spec.add_development_dependency "rake"
31 | spec.add_development_dependency 'coveralls', '~> 0.7', '>= 0.7.0'
32 |
33 | spec.add_dependency "resolv-ipv6favor"
34 | # spec.add_dependency "codtls"
35 | end
36 |
--------------------------------------------------------------------------------
/lib/coap.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | module CoAP
3 | module_function
4 |
5 | def logger
6 | if @logger.nil?
7 | @logger = Logger.new(STDOUT)
8 | @logger.level = Logger::WARN
9 | end
10 |
11 | @logger
12 | end
13 | def logger= logger
14 | @logger.close unless @logger.nil?
15 |
16 | @logger = logger
17 | end
18 | end
19 |
20 | require 'logger'
21 | require 'socket'
22 | require 'resolv-ipv6favor'
23 | require 'ipaddr'
24 | require 'timeout'
25 | #require 'CoDTLS'
26 |
27 | require 'coap/coap.rb'
28 | require 'coap/message.rb'
29 | require 'coap/block.rb'
30 | require 'coap/mysocket.rb'
31 | require 'coap/observer.rb'
32 | require 'coap/client.rb'
33 |
34 | require 'misc/hexdump.rb'
--------------------------------------------------------------------------------
/lib/coap/block.rb:
--------------------------------------------------------------------------------
1 | module CoAP
2 | # CoAP client library
3 | class Block
4 | def self.initialize(num, more, szx)
5 | @logger = CoAP.logger
6 |
7 | { more: more, szx: szx, num: num }
8 | end
9 |
10 | def self.encode(num, more, szx)
11 | block = szx | more << 3 | num << 4
12 |
13 | @logger.debug '### CoAP Block encode ###'
14 | @logger.debug block
15 | @logger.debug '### CoAP Block encode ###'
16 |
17 | block
18 | end
19 |
20 | def self.encode_hash(blockHash)
21 | blockHash[:more] ? more = 1 : more = 0
22 | block = blockHash[:szx] | more << 3 | blockHash[:num] << 4
23 |
24 | @logger.debug '### CoAP Block encode ###'
25 | @logger.debug block
26 | @logger.debug '### CoAP Block encode ###'
27 |
28 | block
29 | end
30 |
31 | def self.decode(blockOption)
32 | more = blockOption != nil && (blockOption & 8) === 8
33 | szx = blockOption != nil ? blockOption & 7 : 0
34 | num = blockOption != nil ? blockOption >> 4 : 0
35 |
36 | @logger.debug '### CoAP Block decode ###'
37 | @logger.debug 'm: ' + more.to_s
38 | @logger.debug 'szx: ' + szx.to_s
39 | @logger.debug 'num: ' + num.to_s
40 | @logger.debug '### CoAP Block decode ###'
41 |
42 | { more: more, szx: szx, num: num }
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/coap/client.rb:
--------------------------------------------------------------------------------
1 | module CoAP
2 |
3 | # CoAP client library
4 | class Client
5 |
6 | # attr_writer
7 | #
8 | # @param max_payload optional payload size, default 256
9 | # @param max_retransmit optional retransmit count, default 4
10 | # @param ack_timeout optional ack_timeout, default 2
11 | # @param hostname dest optional hostname
12 | # @param port dest optional port
13 | attr_writer :max_payload, :max_retransmit, :ack_timeout, :hostname, :port
14 |
15 | # new arguments
16 | #
17 | # @param max_payload optional payload size, default 256
18 | # @param max_retransmit optional retransmit count, default 4
19 | # @param ack_timeout optional ack_timeout, default 2
20 | # @param hostname dest optional hostname
21 | # @param port dest optional port
22 | def initialize(max_payload = 256, max_retransmit = 4, ack_timeout = 2, hostname = nil, port = nil)
23 | @max_payload = max_payload
24 | @max_retransmit = max_retransmit
25 | @ack_timeout = ack_timeout
26 |
27 | @hostname = hostname
28 | @port = port
29 |
30 | # dont change the following stuff
31 | @retry_count = 0
32 |
33 | @MySocket = MySocket.new
34 | @MySocket.socket_type = UDPSocket
35 | @MySocket.ack_timeout = @ack_timeout
36 |
37 | @logger = CoAP.logger
38 | end
39 |
40 | # Enable DTLS Socket
41 | # Needs CoDTLS Gem
42 | def use_dtls
43 | @MySocket = MySocket.new
44 | @MySocket.socket_type = CoDTLS::SecureSocket
45 | @MySocket.ack_timeout = @ack_timeout
46 |
47 | self
48 | end
49 |
50 | def chunk(string, size)
51 | chunks = []
52 | string.bytes.each_slice(size) { |i| chunks << i.pack('C*') }
53 | chunks
54 | # string.scan(/.{1,#{size}}/)
55 | end
56 |
57 | def decode_url(url)
58 | url_decoded = CoAP.scheme_and_authority_decode(url)
59 |
60 | @logger.debug 'url decoded: ' + url_decoded.inspect
61 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
62 |
63 | url_decoded
64 | end
65 |
66 | # simple coap get
67 | #
68 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
69 | # @param options coap options
70 | #
71 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
72 | def get_by_url(url, options = {})
73 | url_decoded = CoAP.scheme_and_authority_decode(url)
74 |
75 | @logger.debug 'url decoded: ' + url_decoded.inspect
76 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
77 |
78 | get(url_decoded[0], url_decoded[1], url_decoded[2], options)
79 | end
80 |
81 | # simple coap get
82 | #
83 | # @param hostname hostname or ip address
84 | # @param port port to connect to
85 | # @param uri eg /.well-known/core
86 | # @param options coap options
87 | #
88 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
89 | def get(hostname, port, uri, options = {})
90 | @retry_count = 0
91 | client(hostname, port, uri, :get, nil, options)
92 | end
93 |
94 | # coap post
95 | #
96 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
97 | # @param payload payload which should be send
98 | # @param options coap options
99 | #
100 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
101 | def post_by_url(url, payload, options = {})
102 | url_decoded = CoAP.scheme_and_authority_decode(url)
103 |
104 | @logger.debug 'url decoded: ' + url_decoded.inspect
105 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
106 |
107 | post(url_decoded[0], url_decoded[1], url_decoded[2], payload, options)
108 | end
109 |
110 | # coap post
111 | #
112 | # @param hostname hostname or ip address
113 | # @param port port to connect to
114 | # @param uri eg /.well-known/core
115 | # @param payload payload which should be send
116 | # @param options coap options
117 | #
118 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
119 | def post(hostname, port, uri, payload, options = {})
120 | @retry_count = 0
121 | client(hostname, port, uri, :post, payload, options)
122 | end
123 |
124 | # coap put
125 | #
126 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
127 | # @param payload payload which should be send
128 | # @param options coap options
129 | #
130 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
131 | def put_by_url(url, payload, options = {})
132 | url_decoded = CoAP.scheme_and_authority_decode(url)
133 |
134 | @logger.debug 'url decoded: ' + url_decoded.inspect
135 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
136 |
137 | put(url_decoded[0], url_decoded[1], url_decoded[2], payload, options)
138 | end
139 |
140 | # coap put
141 | #
142 | # @param hostname hostname or ip address
143 | # @param port port to connect to
144 | # @param uri eg /.well-known/core
145 | # @param payload payload which should be send
146 | # @param options coap options
147 | #
148 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
149 | def put(hostname, port, uri, payload, options = {})
150 | @retry_count = 0
151 | client(hostname, port, uri, :put, payload, options)
152 | end
153 |
154 | # coap delete
155 | #
156 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
157 | # @param options coap options
158 | #
159 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
160 | def delete_by_url(hostname, port, uri, options = {})
161 | url_decoded = CoAP.scheme_and_authority_decode(url)
162 |
163 | @logger.debug 'url decoded: ' + url_decoded.inspect
164 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
165 |
166 | delete(url_decoded[0], url_decoded[1], url_decoded[2], options)
167 | end
168 |
169 | # coap delete
170 | #
171 | # @param hostname hostname or ip address
172 | # @param port port to connect to
173 | # @param uri eg /.well-known/core
174 | # @param options coap options
175 | #
176 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
177 | def delete(hostname, port, uri, options = {})
178 | @retry_count = 0
179 | client(hostname, port, uri, :delete, nil, options)
180 | end
181 |
182 | # coap observe
183 | #
184 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
185 | # @param options coap options
186 | # @param callback method to call with the observe data eg. observe_callback(payload, socket)
187 | #
188 | def observe_by_url(hostname, port, uri, callback, options = {})
189 | url_decoded = CoAP.scheme_and_authority_decode(url)
190 |
191 | @logger.debug 'url decoded: ' + url_decoded.inspect
192 | fail ArgumentError, 'Invalid URL' if url_decoded.nil?
193 |
194 | observe(url_decoded[0], url_decoded[1], url_decoded[2], callback, options)
195 | end
196 |
197 | # coap observe
198 | #
199 | # @param hostname hostname or ip address
200 | # @param port port to connect to
201 | # @param uri eg /.well-known/core
202 | # @param options coap options
203 | # @param callback method to call with the observe data eg. observe_callback(payload, socket)
204 | #
205 | def observe(hostname, port, uri, callback, options = {})
206 | options[:observe] = 0
207 | client(hostname, port, uri, :get, nil, options, callback)
208 | end
209 |
210 | # custom client for special use
211 | #
212 | # @param url coap scheme + authority url eg coap://coap.me:5683/time
213 | # @param method string get post put delete etc
214 | # @param payload message payload
215 | # @param options coap options
216 | #
217 | # @return CoAP::Message #60, :token=>150, :etag=>[16135160785136240028], :content_format=>40, :block2=>226}, payload=\"\\\";ct=50,5>;rt=\\\"5\\\";ct=50\">"
218 | def custom(url, method, payload = nil, options = {})
219 |
220 | url_decoded = decode_url url
221 | client(url_decoded[0], url_decoded[1], url_decoded[2], method.to_sym, payload, options)
222 |
223 | end
224 |
225 | private
226 |
227 | def client(hostname, port, uri, method, payload, options, observe_callback = nil)
228 |
229 | # set hostname + port only one time on multiple requests
230 | hostname.nil? ? (hostname = @hostname unless @hostname.nil?) : @hostname = hostname
231 | port.nil? ? (port = @port unless @port.nil?) : @port = port
232 |
233 | # Error handling for paramaters
234 | fail ArgumentError, 'hostname missing' if hostname.nil? || hostname.empty?
235 | fail ArgumentError, 'port missing' if port.nil?
236 | fail ArgumentError, 'port must ba an integer' unless port.is_a? Integer
237 | fail ArgumentError, 'uri missing' if uri.nil? || uri.empty?
238 | fail ArgumentError, 'payload must be a string' unless payload.is_a? String unless payload.nil?
239 | fail ArgumentError, 'payload shouldnt be empty ' if payload.empty? unless payload.nil?
240 |
241 | # generate random message id
242 | mid = Random.rand(999)
243 | token = Random.rand(256)
244 | szx = Math.log2(@max_payload).floor - 4
245 |
246 | # initialize block 2 with payload size
247 | block2 = Block.initialize(0, false, szx)
248 |
249 | # initialize block1 if set
250 | block1 = options[:block1].nil? ? Block.initialize(0, false, szx) : Block.decode(options[:block1])
251 |
252 | # initialize chunks if payload size > max_payload
253 | chunks = chunk(payload, 2**((Math.log2(@max_payload).floor - 4) + 4)) unless payload.nil?
254 |
255 | # create coap message struct
256 | message = Message.new(:con, method, mid, nil, {})
257 | # , block2: Block.encode_hash(block2)
258 | message.options = { uri_path: CoAP.path_decode(uri), token: token }
259 | message.options[:block2] = Block.encode_hash(block2) if @max_payload != 256 # temp fix to disable early negotation
260 | message.payload = payload unless payload.nil?
261 |
262 | ### initialize stuff end
263 |
264 | # if chunks.size 1 > we need to use block1
265 | if !payload.nil? and chunks.size > 1
266 | # increase block number
267 | block1[:num] += 1 unless options[:block1].nil?
268 | # more ?
269 | block1[:more] = chunks.size > block1[:num] + 1
270 | chunks.size > block1[:num] + 1 ? message.options.delete(:block2) : block1[:more] = false
271 | # set final payload
272 | message.payload = chunks[block1[:num]]
273 | # set message option
274 | message.options[:block1] = Block.encode_hash(block1)
275 | end
276 |
277 | # preserve user options
278 | message.options[:block2] = options[:block2] unless options[:block2] == nil
279 | message.options[:observe] = options[:observe] unless options[:observe] == nil
280 | message.options.merge(options)
281 |
282 | # debug functions
283 | @logger.debug '### CoAP Send Data ###'
284 | #@logger.debug message.to_s.hexdump
285 | @logger.debug message.inspect
286 | @logger.debug '### CoAP Send Data ###'
287 |
288 | # connect via udp/dtls
289 | @MySocket.connect hostname, port
290 | @MySocket.send message.to_wire, 0
291 |
292 | # send message + retry
293 | begin
294 | recv_data = @MySocket.receive
295 | rescue Timeout::Error
296 | @retry_count += 1
297 | raise 'Retry Timeout ' + @retry_count.to_s if @retry_count > @max_retransmit
298 | return client(hostname, port, uri, method, payload, options, observe_callback)
299 | end
300 |
301 | # parse recv data
302 | recv_parsed = CoAP.parse(recv_data[0].force_encoding('BINARY'))
303 |
304 | # debug functions
305 | @logger.debug '### CoAP Received Data ###'
306 | #@logger.debug recv_parsed.to_s.hexdump
307 | @logger.debug recv_parsed.inspect
308 | @logger.debug '### CoAP Received Data ###'
309 |
310 | # payload is not fully transmitted
311 | if block1[:more]
312 | fail 'Max Recursion' if @retry_count > 10
313 | return client(hostname, port, uri, method, payload, message.options)
314 | end
315 |
316 | # separate ?
317 | if recv_parsed.tt == :ack && recv_parsed.payload.empty? && recv_parsed.mid == mid && recv_parsed.mcode[0] == 0 && recv_parsed.mcode[1] == 0
318 | @logger.debug '### SEPARATE REQUEST ###'
319 |
320 | # wait for answer ...
321 | recv_data = @MySocket.receive(600, @retry_count)
322 | recv_parsed = CoAP.parse(recv_data[0].force_encoding('BINARY'))
323 |
324 | # debug functions
325 | @logger.debug '### CoAP SEPARAT Data ###'
326 | #@logger.debug recv_parsed.to_s.hexdump
327 | @logger.debug recv_parsed.inspect
328 | @logger.debug '### CoAP SEPARAT Data ###'
329 |
330 | if recv_parsed.tt == :con
331 | message = Message.new(:ack, 0, recv_parsed.mid, nil, {})
332 | message.options = { token: recv_parsed.options[:token] }
333 | @MySocket.send message.to_wire, 0
334 | end
335 |
336 | @logger.debug '### SEPARATE REQUEST END ###'
337 | end
338 |
339 | # test for more block2 payload
340 | block2 = Block.decode(recv_parsed.options[:block2])
341 |
342 | if block2[:more]
343 | block2[:num] += 1
344 |
345 | options.delete(:block1) # end block1
346 | options[:block2] = Block.encode_hash(block2)
347 | fail 'Max Recursion' if @retry_count > 50
348 | local_recv_parsed = client(hostname, port, uri, method, nil, options)
349 | recv_parsed.payload << local_recv_parsed.payload unless local_recv_parsed.nil?
350 | end
351 |
352 | # do we need to observe?
353 | if recv_parsed.options[:observe]
354 | @Observer = CoAP::Observer.new
355 | @Observer.observe(recv_parsed, recv_data, observe_callback, @MySocket)
356 | end
357 |
358 | # this is bad
359 | fail ArgumentError, 'wrong token returned' if recv_parsed.options[:token] != token # create own error class
360 |
361 | recv_parsed
362 | end
363 | end
364 | end
365 |
--------------------------------------------------------------------------------
/lib/coap/coap.rb:
--------------------------------------------------------------------------------
1 | # coapmessage.rb
2 | # Copyright (C) 2010..2013 Carsten Bormann
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
274 |
--------------------------------------------------------------------------------
/lib/coap/message.rb:
--------------------------------------------------------------------------------
1 | # coapmessage.rb
2 | # Copyright (C) 2010..2013 Carsten Bormann
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
188 |
--------------------------------------------------------------------------------
/lib/coap/mysocket.rb:
--------------------------------------------------------------------------------
1 | module CoAP
2 | class MySocket
3 |
4 | attr_writer :socket_type, :ack_timeout
5 |
6 | def initialize
7 | @logger = CoAP.logger
8 |
9 | end
10 |
11 | def connect(hostname, port)
12 | # hostname or ipv4/ipv6 address?
13 | address = IPAddr.new(hostname)
14 | return connect_socket(address, port)
15 |
16 | rescue IPAddr::InvalidAddressError
17 | # got a hostname, trying to resolv
18 |
19 | addresses = IPv6FavorResolv.getaddresses(hostname)
20 |
21 | raise Resolv::ResolvError if addresses.empty?
22 |
23 | addresses.each do |address|
24 |
25 | begin
26 |
27 | # transform to IPAddr object
28 | address = IPAddr.new(address.to_s)
29 | return connect_socket(address, port)
30 |
31 | rescue Errno::EHOSTUNREACH
32 |
33 | @logger.fatal 'Address unreachable: ' + address.to_s if $DEBUG
34 | # try next one if exists
35 |
36 | rescue Errno::ENETUNREACH
37 |
38 | @logger.fatal 'Net unreachable: ' + address.to_s if $DEBUG
39 | # try next one if exists
40 |
41 | end
42 |
43 | end
44 | end
45 |
46 | def send(data, flags)
47 | @socket.send(data, flags)
48 | end
49 |
50 | def receive(timeout = nil, retry_count = 0)
51 | timeout = @ack_timeout**(retry_count + 1) if timeout.nil? # timeout doubles
52 |
53 | @logger.debug @socket.peeraddr.inspect
54 | @logger.debug @socket.addr.inspect
55 | @logger.debug 'AAA Timeout: ' + timeout.to_s
56 |
57 | recv_data = nil
58 | status = Timeout.timeout(timeout) do
59 | recv_data = @socket.recvfrom(1024)
60 | end
61 |
62 | # pp recv_data
63 | recv_data
64 | end
65 |
66 | private
67 |
68 | def connect_socket(address, port)
69 | if address.ipv6?
70 | @socket = @socket_type.new(Socket::AF_INET6)
71 | else
72 | @socket = @socket_type.new
73 | end
74 |
75 | # TODO: Error handling connection
76 | @socket.connect(address.to_s, port)
77 |
78 | @socket
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/lib/coap/observer.rb:
--------------------------------------------------------------------------------
1 | module CoAP
2 | class Observer
3 | MAX_OBSERVE_OPTION_VALUE = 8_388_608
4 |
5 | def initialize
6 | @logger = CoAP.logger
7 |
8 | @retry_count = 0
9 | end
10 |
11 | def observe(recv_parsed, recv_data, observe_callback, mySocket)
12 | observe_number = recv_parsed.options[:observe]
13 | observe_callback.call(recv_parsed, recv_data)
14 |
15 | loop do
16 | begin # todo fix this
17 | recv_data = mySocket.receive(60, @retry_count)
18 | rescue Timeout::Error
19 | @retry_count = 0
20 | @logger.error 'Observe Timeout'
21 | end
22 |
23 | recv_parsed = CoAP.parse(recv_data[0].force_encoding('BINARY'))
24 |
25 | if recv_parsed.tt == :con
26 | message = Message.new(:ack, 0, recv_parsed.mid, nil, {})
27 | mySocket.send message.to_wire, 0
28 | end
29 |
30 | if recv_parsed.options[:observe] &&
31 | ((recv_parsed.options[:observe] > observe_number && recv_parsed.options[:observe] - observe_number < MAX_OBSERVE_OPTION_VALUE) ||
32 | (recv_parsed.options[:observe] < observe_number && observe_number - recv_parsed.options[:observe] > MAX_OBSERVE_OPTION_VALUE))
33 |
34 | observe_callback.call(recv_parsed, recv_data)
35 |
36 | end
37 |
38 | end
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/coap/version.rb:
--------------------------------------------------------------------------------
1 | module Coap
2 | VERSION = "0.0.16"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/misc/hexdump.rb:
--------------------------------------------------------------------------------
1 | # hexdump.rb
2 | # Copyright (C) 2010..2013 Carsten Bormann
3 |
4 | class String
5 | def hexdump(prefix = '', out = STDOUT)
6 | i = 0
7 | while i < length
8 | slice = byteslice(i, 16)
9 | out.puts '%s%-48s |%-16s|' %
10 | [prefix,
11 | slice.bytes.map { |b| '%02x' % b.ord }.join(' '),
12 | slice.gsub(/[^ -~]/mn, '.')]
13 | i += 16
14 | end
15 | out
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/coap_test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'coveralls'
2 | Coveralls.wear!
3 | require 'minitest/autorun'
4 |
5 | require 'coap.rb'
6 |
--------------------------------------------------------------------------------
/test/disabled_econotag_blck.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require_relative 'coap_test_helper'
3 |
4 | ECONOTAG_PAYLOAD = Random.rand(999).to_s + 'TESTLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligul'
5 | ECONOTAG_SHORT_PAYLOAD = Random.rand(999).to_s + 'TESTLorem ipsum'
6 |
7 | class TestMessage < Minitest::Unit::TestCase
8 | def observe_tester(data, socket)
9 | $observe_count += 1
10 | end
11 |
12 | def test_econotag_pr_b1_b2
13 |
14 | client = CoAP::Client.new
15 | client.max_payload = 48
16 | answer = client.post('aaaa::60b1:10', 5683, '/r', ECONOTAG_PAYLOAD)
17 | assert_equal(2, answer.mcode[0])
18 | assert_equal(1, answer.mcode[1])
19 | assert_equal(ECONOTAG_PAYLOAD, answer.payload)
20 |
21 | client = CoAP::Client.new
22 | client.max_payload = 48
23 | answer = client.post('aaaa::60b1:10', 5683, '/r', ECONOTAG_SHORT_PAYLOAD)
24 | assert_equal(ECONOTAG_SHORT_PAYLOAD, answer.payload)
25 |
26 | end
27 |
28 | end
29 |
30 | # post
31 | # mit payload und block1 oder ohne
32 | # seperate
33 | # block2 antwort
34 |
--------------------------------------------------------------------------------
/test/test_client.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require_relative 'coap_test_helper'
3 |
4 | # BAD: tests rely on working connection to coap.me (!)
5 | # TODO: rewrite tests with local coap server
6 | # test
7 | $observe_count = 0
8 | PAYLOAD = Random.rand(999).to_s + 'TESTLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nuncLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nuncTEST'
9 |
10 | PAYLOAD_SONDERZEICHEN = Random.rand(999).to_s + 'TESTLorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla 超大航母 consequat massa quis enim. Donec pede justoidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nuncTEST'
11 |
12 |
13 | class TestMessage < Minitest::Unit::TestCase
14 | def observe_tester(data, socket)
15 | $observe_count += 1
16 | end
17 |
18 | def test_client_get_v4_v6_hostname
19 | # client = CoAP::Client.new
20 | # answer = client.get('2001:638:708:30da:219:d1ff:fea4:abc5', 5683, '/hello')
21 | # assert_equal(2,answer.mcode[0])
22 | # assert_equal(5,answer.mcode[1])
23 | # assert_equal('world',answer.payload)
24 |
25 | client = CoAP::Client.new
26 | answer = client.get('coap.me', 5683, '/hello')
27 | assert_equal(2, answer.mcode[0])
28 | assert_equal(5, answer.mcode[1])
29 | assert_equal('world', answer.payload)
30 |
31 | client = CoAP::Client.new
32 | answer = client.get('134.102.218.16', 5683, '/hello')
33 | assert_equal(2, answer.mcode[0])
34 | assert_equal(5, answer.mcode[1])
35 | assert_equal('world', answer.payload)
36 | end
37 |
38 | def test_client_404
39 | # 404
40 | client = CoAP::Client.new
41 | answer = client.get('coap.me', 5683, '/hello-this-does-not-exist')
42 | assert_equal(4, answer.mcode[0])
43 | assert_equal(4, answer.mcode[1])
44 | assert_equal('Not found', answer.payload)
45 | end
46 |
47 | def test_multi_request
48 |
49 | x = Time.now
50 | client = CoAP::Client.new
51 | assert_raises RuntimeError do
52 | answer = client.get('192.0.2.1', 5683, '/hello')
53 | end
54 |
55 | x = Time.now
56 | assert_raises RuntimeError do
57 | answer = client.get('192.0.2.1', 5683, '/hello')
58 | end
59 | duration = Time.now - x
60 | #assert_operator duration, :>, 0
61 | #assert_operator duration, :<, 5
62 |
63 | end
64 |
65 | def test_client_connection_dns_problems
66 |
67 | # invalid hostname
68 | client = CoAP::Client.new
69 | assert_raises Resolv::ResolvError do
70 | client.get('coapunknown.mexyz', 5683, '/hello')
71 | end
72 |
73 | end
74 |
75 | def test_max_retransmit_ack_timeout
76 |
77 | client = CoAP::Client.new(256, 0, 30)
78 | duration = 0
79 | x = Time.now
80 | assert_raises RuntimeError do
81 | answer = client.get('bongo.xmorpheus.de', 1194, '/hello')
82 | end
83 | duration = Time.now - x
84 | assert_operator duration, :>, 25
85 | assert_operator duration, :<, 35
86 |
87 | client = CoAP::Client.new(256, 1, 30)
88 | duration = 0
89 | x = Time.now
90 | assert_raises RuntimeError do
91 | answer = client.get('bongo.xmorpheus.de', 1194, '/hello')
92 | end
93 | duration = Time.now - x
94 | assert_operator duration, :>, 55
95 | assert_operator duration, :<, 110
96 |
97 | end
98 |
99 | def test_client_arguments
100 | # wrong port
101 | client = CoAP::Client.new
102 | assert_raises Errno::ECONNREFUSED do
103 | client.get('coap.me', 15_683, '/hello')
104 | end
105 |
106 | # empty uri
107 | client = CoAP::Client.new
108 | assert_raises ArgumentError do
109 | answer = client.get('coap.me', 5683, '')
110 | end
111 |
112 | # empty hostname
113 | client = CoAP::Client.new
114 | assert_raises ArgumentError do
115 | answer = client.get('', 15_683, '/hello')
116 | end
117 |
118 | # missing port
119 | client = CoAP::Client.new
120 | assert_raises ArgumentError do
121 | answer = client.get('coap.me', nil, '/hello')
122 | end
123 |
124 | # string port
125 | client = CoAP::Client.new
126 | assert_raises ArgumentError do
127 | answer = client.get('coap.me', 'i m a sring', '/hello')
128 | end
129 |
130 | # empty payload
131 | client = CoAP::Client.new
132 | @suchEmpty = ''
133 | assert_raises ArgumentError do
134 | answer = client.post('coap.me', 5683, '/large-create', @suchEmpty)
135 | end
136 |
137 | end
138 |
139 | def test_client_by_url
140 | client = CoAP::Client.new
141 | answer = client.get_by_url('coap://coap.me:5683/hello')
142 | assert_equal('world', answer.payload)
143 |
144 | # broken url
145 | client = CoAP::Client.new
146 | assert_raises ArgumentError do
147 | answer = client.get_by_url('coap:/#/coap.me:5683/hello')
148 | end
149 | end
150 |
151 | # need to test payload
152 |
153 | def test_client_post
154 | client = CoAP::Client.new
155 | answer = client.post('coap.me', 5683, '/test', 'TD_COAP_CORE_04')
156 | assert_equal(2, answer.mcode[0])
157 | assert_equal(1, answer.mcode[1])
158 | assert_equal('POST OK', answer.payload)
159 | end
160 |
161 | # basic test put
162 | def test_client_put
163 | client = CoAP::Client.new
164 | answer = client.put('coap.me', 5683, '/test', 'TD_COAP_CORE_03')
165 | assert_equal(2, answer.mcode[0])
166 | assert_equal(4, answer.mcode[1])
167 | assert_equal('PUT OK', answer.payload)
168 | end
169 |
170 | def test_client_delete
171 | client = CoAP::Client.new
172 | answer = client.delete('coap.me', 5683, '/test')
173 | assert_equal(2, answer.mcode[0])
174 | assert_equal(2, answer.mcode[1])
175 | assert_equal('DELETE OK', answer.payload)
176 | end
177 |
178 | def test_client_sönderzeichen
179 | client = CoAP::Client.new
180 | answer = client.get_by_url('coap://coap.me/bl%C3%A5b%C3%A6rsyltet%C3%B8y')
181 | assert_equal(2, answer.mcode[0])
182 | assert_equal(5, answer.mcode[1])
183 | assert_equal('Übergrößenträger = 特大の人 = 超大航母', answer.payload.force_encoding('utf-8'))
184 | end
185 |
186 | def test_client_custom_call
187 | client = CoAP::Client.new
188 | answer = client.custom('coap://coap.me/bl%C3%A5b%C3%A6rsyltet%C3%B8y', 'get')
189 | assert_equal(2, answer.mcode[0])
190 | assert_equal(5, answer.mcode[1])
191 | assert_equal('Übergrößenträger = 特大の人 = 超大航母', answer.payload.force_encoding('utf-8'))
192 | end
193 |
194 | def test_multi_request_without_hostname_port
195 |
196 | client = CoAP::Client.new
197 | client.hostname = 'coap.me'
198 | client.port = 5683
199 | answer = client.get(nil, nil, '/hello')
200 | assert_equal(2, answer.mcode[0])
201 | assert_equal(5, answer.mcode[1])
202 | assert_equal('world', answer.payload)
203 |
204 | answer = client.get(nil, nil, '/hello')
205 | assert_equal(2, answer.mcode[0])
206 | assert_equal(5, answer.mcode[1])
207 | assert_equal('world', answer.payload)
208 |
209 | answer = client.get(nil, nil, '/hello')
210 | assert_equal(2, answer.mcode[0])
211 | assert_equal(5, answer.mcode[1])
212 | assert_equal('world', answer.payload)
213 |
214 | client = CoAP::Client.new
215 | answer = client.get('coap.me', 5683, '/hello')
216 | assert_equal(2, answer.mcode[0])
217 | assert_equal(5, answer.mcode[1])
218 | assert_equal('world', answer.payload)
219 |
220 | answer = client.get(nil, nil, '/hello')
221 | assert_equal(2, answer.mcode[0])
222 | assert_equal(5, answer.mcode[1])
223 | assert_equal('world', answer.payload)
224 |
225 | answer = client.get(nil, nil, '/hello')
226 | assert_equal(2, answer.mcode[0])
227 | assert_equal(5, answer.mcode[1])
228 | assert_equal('world', answer.payload)
229 |
230 | end
231 |
232 | def test_initialize
233 | client = CoAP::Client.new(16, 4, 2, 'coap.me', 5683)
234 | answer = client.get(nil, nil, '/large')
235 | assert_equal(2, answer.mcode[0])
236 | assert_equal(5, answer.mcode[1])
237 | assert_equal(%Q'\n 0 1 2 3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n |Ver| T | TKL | Code | Message ID |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n | Token (if any, TKL bytes) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n | Options (if any) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n |1 1 1 1 1 1 1 1| Payload (if any) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n[...]\n\n Token Length (TKL): 4-bit unsigned integer. Indicates the length of\n the variable-length Token field (0-8 bytes). Lengths 9-15 are\n reserved, MUST NOT be sent, and MUST be processed as a message\n format error.\n\n Code: 8-bit unsigned integer, split into a 3-bit class (most\n significant bits) and a 5-bit detail (least significant bits),\n documented as c.dd where c is a digit from 0 to 7 for the 3-bit\n subfield and dd are two digits from 00 to 31 for the 5-bit\n subfield. The class can indicate a request (0), a success\n response (2), a client error response (4), or a server error\n response (5). (All other class values are reserved.) As a\n special case, Code 0.00 indicates an Empty message. In case of a\n request, the Code field indicates the Request Method; in case of a\n response a Response Code. Possible values are maintained in the\n CoAP Code Registries (Section 12.1). The semantics of requests\n and responses are defined in Section 5.\n\n', answer.payload)
238 | end
239 |
240 | def test_client_separate
241 | client = CoAP::Client.new
242 | answer = client.get('coap.me', 5683, '/separate')
243 | assert_equal(2, answer.mcode[0])
244 | assert_equal(5, answer.mcode[1])
245 | assert_equal('That took a long time', answer.payload)
246 | end
247 |
248 | def test_client_observe
249 | # basic observe test
250 | client = CoAP::Client.new
251 |
252 | t1 = Thread.new do
253 | answer = client.observe('vs0.inf.ethz.ch', 5683, '/obs', method(:observe_tester))
254 | end
255 | sleep(10)
256 | t1.kill
257 |
258 | assert_operator $observe_count, :>, 2
259 | end
260 |
261 | def test_client_block2
262 | # blockwise transfer (block2)
263 | client = CoAP::Client.new
264 | client.max_payload = 512
265 | answer = client.get('coap.me', 5683, '/large')
266 | assert_equal(2, answer.mcode[0])
267 | assert_equal(5, answer.mcode[1])
268 | assert_equal(%Q'\n 0 1 2 3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n |Ver| T | TKL | Code | Message ID |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n | Token (if any, TKL bytes) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n | Options (if any) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n |1 1 1 1 1 1 1 1| Payload (if any) ...\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n[...]\n\n Token Length (TKL): 4-bit unsigned integer. Indicates the length of\n the variable-length Token field (0-8 bytes). Lengths 9-15 are\n reserved, MUST NOT be sent, and MUST be processed as a message\n format error.\n\n Code: 8-bit unsigned integer, split into a 3-bit class (most\n significant bits) and a 5-bit detail (least significant bits),\n documented as c.dd where c is a digit from 0 to 7 for the 3-bit\n subfield and dd are two digits from 00 to 31 for the 5-bit\n subfield. The class can indicate a request (0), a success\n response (2), a client error response (4), or a server error\n response (5). (All other class values are reserved.) As a\n special case, Code 0.00 indicates an Empty message. In case of a\n request, the Code field indicates the Request Method; in case of a\n response a Response Code. Possible values are maintained in the\n CoAP Code Registries (Section 12.1). The semantics of requests\n and responses are defined in Section 5.\n\n', answer.payload)
269 | end
270 |
271 | def test_client_block1
272 | client = CoAP::Client.new
273 | client.max_payload = 512
274 | answer = client.post('coap.me', 5683, '/large-create', PAYLOAD)
275 | assert_equal(2, answer.mcode[0])
276 | assert_equal(1, answer.mcode[1])
277 |
278 | client = CoAP::Client.new
279 | client.max_payload = 512
280 | answer = client.get('coap.me', 5683, '/large-create')
281 | assert_equal(2, answer.mcode[0])
282 | assert_equal(5, answer.mcode[1])
283 | assert_equal(PAYLOAD, answer.payload)
284 |
285 | client = CoAP::Client.new
286 | client.max_payload = 512
287 | answer = client.post('coap.me', 5683, '/large-create', PAYLOAD_SONDERZEICHEN)
288 | assert_equal(2, answer.mcode[0])
289 | assert_equal(1, answer.mcode[1])
290 |
291 | client = CoAP::Client.new
292 | client.max_payload = 512
293 | answer = client.get('coap.me', 5683, '/large-create')
294 | assert_equal(2, answer.mcode[0])
295 | assert_equal(5, answer.mcode[1])
296 | assert_equal(PAYLOAD_SONDERZEICHEN, answer.payload.force_encoding('utf-8'))
297 |
298 | client = CoAP::Client.new
299 | client.max_payload = 512
300 | answer = client.put('coap.me', 5683, '/large-update', PAYLOAD)
301 | assert_equal(2, answer.mcode[0])
302 | assert_equal(4, answer.mcode[1])
303 |
304 | client = CoAP::Client.new
305 | client.max_payload = 512
306 | answer = client.get('coap.me', 5683, '/large-update')
307 | assert_equal(2, answer.mcode[0])
308 | assert_equal(5, answer.mcode[1])
309 | assert_equal(PAYLOAD, answer.payload)
310 | end
311 | end
312 |
--------------------------------------------------------------------------------
/test/test_message.rb:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | require_relative 'coap_test_helper'
3 |
4 | class TestMessage < Minitest::Unit::TestCase
5 | def test_number_of_bits_up_to
6 | assert_equal 0, CoAP.number_of_bits_up_to(1)
7 | assert_equal 4, CoAP.number_of_bits_up_to(16)
8 | assert_equal 5, CoAP.number_of_bits_up_to(32)
9 | assert_equal 7, CoAP.number_of_bits_up_to(128)
10 | end
11 |
12 | def test_query_encode
13 | assert_equal '', CoAP.query_encode([])
14 | assert_equal '?', CoAP.query_encode([''])
15 | assert_equal '?foo', CoAP.query_encode(['foo'])
16 | assert_equal '?foo&bar', CoAP.query_encode(%w(foo bar))
17 | assert_equal '?f.o&b-r', CoAP.query_encode(['f.o', 'b-r'])
18 | assert_equal '?f(o&b)r', CoAP.query_encode(['f(o', 'b)r'])
19 | assert_equal '?foo&b/r', CoAP.query_encode(['foo', 'b/r'])
20 | assert_equal '?foo&b%26r', CoAP.query_encode(['foo', 'b&r'])
21 | assert_equal '?f%C3%B8o&b%C3%A4r', CoAP.query_encode(['føo', 'bär'])
22 | end
23 |
24 | # XXX: now properly checks for trailing slashes, how much trouble?
25 | def test_path_decode
26 | assert_equal [], CoAP.path_decode('/')
27 | assert_equal ['foo'], CoAP.path_decode('/foo')
28 | assert_equal ['foo', ''], CoAP.path_decode('/foo/') # confusing!
29 | assert_equal %w(foo bar), CoAP.path_decode('/foo/bar')
30 | assert_equal ['f.o', 'b-r'], CoAP.path_decode('/f.o/b-r')
31 | assert_equal ['f(o', 'b)r'], CoAP.path_decode('/f(o/b)r')
32 | assert_equal ['foo', 'b/r'], CoAP.path_decode('/foo/b%2Fr')
33 | assert_equal ['foo', 'b&r'], CoAP.path_decode('/foo/b&r')
34 | assert_equal ['føo', 'bär'], CoAP.path_decode('/f%C3%B8o/b%C3%A4r')
35 | end
36 |
37 | # XXX: now checks for trailing ampersands
38 | def test_query_decode
39 | assert_equal [], CoAP.query_decode('')
40 | assert_equal [''], CoAP.query_decode('?')
41 | assert_equal ['foo'], CoAP.query_decode('?foo')
42 | assert_equal ['foo', ''], CoAP.query_decode('?foo&')
43 | assert_equal %w(foo bar), CoAP.query_decode('?foo&bar')
44 | assert_equal ['f.o', 'b-r'], CoAP.query_decode('?f.o&b-r')
45 | assert_equal ['f(o', 'b)r'], CoAP.query_decode('?f(o&b)r')
46 | assert_equal ['foo', 'b/r'], CoAP.query_decode('?foo&b/r')
47 | assert_equal ['foo', 'b&r'], CoAP.query_decode('?foo&b%26r')
48 | assert_equal ['føo', 'bär'], CoAP.query_decode('?f%C3%B8o&b%C3%A4r')
49 | end
50 |
51 | def test_scheme_and_authority_encode
52 | assert_equal 'coap://foo.bar:4711', CoAP.scheme_and_authority_encode('foo.bar', 4711)
53 | assert_equal 'coap://foo.bar:4711', CoAP.scheme_and_authority_encode('foo.bar', '4711')
54 | assert_raises ArgumentError do
55 | CoAP.scheme_and_authority_encode('foo.bar', 'baz')
56 | end
57 | assert_equal 'coap://bar.baz', CoAP.scheme_and_authority_encode('bar.baz', 5683)
58 | assert_equal 'coap://bar.baz', CoAP.scheme_and_authority_encode('bar.baz', '5683')
59 | end
60 |
61 | def test_scheme_and_authority_decode
62 | assert_equal ['foo.bar', 4711, nil], CoAP.scheme_and_authority_decode('coap://foo.bar:4711')
63 | assert_equal ['foo.bar', 5683, nil], CoAP.scheme_and_authority_decode('coap://foo.bar')
64 | assert_equal ['foo:bar', 4711, nil], CoAP.scheme_and_authority_decode('coap://[foo:bar]:4711')
65 | assert_equal ['foo:bar', 5683, nil], CoAP.scheme_and_authority_decode('coap://%5Bfoo:bar%5D')
66 | end
67 |
68 | def test_coap_message
69 | input = "\x44\x02\x12\xA0abcd\x41A\x7B.well-known\x04core\x0D\x04rhabarbersaftglas\xFFfoobar".force_encoding('BINARY')
70 | output = CoAP.parse(input)
71 | w = output.to_wire
72 | assert_equal input, w
73 | end
74 |
75 | # XXX TODO add token tests
76 |
77 | def test_fenceposting
78 | m = CoAP::Message.new(:con, :get, 4711, 'Hello', {})
79 | m.options = { max_age: 987_654_321, if_none_match: true }
80 | me = m.to_wire
81 | m2 = CoAP.parse(me)
82 | m.options = CoAP::DEFAULTING_OPTIONS.merge(m.options)
83 | assert_equal m2, m
84 | end
85 |
86 | def test_fenceposting2
87 | m = CoAP::Message.new(:con, :get, 4711, 'Hello', {})
88 | m.options = { 4711 => ['foo'], 256 => ['bar'] }
89 | me = m.to_wire
90 | m2 = CoAP.parse(me)
91 | m.options = CoAP::DEFAULTING_OPTIONS.merge(m.options)
92 | assert_equal m2, m
93 | end
94 |
95 | def test_emptypayload
96 | m = CoAP::Message.new(:con, :get, 4711, '', {})
97 | m.options = { 4711 => ['foo'], 256 => ['bar'], 65_535 => ['abc' * 100] }
98 | me = m.to_wire
99 | m2 = CoAP.parse(me)
100 | m.options = CoAP::DEFAULTING_OPTIONS.merge(m.options)
101 | assert_equal m2, m
102 | end
103 |
104 | def test_option_numbers
105 | (0...65_536).each do |on|
106 | unless CoAP::OPTIONS[on] # those might have special semantics
107 | m = CoAP::Message.new(:con, :get, 4711, 'Hello', {})
108 | m.options = { on => [''] }
109 | me = m.to_wire
110 | m2 = CoAP.parse(me)
111 | m.options = CoAP::DEFAULTING_OPTIONS.merge(m.options)
112 | assert_equal m2, m
113 | end
114 | end
115 | end
116 |
117 | def test_option_lengths
118 | (0...1024).each do |ol|
119 | m = CoAP::Message.new(:con, :get, 4711, 'Hello', {})
120 | m.options = { 99 => ['x' * ol] }
121 | me = m.to_wire
122 | m2 = CoAP.parse(me)
123 | m.options = CoAP::DEFAULTING_OPTIONS.merge(m.options)
124 | assert_equal m2, m
125 | end
126 | end
127 | end
128 |
--------------------------------------------------------------------------------