david 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.travis.yml +2 -2
  4. data/Gemfile +8 -2
  5. data/Gemfile.lock +84 -44
  6. data/README.md +87 -5
  7. data/benchmarks/Gemfile +1 -0
  8. data/benchmarks/Gemfile.lock +3 -1
  9. data/benchmarks/coapbench.sh +20 -0
  10. data/benchmarks/quick.sh +2 -0
  11. data/benchmarks/rackup/Gemfile +10 -0
  12. data/benchmarks/rackup/Gemfile.lock +159 -0
  13. data/benchmarks/rackup/grape.ru +18 -0
  14. data/benchmarks/rackup/rack.ru +7 -0
  15. data/benchmarks/rackup/rails.ru +14 -0
  16. data/benchmarks/rps.rb +14 -2
  17. data/benchmarks/stress.sh +17 -0
  18. data/david.gemspec +0 -3
  19. data/experiments/Gemfile +6 -0
  20. data/experiments/concurrency/Gemfile +3 -0
  21. data/experiments/concurrency/stub.rb +88 -0
  22. data/experiments/hash_key.rb +64 -0
  23. data/experiments/string_concat.rb +21 -0
  24. data/experiments/symbol_to_proc.rb +15 -0
  25. data/experiments/thread_safe.rb +40 -0
  26. data/lib/david.rb +8 -4
  27. data/lib/david/actor.rb +1 -11
  28. data/lib/david/app_config.rb +112 -0
  29. data/lib/david/exchange.rb +124 -0
  30. data/lib/david/fake_logger.rb +11 -0
  31. data/lib/david/garbage_collector.rb +9 -17
  32. data/lib/david/guerilla/rack/utils.rb +18 -0
  33. data/lib/david/interop.rb +4 -0
  34. data/lib/david/interop/mandatory_etsi.rb +4 -0
  35. data/lib/david/interop/mandatory_etsi/grape.rb +37 -0
  36. data/lib/david/interop/mandatory_etsi/hobbit.rb +30 -0
  37. data/lib/david/interop/mandatory_etsi/nyny.rb +36 -0
  38. data/lib/david/interop/mandatory_etsi/rack.rb +26 -0
  39. data/lib/david/interop/mandatory_etsi/sinatra.rb +36 -0
  40. data/lib/david/observe.rb +24 -29
  41. data/lib/david/rails/action_controller/base.rb +9 -7
  42. data/lib/david/railties/config.rb +4 -4
  43. data/lib/david/registry.rb +22 -0
  44. data/lib/david/server.rb +56 -56
  45. data/lib/david/server/constants.rb +1 -0
  46. data/lib/david/server/mapping.rb +43 -11
  47. data/lib/david/server/mid_cache.rb +29 -0
  48. data/lib/david/server/multicast.rb +7 -5
  49. data/lib/david/server/respond.rb +53 -43
  50. data/lib/david/server/utility.rb +2 -1
  51. data/lib/david/show_exceptions.rb +12 -8
  52. data/lib/david/transmitter.rb +44 -0
  53. data/lib/david/trap.rb +10 -0
  54. data/lib/david/version.rb +1 -1
  55. data/lib/rack/handler/david.rb +6 -10
  56. data/lib/rack/hello_world.rb +25 -0
  57. data/spec/app_config_spec.rb +56 -0
  58. data/spec/dummy/app/controllers/etsis_controller.rb +26 -0
  59. data/spec/dummy/app/controllers/tests_controller.rb +9 -0
  60. data/spec/dummy/config/application.rb +4 -6
  61. data/spec/dummy/config/environments/development.rb +2 -2
  62. data/spec/dummy/config/environments/test.rb +2 -2
  63. data/spec/dummy/config/routes.rb +13 -53
  64. data/spec/interop/mandatory_spec.rb +100 -0
  65. data/spec/observe_spec.rb +8 -7
  66. data/spec/resource_discovery_spec.rb +3 -3
  67. data/spec/server_spec.rb +60 -15
  68. data/spec/spec_helper.rb +21 -2
  69. metadata +40 -33
  70. data/lib/david/request.rb +0 -80
  71. data/lib/david/server/deduplication.rb +0 -21
  72. data/lib/david/server/options.rb +0 -79
