├── .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 | [![Gem Version](https://badge.fury.io/rb/coap.png)](http://badge.fury.io/rb/coap) 2 | [![Dependency Status](https://gemnasium.com/SmallLars/coap.png)](https://gemnasium.com/SmallLars/coap) 3 | [![Build Status](https://travis-ci.org/SmallLars/coap.png?branch=master)](https://travis-ci.org/SmallLars/coap) 4 | [![Coverage Status](https://coveralls.io/repos/SmallLars/coap/badge.png?branch=master)](https://coveralls.io/r/SmallLars/coap) 5 | [![Code Climate](https://codeclimate.com/github/SmallLars/coap.png)](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,;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,;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,;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,;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,;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,;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,;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,;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,;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 | --------------------------------------------------------------------------------