david 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +4 -0
- data/.travis.yml +2 -2
- data/Gemfile +8 -2
- data/Gemfile.lock +84 -44
- data/README.md +87 -5
- data/benchmarks/Gemfile +1 -0
- data/benchmarks/Gemfile.lock +3 -1
- data/benchmarks/coapbench.sh +20 -0
- data/benchmarks/quick.sh +2 -0
- data/benchmarks/rackup/Gemfile +10 -0
- data/benchmarks/rackup/Gemfile.lock +159 -0
- data/benchmarks/rackup/grape.ru +18 -0
- data/benchmarks/rackup/rack.ru +7 -0
- data/benchmarks/rackup/rails.ru +14 -0
- data/benchmarks/rps.rb +14 -2
- data/benchmarks/stress.sh +17 -0
- data/david.gemspec +0 -3
- data/experiments/Gemfile +6 -0
- data/experiments/concurrency/Gemfile +3 -0
- data/experiments/concurrency/stub.rb +88 -0
- data/experiments/hash_key.rb +64 -0
- data/experiments/string_concat.rb +21 -0
- data/experiments/symbol_to_proc.rb +15 -0
- data/experiments/thread_safe.rb +40 -0
- data/lib/david.rb +8 -4
- data/lib/david/actor.rb +1 -11
- data/lib/david/app_config.rb +112 -0
- data/lib/david/exchange.rb +124 -0
- data/lib/david/fake_logger.rb +11 -0
- data/lib/david/garbage_collector.rb +9 -17
- data/lib/david/guerilla/rack/utils.rb +18 -0
- data/lib/david/interop.rb +4 -0
- data/lib/david/interop/mandatory_etsi.rb +4 -0
- data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
- data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
- data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
- data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
- data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
- data/lib/david/observe.rb +24 -29
- data/lib/david/rails/action_controller/base.rb +9 -7
- data/lib/david/railties/config.rb +4 -4
- data/lib/david/registry.rb +22 -0
- data/lib/david/server.rb +56 -56
- data/lib/david/server/constants.rb +1 -0
- data/lib/david/server/mapping.rb +43 -11
- data/lib/david/server/mid_cache.rb +29 -0
- data/lib/david/server/multicast.rb +7 -5
- data/lib/david/server/respond.rb +53 -43
- data/lib/david/server/utility.rb +2 -1
- data/lib/david/show_exceptions.rb +12 -8
- data/lib/david/transmitter.rb +44 -0
- data/lib/david/trap.rb +10 -0
- data/lib/david/version.rb +1 -1
- data/lib/rack/handler/david.rb +6 -10
- data/lib/rack/hello_world.rb +25 -0
- data/spec/app_config_spec.rb +56 -0
- data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
- data/spec/dummy/app/controllers/tests_controller.rb +9 -0
- data/spec/dummy/config/application.rb +4 -6
- data/spec/dummy/config/environments/development.rb +2 -2
- data/spec/dummy/config/environments/test.rb +2 -2
- data/spec/dummy/config/routes.rb +13 -53
- data/spec/interop/mandatory_spec.rb +100 -0
- data/spec/observe_spec.rb +8 -7
- data/spec/resource_discovery_spec.rb +3 -3
- data/spec/server_spec.rb +60 -15
- data/spec/spec_helper.rb +21 -2
- metadata +40 -33
- data/lib/david/request.rb +0 -80
- data/lib/david/server/deduplication.rb +0 -21
- data/lib/david/server/options.rb +0 -79
data/lib/david/server/utility.rb
CHANGED
@@ -6,7 +6,8 @@ module David
|
|
6
6
|
# This can only use each on body and currently does not support streaming.
|
7
7
|
def body_to_string(body)
|
8
8
|
s = ''
|
9
|
-
body.each { |line| s << line
|
9
|
+
body.each { |line| s << line << "\r\n" }
|
10
|
+
body.close if body.respond_to?(:close)
|
10
11
|
s.chomp
|
11
12
|
end
|
12
13
|
end
|
@@ -22,18 +22,23 @@ module David
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def render_exception(
|
25
|
+
def render_exception(e)
|
26
26
|
body = {
|
27
|
-
error:
|
28
|
-
message:
|
27
|
+
error: e.class.to_s,
|
28
|
+
message: e.message
|
29
29
|
}
|
30
30
|
|
31
|
-
log(
|
31
|
+
log.error([body[:error], body[:message]].join(': '))
|
32
32
|
|
33
33
|
body = body.to_json
|
34
34
|
|
35
|
-
code =
|
36
|
-
|
35
|
+
code = if defined?(ActiveRecord) && e.is_a?(ActiveRecord::RecordNotFound)
|
36
|
+
404
|
37
|
+
elsif e.is_a?(ActionController::RoutingError)
|
38
|
+
404
|
39
|
+
else
|
40
|
+
500
|
41
|
+
end
|
37
42
|
|
38
43
|
[code,
|
39
44
|
{
|
@@ -44,9 +49,8 @@ module David
|
|
44
49
|
]
|
45
50
|
end
|
46
51
|
|
47
|
-
def log
|
52
|
+
def log
|
48
53
|
@logger ||= @env['rack.logger']
|
49
|
-
@logger.send(level, message) if @logger
|
50
54
|
end
|
51
55
|
end
|
52
56
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module David
|
2
|
+
class Transmitter
|
3
|
+
include Registry
|
4
|
+
|
5
|
+
AF_INET6 = 'AF_INET6'.freeze
|
6
|
+
|
7
|
+
def initialize(socket)
|
8
|
+
@log = Celluloid.logger
|
9
|
+
@socket = socket || server.socket
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO Retransmissions
|
13
|
+
def send(exchange)
|
14
|
+
host = normalize_host(exchange.host)
|
15
|
+
|
16
|
+
@socket.send(exchange.message.to_wire, 0, host, exchange.port)
|
17
|
+
|
18
|
+
@log.info('-> ' + exchange.to_s)
|
19
|
+
@log.debug(exchange.message.inspect)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def ipv6?
|
25
|
+
@socket.addr[0] == AF_INET6
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_host(host)
|
29
|
+
ip = IPAddr.new(host)
|
30
|
+
|
31
|
+
if ipv6? && ip.ipv4?
|
32
|
+
ip = ip.ipv4_mapped
|
33
|
+
end
|
34
|
+
rescue ArgumentError
|
35
|
+
begin
|
36
|
+
host = Resolv.getaddress(host)
|
37
|
+
retry
|
38
|
+
rescue Resolv::ResolvError
|
39
|
+
end
|
40
|
+
else
|
41
|
+
ip.to_s
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/david/trap.rb
ADDED
data/lib/david/version.rb
CHANGED
data/lib/rack/handler/david.rb
CHANGED
@@ -1,22 +1,18 @@
|
|
1
1
|
module Rack
|
2
2
|
module Handler
|
3
3
|
class David
|
4
|
-
DEFAULT_OPTIONS = {
|
5
|
-
:Host => ENV['RACK_ENV'] == 'development' ? '::1' : '::',
|
6
|
-
:Port => ::CoAP::PORT
|
7
|
-
}
|
8
|
-
|
9
4
|
def self.run(app, options={})
|
10
|
-
options = DEFAULT_OPTIONS.merge(options)
|
11
|
-
|
12
5
|
g = Celluloid::SupervisionGroup.run!
|
13
6
|
|
14
7
|
g.supervise_as(:server, ::David::Server, app, options)
|
15
|
-
g.supervise_as(:observe, ::David::Observe) if options[:Observe] != false
|
16
8
|
g.supervise_as(:gc, ::David::GarbageCollector)
|
17
9
|
|
10
|
+
unless options[:Observe] == 'false'
|
11
|
+
g.supervise_as(:observe, ::David::Observe)
|
12
|
+
end
|
13
|
+
|
18
14
|
begin
|
19
|
-
|
15
|
+
Celluloid::Actor[:server].run
|
20
16
|
rescue Interrupt
|
21
17
|
Celluloid.logger.info 'Terminated'
|
22
18
|
Celluloid.logger = nil
|
@@ -25,7 +21,7 @@ module Rack
|
|
25
21
|
end
|
26
22
|
|
27
23
|
def self.valid_options
|
28
|
-
host, port = DEFAULT_OPTIONS.values_at(:Host, :Port)
|
24
|
+
host, port = AppConfig::DEFAULT_OPTIONS.values_at(:Host, :Port)
|
29
25
|
|
30
26
|
{
|
31
27
|
'Block=BOOLEAN' => 'Support for blockwise transfer (default: true)',
|
data/lib/rack/hello_world.rb
CHANGED
@@ -60,6 +60,31 @@ module Rack
|
|
60
60
|
{'Content-Type' => 'text/plain'},
|
61
61
|
[Time.now.to_s]
|
62
62
|
]
|
63
|
+
when '/cbor'
|
64
|
+
require 'json'
|
65
|
+
|
66
|
+
body = JSON.parse(env['rack.input'].read).to_s +
|
67
|
+
env['coap.cbor'].to_s
|
68
|
+
|
69
|
+
[200,
|
70
|
+
{
|
71
|
+
'Content-Type' => 'text/plain',
|
72
|
+
'Content-Length' => body.bytesize.to_s
|
73
|
+
},
|
74
|
+
[body]
|
75
|
+
]
|
76
|
+
when '/json'
|
77
|
+
require 'json'
|
78
|
+
|
79
|
+
body = {'Hello' => 'World!'}.to_json
|
80
|
+
|
81
|
+
[200,
|
82
|
+
{
|
83
|
+
'Content-Type' => 'application/json',
|
84
|
+
'Content-Length' => body.bytesize.to_s
|
85
|
+
},
|
86
|
+
[body]
|
87
|
+
]
|
63
88
|
else
|
64
89
|
[404, {}, ['']]
|
65
90
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AppConfig do
|
4
|
+
let(:port) { random_port }
|
5
|
+
|
6
|
+
let!(:server) { supervised_server(:Port => port, :CBOR => true) }
|
7
|
+
|
8
|
+
it { expect(subject).to be_a(Hash) }
|
9
|
+
|
10
|
+
describe '#choose_host' do
|
11
|
+
let(:method) do
|
12
|
+
->(*args) { subject.send(:choose_host, *args) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it { expect(method.call(nil)).to eq(nil) }
|
16
|
+
it { expect(method.call('::')).to eq('::') }
|
17
|
+
it { expect(method.call('::1')).to eq('::1') }
|
18
|
+
it { expect(method.call('localhost')).to eq('::1') }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#choose_port' do
|
22
|
+
let(:method) do
|
23
|
+
->(*args) { subject.send(:choose_port, *args) }
|
24
|
+
end
|
25
|
+
|
26
|
+
it { expect(method.call(nil)).to eq(nil) }
|
27
|
+
it { expect(method.call('1')).to be_a(Fixnum) }
|
28
|
+
it { expect(method.call('1')).to eq(1) }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#default_to_true' do
|
32
|
+
let(:method) do
|
33
|
+
->(*args) { subject.send(:default_to_true, *args) }
|
34
|
+
end
|
35
|
+
|
36
|
+
it { expect(method.call(:block, nil)).to eq(true) }
|
37
|
+
it { expect(method.call(:block, true)).to eq(true) }
|
38
|
+
it { expect(method.call(:block, 'true')).to eq(true) }
|
39
|
+
|
40
|
+
it { expect(method.call(:block, false)).to eq(false) }
|
41
|
+
it { expect(method.call(:block, 'false')).to eq(false) }
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#default_to_false' do
|
45
|
+
let(:method) do
|
46
|
+
->(*args) { subject.send(:default_to_false, *args) }
|
47
|
+
end
|
48
|
+
|
49
|
+
it { expect(method.call(:cbor, nil)).to eq(false) }
|
50
|
+
it { expect(method.call(:cbor, false)).to eq(false) }
|
51
|
+
it { expect(method.call(:cbor, 'false')).to eq(false) }
|
52
|
+
|
53
|
+
it { expect(method.call(:cbor, true)).to eq(true) }
|
54
|
+
it { expect(method.call(:cbor, 'true')).to eq(true) }
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class EtsisController < ActionController::Base
|
2
|
+
def show
|
3
|
+
headers['Content-Type'] = 'text/plain'
|
4
|
+
head 2.05
|
5
|
+
end
|
6
|
+
|
7
|
+
def update
|
8
|
+
head 2.01
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
head 2.04
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroy
|
16
|
+
head 2.02
|
17
|
+
end
|
18
|
+
|
19
|
+
def seg
|
20
|
+
show
|
21
|
+
end
|
22
|
+
|
23
|
+
def query
|
24
|
+
show
|
25
|
+
end
|
26
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
require File.expand_path('../boot', __FILE__)
|
2
2
|
|
3
3
|
# Pick the frameworks you want:
|
4
|
-
require "active_record/railtie"
|
4
|
+
# require "active_record/railtie"
|
5
5
|
require "action_controller/railtie"
|
6
|
-
require "action_mailer/railtie"
|
7
|
-
require "action_view/railtie"
|
8
|
-
require "sprockets/railtie"
|
6
|
+
# require "action_mailer/railtie"
|
7
|
+
# require "action_view/railtie"
|
8
|
+
# require "sprockets/railtie"
|
9
9
|
|
10
10
|
Bundler.require(*Rails.groups)
|
11
11
|
|
@@ -22,8 +22,6 @@ module Dummy
|
|
22
22
|
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
23
23
|
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
24
24
|
# config.i18n.default_locale = :de
|
25
|
-
|
26
|
-
config.coap.cbor = false
|
27
25
|
end
|
28
26
|
end
|
29
27
|
|
@@ -14,13 +14,13 @@ Rails.application.configure do
|
|
14
14
|
config.action_controller.perform_caching = false
|
15
15
|
|
16
16
|
# Don't care if the mailer can't send.
|
17
|
-
config.action_mailer.raise_delivery_errors = false
|
17
|
+
# config.action_mailer.raise_delivery_errors = false
|
18
18
|
|
19
19
|
# Print deprecation notices to the Rails logger.
|
20
20
|
config.active_support.deprecation = :log
|
21
21
|
|
22
22
|
# Raise an error on page load if there are pending migrations.
|
23
|
-
config.active_record.migration_error = :page_load
|
23
|
+
# config.active_record.migration_error = :page_load
|
24
24
|
|
25
25
|
# Debug mode disables concatenation and preprocessing of assets.
|
26
26
|
# This option may cause significant delays in view rendering with a large
|
@@ -21,7 +21,7 @@ Rails.application.configure do
|
|
21
21
|
config.action_controller.perform_caching = false
|
22
22
|
|
23
23
|
# Raise exceptions instead of rendering exception templates.
|
24
|
-
config.action_dispatch.show_exceptions =
|
24
|
+
config.action_dispatch.show_exceptions = true
|
25
25
|
|
26
26
|
# Disable request forgery protection in test environment.
|
27
27
|
config.action_controller.allow_forgery_protection = false
|
@@ -29,7 +29,7 @@ Rails.application.configure do
|
|
29
29
|
# Tell Action Mailer not to deliver emails to the real world.
|
30
30
|
# The :test delivery method accumulates sent emails in the
|
31
31
|
# ActionMailer::Base.deliveries array.
|
32
|
-
config.action_mailer.delivery_method = :test
|
32
|
+
# config.action_mailer.delivery_method = :test
|
33
33
|
|
34
34
|
# Print deprecation notices to the stderr.
|
35
35
|
config.active_support.deprecation = :stderr
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -1,58 +1,18 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
#
|
3
|
-
|
4
|
-
|
5
|
-
# You can have the root of your site routed with "root"
|
6
|
-
# root 'welcome#index'
|
7
|
-
|
8
|
-
# Example of regular route:
|
9
|
-
# get 'products/:id' => 'catalog#view'
|
10
|
-
|
11
|
-
# Example of named route that can be invoked with purchase_url(id: product.id)
|
12
|
-
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
|
13
|
-
|
14
|
-
# Example resource route (maps HTTP verbs to controller actions automatically):
|
15
|
-
# resources :products
|
16
|
-
|
17
|
-
# Example resource route with options:
|
18
|
-
# resources :products do
|
19
|
-
# member do
|
20
|
-
# get 'short'
|
21
|
-
# post 'toggle'
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# collection do
|
25
|
-
# get 'sold'
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
|
29
|
-
# Example resource route with sub-resources:
|
30
|
-
# resources :products do
|
31
|
-
# resources :comments, :sales
|
32
|
-
# resource :seller
|
33
|
-
# end
|
34
|
-
|
35
|
-
# Example resource route with more complex sub-resources:
|
36
|
-
# resources :products do
|
37
|
-
# resources :comments
|
38
|
-
# resources :sales do
|
39
|
-
# get 'recent', on: :collection
|
40
|
-
# end
|
41
|
-
# end
|
2
|
+
# For testing resource discovery middleware
|
3
|
+
resources :things
|
42
4
|
|
43
|
-
#
|
44
|
-
|
45
|
-
# post 'toggle'
|
46
|
-
# end
|
47
|
-
# resources :posts, concerns: :toggleable
|
48
|
-
# resources :photos, concerns: :toggleable
|
5
|
+
# Requests per second benchmark
|
6
|
+
get 'hello' => 'tests#benchmark'
|
49
7
|
|
50
|
-
#
|
51
|
-
|
52
|
-
# # Directs /admin/products/* to Admin::ProductsController
|
53
|
-
# # (app/controllers/admin/products_controller.rb)
|
54
|
-
# resources :products
|
55
|
-
# end
|
8
|
+
# CBOR transcoding tests
|
9
|
+
get 'cbor' => 'tests#cbor'
|
56
10
|
|
57
|
-
|
11
|
+
# ETSI Plugtests
|
12
|
+
get 'test' => 'etsis#show'
|
13
|
+
post 'test' => 'etsis#update'
|
14
|
+
put 'test' => 'etsis#create'
|
15
|
+
delete 'test' => 'etsis#destroy'
|
16
|
+
get 'seg1/seg2/seg3' => 'etsis#seg'
|
17
|
+
get 'query' => 'etsis#query'
|
58
18
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
[
|
4
|
+
Interop::MandatoryETSI::Grape,
|
5
|
+
Interop::MandatoryETSI::Hobbit,
|
6
|
+
Interop::MandatoryETSI::NYNY,
|
7
|
+
Interop::MandatoryETSI::Rack,
|
8
|
+
Interop::MandatoryETSI::Sinatra,
|
9
|
+
Rails.application
|
10
|
+
].each do |app|
|
11
|
+
describe "ETSI Plugstests, Mandatory, #{app.to_s.split('::').last}" do
|
12
|
+
let!(:server) { supervised_server(:MinimalMapping => true, app: app) }
|
13
|
+
|
14
|
+
[:con, :non].each do |tt|
|
15
|
+
context tt do
|
16
|
+
it 'TD_COAP_CORE_0{1,5}' do
|
17
|
+
mid, response = req(:get, '/test', tt: tt)
|
18
|
+
|
19
|
+
expect(response).to be_a(CoAP::Message)
|
20
|
+
expect(response.mcode).to eq([2, 5])
|
21
|
+
expect(response.mid).to eq(mid)
|
22
|
+
expect(response.options[:content_format]).to eq(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'TD_COAP_CORE_0{2,6}' do
|
26
|
+
mid, response = req(:post, '/test', tt: tt, payload: 'foo',
|
27
|
+
content_format: 0)
|
28
|
+
|
29
|
+
expect(response).to be_a(CoAP::Message)
|
30
|
+
expect(response.mcode).to eq([2, 1])
|
31
|
+
expect(response.mid).to eq(mid)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'TD_COAP_CORE_0{3,7}' do
|
35
|
+
mid, response = req(:put, '/test', tt: tt, payload: 'foo',
|
36
|
+
content_format: 0)
|
37
|
+
|
38
|
+
expect(response).to be_a(CoAP::Message)
|
39
|
+
expect(response.mcode).to eq([2, 4])
|
40
|
+
expect(response.mid).to eq(mid)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'TD_COAP_CORE_0{4,8}' do
|
44
|
+
mid, response = req(:delete, '/test', tt: tt)
|
45
|
+
|
46
|
+
expect(response).to be_a(CoAP::Message)
|
47
|
+
expect(response.mcode).to eq([2, 2])
|
48
|
+
expect(response.mid).to eq(mid)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'TD_COAP_CORE_09' do
|
54
|
+
pending
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'TD_COAP_CORE_10' do
|
58
|
+
token = rand(0xffffffff)
|
59
|
+
mid, response = req(:get, '/test', token: token)
|
60
|
+
|
61
|
+
expect(response).to be_a(CoAP::Message)
|
62
|
+
expect(response.mcode).to eq([2, 5])
|
63
|
+
expect(response.mid).to eq(mid)
|
64
|
+
expect(response.options[:content_format]).to eq(0)
|
65
|
+
expect(response.options[:token]).to eq(token)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'TD_COAP_CORE_11' do
|
69
|
+
mid, response = req(:get, '/test')
|
70
|
+
|
71
|
+
expect(response).to be_a(CoAP::Message)
|
72
|
+
expect(response.mcode).to eq([2, 5])
|
73
|
+
expect(response.mid).to eq(mid)
|
74
|
+
expect(response.options[:content_format]).to eq(0)
|
75
|
+
expect(response.options[:token]).to eq(0)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'TD_COAP_CORE_12' do
|
79
|
+
mid, response = req(:get, '/seg1/seg2/seg3')
|
80
|
+
|
81
|
+
expect(response).to be_a(CoAP::Message)
|
82
|
+
expect(response.mcode).to eq([2, 5])
|
83
|
+
expect(response.mid).to eq(mid)
|
84
|
+
expect(response.options[:content_format]).to eq(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'TD_COAP_CORE_13' do
|
88
|
+
mid, response = req(:get, '/query', uri_query: ['foo=1', 'bar=2'])
|
89
|
+
|
90
|
+
expect(response).to be_a(CoAP::Message)
|
91
|
+
expect(response.mcode).to eq([2, 5])
|
92
|
+
expect(response.mid).to eq(mid)
|
93
|
+
expect(response.options[:content_format]).to eq(0)
|
94
|
+
end
|
95
|
+
|
96
|
+
after do
|
97
|
+
server.terminate
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|