@@ -1,11 +1,13 @@
1
- class ActionController::Base
2
- def self.discoverable(options)
3
- discovery_actor.register(self, options)
4
- end
1
+ module ActionController
2
+ class Base
3
+ def self.discoverable(options)
4
+ discovery_actor.register(self, options)
5
+ end
5
6
 
6
- protected
7
+ protected
7
8
 
8
- def self.discovery_actor
9
- Celluloid::Actor[:discovery]
9
+ def self.discovery_actor
10
+ Celluloid::Actor[:discovery]
11
+ end
10
12
  end
11
13
  end
@@ -4,19 +4,19 @@ module David
4
4
  config.coap = ActiveSupport::OrderedOptions.new
5
5
 
6
6
  # Blockwise transfer
7
- config.coap.block = true
7
+ config.coap.block = nil
8
8
 
9
9
  # Transparent JSON<>CBOR conversion
10
- config.coap.cbor = false
10
+ config.coap.cbor = nil
11
11
 
12
12
  # Default Content-Type if HTTP_ACCEPT is empty
13
13
  config.coap.default_format = nil
14
14
 
15
15
  # Multicast
16
- config.coap.multicast = true
16
+ config.coap.multicast = nil
17
17
 
18
18
  # Observe
19
- config.coap.observe = true
19
+ config.coap.observe = nil
20
20
 
21
21
  # david as default Rack handler (`rails s` starts david)
22
22
  config.coap.only = true
@@ -0,0 +1,22 @@
1
+ module David
2
+ module Registry
3
+ protected
4
+
5
+ def log
6
+ @log ||= Celluloid.logger
7
+ @log ||= ::Logger.new(nil)
8
+ end
9
+
10
+ def gc
11
+ Celluloid::Actor[:gc]
12
+ end
13
+
14
+ def observe
15
+ Celluloid::Actor[:observe]
16
+ end
17
+
18
+ def server
19
+ Celluloid::Actor[:server]
20
+ end
21
+ end
22
+ end
data/lib/david/server.rb CHANGED
@@ -1,103 +1,103 @@
1
- require 'david/server/deduplication'
1
+ require 'david/app_config'
2
+ require 'david/server/mid_cache'
2
3
  require 'david/server/multicast'
3
- require 'david/server/options'
4
4
  require 'david/server/respond'
5
5
 
6
6
  module David
7
7
  class Server
8
8
  include Celluloid::IO
9
- include CoAP::Coding
10
9
 
11
- include Deduplication
10
+ include MidCache
12
11
  include Multicast
13
- include Options
14
12
  include Respond
15
13
 
16
- attr_reader :logger, :socket
14
+ attr_reader :socket
17
15
 
18
16
  finalizer :shutdown
19
17
 
20
18
  def initialize(app, options)
21
- @block = choose(:block, options[:Block])
22
- @cbor = choose(:cbor, options[:CBOR])
23
- @host = choose(:host, options[:Host])
24
- @logger = choose(:logger, options[:Log])
25
- @mcast = choose(:mcast, options[:Multicast])
26
- @observe = choose(:observe, options[:Observe])
27
- @port = options[:Port].to_i
19
+ @app = app.respond_to?(:new) ? app.new : app
20
+ @mid_cache = {}
21
+ @options = AppConfig.new(options)
28
22
 
29
- @app = app.respond_to?(:new) ? app.new : app
23
+ host, port = @options.values_at(:Host, :Port)
30
24
 
31
- @default_format = choose(:default_format, options[:DefaultFormat])
25
+ log.info "David #{David::VERSION} on #{RUBY_DESCRIPTION}"
26
+ log.info "Starting on [#{host}]:#{port}"
32
27
 
33
- @dedup_cache = {}
34
-
35
- logger.info "David #{David::VERSION} on #{RUBY_DESCRIPTION}"
36
- logger.info "Starting on [#{@host}]:#{@port}"
37
-
38
- @ipv6 = IPAddr.new(@host).ipv6?
39
- af = @ipv6 ? ::Socket::AF_INET6 : ::Socket::AF_INET
28
+ af = ipv6? ? ::Socket::AF_INET6 : ::Socket::AF_INET
40
29
 
41
30
  # Actually Celluloid::IO::UDPSocket.
42
31
  @socket = UDPSocket.new(af)
43
- multicast_initialize if @mcast
44
- @socket.bind(@host, @port)
32
+ multicast_initialize! if @options[:Multicast]
33
+ @socket.bind(host, port)
34
+ end
45
35
 
46
- async.run
36
+ def run
37
+ loop do
38
+ if defined?(JRuby) || defined?(Rubinius)
39
+ dispatch(*@socket.recvfrom(1152))
40
+ else
41
+ begin
42
+ dispatch(*@socket.to_io.recvmsg_nonblock)
43
+ rescue ::IO::WaitReadable
44
+ Celluloid::IO.wait_readable(@socket)
45
+ retry
46
+ end
47
+ end
48
+ end
47
49
  end
48
50
 
49
51
  private
50
52
 
51
- def handle_input(*args)
53
+ def dispatch(*args)
52
54
  data, sender, _, anc = args
53
55
 
54
- if defined?(JRuby)
56
+ if defined?(JRuby) || defined?(Rubinius)
55
57
  port, _, host = sender[1..3]
56
58
  else
57
59
  host, port = sender.ip_address, sender.ip_port
58
60
  end
59
61
 
60
- message = CoAP::Message.parse(data)
61
- request = Request.new(host, port, message, anc)
62
+ message = CoAP::Message.parse(data)
63
+ exchange = Exchange.new(host, port, message, anc)
64
+
65
+ return if !exchange.non? && exchange.multicast?
62
66
 
63
- return unless request.con? || request.non?
64
- return unless request.valid_method?
65
- return if !request.non? && request.multicast?
67
+ log.info('<- ' + exchange.to_s)
68
+ log.debug(message.inspect)
66
69
 
67
- logger.info "[#{host}]:#{port}: #{message} (block #{request.block.num})"
68
- logger.debug message.inspect
70
+ key = exchange.key
71
+ cached = cache_get(key)
69
72
 
70
- if request.con? && duplicate?(request) #&& !request.idempotent?
71
- response, options = cached_response(request)
72
- logger.debug "(mid:#{request.mid} duplicate, response cached)"
73
+ if exchange.response? && !cached.nil?
74
+ cache_delete(key)
75
+ elsif exchange.request?
76
+ handle_request(exchange, key, cached)
77
+ end
78
+ end
79
+
80
+ def handle_request(exchange, key, cached)
81
+ if exchange.con? && !cached.nil? #&& !exchange.idempotent?
82
+ response = cached[0]
83
+ log.debug("dedup cache hit #{exchange.mid}")
73
84
  else
74
- response, options = respond(request)
85
+ response, _ = respond(exchange)
75
86
  end
76
87
 
77
88
  unless response.nil?
78
- logger.debug response.inspect
89
+ @socket.send(response.to_wire, 0, exchange.host, exchange.port)
79
90
 
80
- CoAP::Transmission.send(response, host, port,
81
- options.merge(socket: @socket))
91
+ exchange.message = response if log.info?
92
+ log.info('-> ' + exchange.to_s)
93
+ log.debug(response.inspect)
82
94
 
83
- request.options = options
84
- cache_response(request, response)
95
+ cache_add(key, response) if response.tt == :ack
85
96
  end
86
97
  end
87
98
 
88
- def run
89
- loop do
90
- if defined?(JRuby)
91
- async.handle_input(*@socket.recvfrom(1152))
92
- else
93
- begin
94
- async.handle_input(*@socket.to_io.recvmsg_nonblock)
95
- rescue ::IO::WaitReadable
96
- Celluloid::IO.wait_readable(@socket)
97
- retry
98
- end
99
- end
100
- end
99
+ def ipv6?
100
+ IPAddr.new(@options[:Host]).ipv6?
101
101
  end
102
102
 
103
103
  def shutdown
@@ -27,6 +27,7 @@ module David
27
27
  # Freeze CoAP specific env keys.
28
28
  COAP_VERSION = 'coap.version'.freeze
29
29
  COAP_MULTICAST = 'coap.multicast'.freeze
30
+ COAP_CBOR = 'coap.cbor'.freeze
30
31
  COAP_DTLS = 'coap.dtls'.freeze
31
32
  COAP_DTLS_ID = 'coap.dtls.id'.freeze
32
33
  COAP_DTLS_NOSEC = 'NoSec'.freeze
@@ -20,32 +20,41 @@ module David
20
20
  511 => 500,
21
21
  }.freeze
22
22
 
23
+ HTTP_TO_COAP_CODES_MINIMAL = {
24
+ 200 => 205,
25
+ }.freeze
26
+
23
27
  protected
24
28
 
25
29
  def accept_to_http(request)
26
30
  if request.accept.nil?
27
- @default_format
31
+ @options[:DefaultFormat]
28
32
  else
29
33
  CoAP::Registry.convert_content_format(request.accept)
30
34
  end
31
35
  end
32
36
 
33
- def body_to_cbor(body)
34
- JSON.parse(body).to_cbor
37
+ def body_to_cbor(json)
38
+ JSON.parse(json).to_cbor
35
39
  end
36
40
 
37
- def code_to_coap(code)
38
- if code.is_a?(Float)
39
- return [code.to_i, (code * 100 % 100).round]
41
+ def body_to_json(cbor)
42
+ if cbor.is_a?(String)
43
+ CBOR.load(cbor).to_json
44
+ else
45
+ cbor.to_json
40
46
  end
47
+ end
41
48
 
42
- code = code.to_i
43
- code = HTTP_TO_COAP_CODES[code] if HTTP_TO_COAP_CODES[code]
49
+ def code_to_coap(code)
50
+ set_http_to_coap_codes!
44
51
 
45
- a = code / 100
46
- b = code - (a * 100)
52
+ return float_to_array(code) if code.is_a?(Float)
47
53
 
48
- [a, b]
54
+ code = code.to_i
55
+ code = @http_to_coap_codes[code] if @http_to_coap_codes[code]
56
+
57
+ int_to_array(code)
49
58
  end
50
59
 
51
60
  def etag_to_coap(headers, bytes = 8)
@@ -63,6 +72,19 @@ module David
63
72
  end
64
73
  end
65
74
 
75
+ def float_to_array(float)
76
+ [float.to_i, (float * 100 % 100).round]
77
+ end
78
+
79
+ def int_to_array(int)
80
+ int = int.to_i
81
+
82
+ a = int / 100
83
+ b = int - (a * 100)
84
+
85
+ [a, b]
86
+ end
87
+
66
88
  def location_to_coap(headers)
67
89
  l = headers[HTTP_LOCATION].split('/').reject(&:empty?)
68
90
  return l.empty? ? nil : l
@@ -79,6 +101,16 @@ module David
79
101
  def method_to_http(method)
80
102
  method.to_s.upcase
81
103
  end
104
+
105
+ def set_http_to_coap_codes!
106
+ @http_to_coap_codes ||= begin
107
+ if @options && @options[:MinimalMapping]
108
+ HTTP_TO_COAP_CODES_MINIMAL
109
+ else
110
+ HTTP_TO_COAP_CODES
111
+ end
112
+ end
113
+ end
82
114
  end
83
115
  end
84
116
  end
@@ -0,0 +1,29 @@
1
+ module David
2
+ module MidCache
3
+ def self.included(base)
4
+ base.send(:attr_reader, :mid_cache)
5
+ end
6
+
7
+ def cache
8
+ @mid_cache
9
+ end
10
+
11
+ def cache_add(key, message)
12
+ @mid_cache[key] = [message, Time.now.to_i]
13
+ end
14
+
15
+ def cache_clean!(timeout)
16
+ now = Time.now.to_i
17
+ @mid_cache.delete_if { |_, v| now - v[1] >= timeout }
18
+ log.debug(@mid_cache.map { |k, v| v[0].to_s })
19
+ end
20
+
21
+ def cache_delete(key)
22
+ @mid_cache.delete(key)
23
+ end
24
+
25
+ def cache_get(key)
26
+ @mid_cache[key]
27
+ end
28
+ end
29
+ end
@@ -2,10 +2,10 @@ module David
2
2
  class Server
3
3
  # See https://tools.ietf.org/html/rfc7252#section-12.8
4
4
  module Multicast
5
- def multicast_initialize
5
+ def multicast_initialize!
6
6
  @socket.to_io.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
7
7
 
8
- if @ipv6
8
+ if ipv6?
9
9
  maddrs = ['ff02::fd', 'ff05::fd']
10
10
  maddrs << 'ff02::1' if OS.osx? # OSX needs ff02::1 explicitly joined.
11
11
  maddrs.each { |maddr| multicast_listen_ipv6(maddr) }
@@ -18,12 +18,14 @@ module David
18
18
  setsockopts_ipv4
19
19
  end
20
20
 
21
- logger.debug "Joined multicast groups: #{maddrs.join(', ')}"
21
+ log.debug "Joined multicast groups: #{maddrs.join(', ')}"
22
22
  rescue Errno::ENODEV, Errno::EADDRNOTAVAIL
23
- logger.warn 'Multicast initialization failure: Device not found.'
24
- @mcast = false
23
+ log.warn 'Multicast initialization failure: Device not found.'
24
+ @options[:Multicast] = false
25
25
  end
26
26
 
27
+ private
28
+
27
29
  def multicast_listen_ipv4(address)
28
30
  mreq = IPAddr.new(address).hton + IPAddr.new('0.0.0.0').hton
29
31
  @socket.to_io.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
@@ -5,35 +5,51 @@ require 'david/server/utility'
5
5
  module David
6
6
  class Server
7
7
  module Respond
8
+ include CoAP::Coding
9
+
8
10
  include Constants
9
11
  include Mapping
12
+ include Registry
10
13
  include Utility
11
14
 
12
- def respond(request, env = nil)
13
- block_enabled = @block && request.get?
15
+ def respond(exchange, env = nil)
16
+ block_enabled = @options[:Block] && exchange.get?
14
17
 
15
18
  if block_enabled
16
19
  # Fail if m set.
17
- if request.block.more && !request.multicast?
18
- return error(request, 4.05)
20
+ if exchange.block.more && !exchange.multicast?
21
+ return error(exchange, 4.05)
19
22
  end
20
23
  end
21
24
 
22
- return error(request, 5.05) if request.proxy?
25
+ return error(exchange, 5.05) if exchange.proxy?
26
+
27
+ env ||= basic_env(exchange)
28
+
29
+ if @options[:CBOR] && exchange.cbor?
30
+ begin
31
+ cbor = CBOR.load(exchange.message.payload)
32
+
33
+ body = body_to_json(cbor)
34
+ body = body.force_encoding('ASCII-8BIT') # Rack::Lint insisted...
23
35
 
24
- env ||= basic_env(request)
36
+ env[COAP_CBOR] = cbor
37
+ env[CONTENT_LENGTH] = body.bytesize
38
+ env[CONTENT_TYPE] = CONTENT_TYPE_JSON
39
+ env[RACK_INPUT] = StringIO.new(body)
40
+ rescue EOFError, CBOR::MalformedFormatError
41
+ end
42
+ end
25
43
 
26
44
  code, headers, body = @app.call(env)
27
45
 
28
- # No error responses on multicast requests.
29
- return if request.multicast? && !(200..299).include?(code)
46
+ # No error responses on multicast exchanges.
47
+ return if exchange.multicast? && !(200..299).include?(code)
30
48
 
31
49
  ct = headers[HTTP_CONTENT_TYPE]
32
50
  body = body_to_string(body)
33
51
 
34
- body.close if body.respond_to?(:close)
35
-
36
- if @cbor && ct == 'application/json'
52
+ if @options[:CBOR] && ct == CONTENT_TYPE_JSON
37
53
  begin
38
54
  body = body_to_cbor(body)
39
55
  ct = CONTENT_TYPE_CBOR
@@ -41,37 +57,35 @@ module David
41
57
  end
42
58
  end
43
59
 
44
- # No response on request for non-existent block.
45
- return if block_enabled && !request.block.included_by?(body)
60
+ # No response on exchange for non-existent block.
61
+ return if block_enabled && !exchange.block.included_by?(body)
46
62
 
47
63
  cf = CoAP::Registry.convert_content_format(ct)
48
64
  etag = etag_to_coap(headers, 4)
49
65
  loc = location_to_coap(headers)
50
66
  ma = max_age_to_coap(headers)
51
67
  mcode = code_to_coap(code)
52
- size = headers[HTTP_CONTENT_LENGTH].to_i
53
68
 
54
69
  # App returned cf different from accept
55
- return error(request, 4.06) if request.accept && request.accept != cf
70
+ return error(exchange, 4.06) if exchange.accept && exchange.accept != cf
56
71
 
57
- response = initialize_response(request, mcode)
72
+ response = initialize_response(exchange, mcode)
58
73
 
59
74
  response.options[:content_format] = cf
60
75
  response.options[:etag] = etag
61
76
  response.options[:location_path] = loc unless loc.nil?
62
77
  response.options[:max_age] = ma.to_i unless ma.nil?
63
78
 
64
- if @observe && handle_observe(request, env, etag)
79
+ if @options[:Observe] && handle_observe(exchange, env, etag)
65
80
  response.options[:observe] = 0
66
81
  end
67
82
 
68
83
  if block_enabled
69
- block = request.block.dup
84
+ block = exchange.block.dup
70
85
  block.set_more!(body)
71
86
 
72
87
  response.payload = block.chunk(body)
73
88
  response.options[:block2] = block.encode
74
- # response.options[:size2] = size if size != 0
75
89
  else
76
90
  response.payload = body
77
91
  end
@@ -81,22 +95,22 @@ module David
81
95
 
82
96
  private
83
97
 
84
- def basic_env(request)
85
- m = request.message
98
+ def basic_env(exchange)
99
+ m = exchange.message
86
100
 
87
101
  {
88
- REMOTE_ADDR => request.host,
89
- REMOTE_PORT => request.port.to_s,
102
+ REMOTE_ADDR => exchange.host,
103
+ REMOTE_PORT => exchange.port.to_s,
90
104
  REQUEST_METHOD => method_to_http(m.mcode),
91
105
  SCRIPT_NAME => EMPTY_STRING,
92
106
  PATH_INFO => path_encode(m.options[:uri_path]),
93
107
  QUERY_STRING => query_encode(m.options[:uri_query])
94
108
  .gsub(/^\?/, ''),
95
- SERVER_NAME => @host,
96
- SERVER_PORT => @port.to_s,
109
+ SERVER_NAME => @options[:Host],
110
+ SERVER_PORT => @options[:Port].to_s,
97
111
  CONTENT_LENGTH => m.payload.bytesize.to_s,
98
112
  CONTENT_TYPE => EMPTY_STRING,
99
- HTTP_ACCEPT => accept_to_http(request),
113
+ HTTP_ACCEPT => accept_to_http(exchange),
100
114
  RACK_VERSION => [1, 2],
101
115
  RACK_URL_SCHEME => RACK_URL_SCHEME_HTTP,
102
116
  RACK_INPUT => StringIO.new(m.payload),
@@ -104,42 +118,38 @@ module David
104
118
  RACK_MULTITHREAD => true,
105
119
  RACK_MULTIPROCESS => true,
106
120
  RACK_RUN_ONCE => false,
107
- RACK_LOGGER => @logger,
121
+ RACK_LOGGER => @options[:Log],
108
122
  COAP_VERSION => 1,
109
- COAP_MULTICAST => request.multicast?,
123
+ COAP_MULTICAST => exchange.multicast?,
110
124
  COAP_DTLS => COAP_DTLS_NOSEC,
111
125
  COAP_DTLS_ID => EMPTY_STRING,
112
126
  }
113
127
  end
114
128
 
115
- def error(request, mcode)
116
- [initialize_response(request, mcode), retransmit: false]
129
+ def error(exchange, mcode)
130
+ [initialize_response(exchange, mcode), retransmit: false]
117
131
  end
118
132
 
119
- def handle_observe(request, env, etag)
120
- return unless request.get? && request.observe?
133
+ def handle_observe(exchange, env, etag)
134
+ return unless exchange.get? && exchange.observe?
121
135
 
122
- if request.message.options[:observe] == 0
123
- observe.add(request, env, etag)
136
+ if exchange.message.options[:observe] == 0
137
+ observe.add(exchange, env, etag)
124
138
  true
125
139
  else
126
- observe.delete(request)
140
+ observe.delete(exchange)
127
141
  false
128
142
  end
129
143
  end
130
144
 
131
- def initialize_response(request, mcode = 2.00)
132
- type = request.con? ? :ack : :non
145
+ def initialize_response(exchange, mcode = 2.05)
146
+ type = exchange.con? ? :ack : :non
133
147
 
134
148
  CoAP::Message.new \
135
149
  tt: type,
136
150
  mcode: mcode,
137
- mid: request.message.mid || SecureRandom.random_number(0xffff),
138
- token: request.token
139
- end
140
-
141
- def observe
142
- Celluloid::Actor[:observe]
151
+ mid: exchange.message.mid || SecureRandom.random_number(0xffff),
152
+ token: exchange.token
143
153
  end
144
154
  end
145
155
  end