selfsdk 0.0.124

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SelfSDK
4
+ module Messages
5
+ class Base
6
+ attr_accessor :from, :from_device, :to, :to_device, :expires, :id,
7
+ :fields, :typ, :payload, :status, :input, :intermediary,
8
+ :description, :sub, :exp_timeout
9
+
10
+ def initialize(messaging)
11
+ @client = messaging.client
12
+ @jwt = @client.jwt
13
+ @messaging = messaging
14
+ @device_id = "1"
15
+ end
16
+
17
+ def request
18
+ res = @messaging.send_and_wait_for_response(proto, self)
19
+ SelfSDK.logger.info "synchronously messaging to #{@to}:#{@to_device}"
20
+ res
21
+ end
22
+
23
+ def send_message
24
+ res = @messaging.send_message proto
25
+ SelfSDK.logger.info "asynchronously requested information to #{@to}:#{@to_device}"
26
+ res
27
+ end
28
+
29
+ def unauthorized?
30
+ status == "unauthorized"
31
+ end
32
+
33
+ def rejected?
34
+ status == "rejected"
35
+ end
36
+
37
+ def accepted?
38
+ status == "accepted"
39
+ end
40
+
41
+ def errored?
42
+ status == "errored"
43
+ end
44
+
45
+ def validate!(original)
46
+ unless original.nil?
47
+ raise ::StandardError.new("bad response audience") if @audience != original.from
48
+ if original.intermediary.nil?
49
+ raise ::StandardError.new("bad issuer") if @from != original.to
50
+ else
51
+ raise ::StandardError.new("bad issuer") if @from != original.intermediary
52
+ end
53
+ end
54
+ raise ::StandardError.new("expired message") if @expires < SelfSDK::Time.now
55
+ raise ::StandardError.new("issued too soon") if @issued > SelfSDK::Time.now
56
+ end
57
+
58
+ protected
59
+
60
+ def proto
61
+ raise ::StandardError.new("must define this method")
62
+ end
63
+
64
+ private
65
+
66
+ def get_payload(input)
67
+ body = if input.is_a? String
68
+ input
69
+ else
70
+ input.ciphertext
71
+ end
72
+
73
+ jwt = JSON.parse(body, symbolize_names: true)
74
+ payload = JSON.parse(@jwt.decode(jwt[:payload]), symbolize_names: true)
75
+ @from = payload[:iss]
76
+ verify! jwt
77
+ payload
78
+ end
79
+
80
+ def verify!(jwt)
81
+ k = @client.public_keys(@from).first[:key]
82
+ return if @jwt.verify(jwt, k)
83
+
84
+ SelfSDK.logger.info "skipping message, invalid signature"
85
+ raise ::StandardError.new("invalid signature on incoming message")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'attestation'
4
+
5
+ module SelfSDK
6
+ module Messages
7
+ class Fact
8
+ attr_accessor :name, :attestations, :operator, :expected_value, :sources
9
+
10
+ def initialize(messaging)
11
+ @messaging = messaging
12
+ end
13
+
14
+ def parse(fact)
15
+ @name = SelfSDK::fact_name(fact[:fact])
16
+
17
+ @operator = ""
18
+ @operator = SelfSDK::operator(fact[:operator]) if fact[:operator]
19
+
20
+ @sources = []
21
+ fact[:sources]&.each do |s|
22
+ @sources << SelfSDK::source(s)
23
+ end
24
+
25
+ @expected_value = fact[:expected_value] || ""
26
+ @attestations = []
27
+
28
+ fact[:attestations]&.each do |a|
29
+ attestation = SelfSDK::Messages::Attestation.new(@messaging)
30
+ attestation.parse(fact[:fact].to_sym, a)
31
+ @attestations.push(attestation)
32
+ end
33
+ end
34
+
35
+ def validate!(original)
36
+ @attestations.each do |a|
37
+ a.validate! original
38
+ end
39
+ end
40
+
41
+ def to_hash
42
+ h = { fact: @name }
43
+ unless @sources.nil?
44
+ h[:sources] = @sources if @sources.length > 0
45
+ end
46
+ h[:operator] = @operator unless @operator.empty?
47
+ unless @attestations.nil?
48
+ h[:attestations] = @attestations if @attestations.length > 0
49
+ end
50
+ h[:expected_value] = @expected_value unless @expected_value.empty?
51
+ h
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../ntptime'
5
+
6
+ module SelfSDK
7
+ module Messages
8
+ class FactRequest < Base
9
+ MSG_TYPE = "identities.facts.query.req"
10
+ DEFAULT_EXP_TIMEOUT = 900
11
+
12
+ attr_accessor :facts, :options
13
+
14
+ def parse_facts(facts)
15
+ @facts = []
16
+ facts.each do |fact|
17
+ f = SelfSDK::Messages::Fact.new(@messaging)
18
+ f.parse(fact)
19
+ @facts << f.to_hash
20
+ end
21
+ @facts
22
+ end
23
+
24
+ def populate(selfid, facts, opts)
25
+ @id = SecureRandom.uuid
26
+ @from = @client.jwt.id
27
+ @to = selfid
28
+ @facts = parse_facts(facts)
29
+
30
+ @id = opts[:cid] if opts.include?(:cid)
31
+ @options = opts.fetch(:options, false)
32
+ @description = opts.include?(:description) ? opts[:description] : nil
33
+ @exp_timeout = opts.fetch(:exp_timeout, DEFAULT_EXP_TIMEOUT)
34
+
35
+ @intermediary = if opts.include?(:intermediary)
36
+ opts[:intermediary]
37
+ end
38
+ end
39
+
40
+ def parse(input)
41
+ @input = input
42
+ @typ = MSG_TYPE
43
+ @payload = get_payload input
44
+ @id = @payload[:cid]
45
+ @from = @payload[:iss]
46
+ @to = @payload[:sub]
47
+ @expires = @payload[:exp]
48
+ @description = @payload.include?(:description) ? @payload[:description] : nil
49
+ @facts = @payload[:facts]
50
+ @options = @payload[:options]
51
+ end
52
+
53
+ def build_response
54
+ m = SelfSDK::Messages::FactResponse.new(@messaging)
55
+ m.id = @id
56
+ m.from = @to
57
+ m.to = @from
58
+ m.audience = @from
59
+ m.to_device = @messaging.device_id
60
+ m.facts = @facts
61
+ m
62
+ end
63
+
64
+ def share_facts(facts)
65
+ m = build_response
66
+ m.facts = parse_facts(facts)
67
+ m.send_message
68
+ end
69
+
70
+ def body
71
+ b = {
72
+ typ: MSG_TYPE,
73
+ iss: @jwt.id,
74
+ sub: @to,
75
+ iat: SelfSDK::Time.now.strftime('%FT%TZ'),
76
+ exp: (SelfSDK::Time.now + @exp_timeout).strftime('%FT%TZ'),
77
+ cid: @id,
78
+ jti: SecureRandom.uuid,
79
+ facts: @facts,
80
+ }
81
+ b[:options] = @options unless (@options.nil? || @options == false)
82
+ b[:description] = @description unless (@description.nil? || @description.empty?)
83
+ b
84
+ end
85
+
86
+ protected
87
+
88
+ def proto
89
+ devices = if @intermediary.nil?
90
+ @client.devices(@to)
91
+ else
92
+ @client.devices(@intermediary)
93
+ end
94
+ @to_device = devices.first
95
+
96
+ recipient = if @intermediary.nil?
97
+ "#{@to}:#{@to_device}"
98
+ else
99
+ "#{@intermediary}:#{@to_device}"
100
+ end
101
+
102
+ Msgproto::Message.new(
103
+ type: Msgproto::MsgType::MSG,
104
+ id: @id,
105
+ sender: "#{@jwt.id}:#{@messaging.device_id}",
106
+ recipient: recipient,
107
+ ciphertext: @jwt.prepare(body),
108
+ )
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative 'fact'
5
+ require_relative '../ntptime'
6
+
7
+ module SelfSDK
8
+ module Messages
9
+ class FactResponse < Base
10
+ MSG_TYPE = "identities.facts.query.resp"
11
+
12
+ attr_accessor :facts, :audience
13
+
14
+ def parse(input)
15
+ @input = input
16
+ @typ = MSG_TYPE
17
+ @payload = get_payload input
18
+ @id = payload[:cid]
19
+ @from = payload[:iss]
20
+ @to = payload[:sub]
21
+ @expires = ::Time.parse(payload[:exp])
22
+ @issued = ::Time.parse(payload[:iat])
23
+ @audience = payload[:aud]
24
+ @status = payload[:status]
25
+ @facts = []
26
+ payload[:facts] = [] if payload[:facts].nil?
27
+ payload[:facts].each do |f|
28
+ begin
29
+ fact = SelfSDK::Messages::Fact.new(@messaging)
30
+ fact.parse(f)
31
+ @facts.push(fact)
32
+ rescue StandardError => e
33
+ SelfSDK.logger.info e.message
34
+ end
35
+ end
36
+ end
37
+
38
+ def fact(name)
39
+ name = SelfSDK::fact_name(name)
40
+ @facts.select{|f| f.name == name}.first
41
+ end
42
+
43
+ def attestations_for(name)
44
+ f = fact(name)
45
+ return [] if f.nil?
46
+ f.attestations
47
+ end
48
+
49
+ def attestation_values_for(name)
50
+ a = attestations_for(name)
51
+ a.map{|a| a.value}
52
+ end
53
+
54
+ def validate!(original)
55
+ super
56
+ @facts.each do |f|
57
+ f.validate! original
58
+ end
59
+ end
60
+
61
+ protected
62
+
63
+ def proto
64
+ encoded_facts = []
65
+ @facts.each do |fact|
66
+ encoded_facts.push(fact.to_hash)
67
+ end
68
+ body = @jwt.prepare(
69
+ typ: MSG_TYPE,
70
+ iss: @jwt.id,
71
+ sub: @sub || @to,
72
+ aud: @audience,
73
+ iat: SelfSDK::Time.now.strftime('%FT%TZ'),
74
+ exp: (SelfSDK::Time.now + 3600).strftime('%FT%TZ'),
75
+ cid: @id,
76
+ jti: SecureRandom.uuid,
77
+ status: @status,
78
+ facts: encoded_facts,
79
+ )
80
+
81
+ Msgproto::Message.new(
82
+ type: Msgproto::MsgType::MSG,
83
+ id: SecureRandom.uuid,
84
+ sender: "#{@jwt.id}:#{@messaging.device_id}",
85
+ recipient: "#{@to}:#{@to_device}",
86
+ ciphertext: body,
87
+ )
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "fact_request"
4
+ require_relative "fact_response"
5
+ require_relative "authentication_resp"
6
+ require_relative "authentication_req"
7
+
8
+ module SelfSDK
9
+ module Messages
10
+ def self.parse(input, messaging, original=nil)
11
+ body = if input.is_a? String
12
+ input
13
+ else
14
+ input.ciphertext
15
+ end
16
+
17
+ jwt = JSON.parse(body, symbolize_names: true)
18
+ payload = JSON.parse(messaging.jwt.decode(jwt[:payload]), symbolize_names: true)
19
+
20
+ case payload[:typ]
21
+ when "identities.facts.query.req"
22
+ m = FactRequest.new(messaging)
23
+ m.parse(body)
24
+ when "identities.facts.query.resp"
25
+ m = FactResponse.new(messaging)
26
+ m.parse(body)
27
+ when "identities.authenticate.resp"
28
+ m = AuthenticationResp.new(messaging)
29
+ m.parse(body)
30
+ when "identities.authenticate.req"
31
+ m = AuthenticationReq.new(messaging)
32
+ m.parse(body)
33
+ else
34
+ raise StandardError.new("Invalid message type.")
35
+ end
36
+ return m
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,441 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'monitor'
5
+ require 'faye/websocket'
6
+ require 'fileutils'
7
+ require_relative 'messages/message'
8
+ require_relative 'proto/auth_pb'
9
+ require_relative 'proto/message_pb'
10
+ require_relative 'proto/msgtype_pb'
11
+ require_relative 'proto/acl_pb'
12
+ require_relative 'proto/aclcommand_pb'
13
+
14
+ module SelfSDK
15
+ class MessagingClient
16
+ DEFAULT_DEVICE="1"
17
+ DEFAULT_AUTO_RECONNECT=true
18
+ DEFAULT_STORAGE_DIR="./.self_storage"
19
+ ON_DEMAND_CLOSE_CODE=3999
20
+
21
+ attr_accessor :client, :jwt, :device_id, :ack_timeout, :timeout, :type_observer, :uuid_observer
22
+
23
+ # RestClient initializer
24
+ #
25
+ # @param url [string] self-messaging url
26
+ # @params client [Object] SelfSDK::Client object
27
+ # @option opts [string] :storage_dir the folder where encryption sessions and settings will be stored
28
+ # @option opts [Bool] :auto_reconnect Automatically reconnects to websocket if connection is lost (defaults to true).
29
+ # @option opts [String] :device_id The device id to be used by the app defaults to "1".
30
+ def initialize(url, client, options = {})
31
+ @mon = Monitor.new
32
+ @url = url
33
+ @messages = {}
34
+ @acks = {}
35
+ @type_observer = {}
36
+ @uuid_observer = {}
37
+ @jwt = client.jwt
38
+ @client = client
39
+ @ack_timeout = 60 # seconds
40
+ @timeout = 120 # seconds
41
+ @device_id = options.fetch(:device_id, DEFAULT_DEVICE)
42
+ @auto_reconnect = options.fetch(:auto_reconnect, DEFAULT_AUTO_RECONNECT)
43
+ @storage_dir = options.fetch(:storage_dir, DEFAULT_STORAGE_DIR)
44
+ @offset_file = "#{@storage_dir}/#{@jwt.id}:#{@device_id}.offset"
45
+ @offset = read_offset
46
+
47
+ FileUtils.mkdir_p @storage_dir unless File.exists? @storage_dir
48
+
49
+ if options.include? :ws
50
+ @ws = options[:ws]
51
+ else
52
+ start
53
+ end
54
+ end
55
+
56
+ def stop
57
+ @acks.each do |k, _v|
58
+ mark_as_acknowledged(k)
59
+ end
60
+ @messages.each do |k, _v|
61
+ mark_as_acknowledged(k)
62
+ mark_as_arrived(k)
63
+ end
64
+ end
65
+
66
+ def close
67
+ @ws.close(ON_DEMAND_CLOSE_CODE, "connection closed by the client")
68
+ end
69
+
70
+ # Responds a request information request
71
+ #
72
+ # @param recipient [string] selfID to be requested
73
+ # @param recipient_device [string] device id for the selfID to be requested
74
+ # @param request [string] original message requesing information
75
+ def share_information(recipient, recipient_device, request)
76
+ send_message Msgproto::Message.new(
77
+ type: Msgproto::MsgType::MSG,
78
+ id: SecureRandom.uuid,
79
+ sender: "#{@jwt.id}:#{@device_id}",
80
+ recipient: "#{recipient}:#{recipient_device}",
81
+ ciphertext: @jwt.prepare(request),
82
+ )
83
+ end
84
+
85
+ # Send custom mmessage
86
+ #
87
+ # @param recipient [string] selfID to be requested
88
+ # @param type [string] message type
89
+ # @param request [hash] original message requesing information
90
+ def send_custom(recipient, request_body)
91
+ @to_device = @client.devices(recipient).first
92
+ send_message msg = Msgproto::Message.new(
93
+ type: Msgproto::MsgType::MSG,
94
+ id: SecureRandom.uuid,
95
+ sender: "#{@jwt.id}:#{@device_id}",
96
+ recipient: "#{recipient}:#{@to_device}",
97
+ ciphertext: @jwt.prepare(request_body),
98
+ )
99
+ end
100
+
101
+ # Allows incomming messages from the given identity
102
+ #
103
+ # @params payload [string] base64 encoded payload to be sent
104
+ def add_acl_rule(payload)
105
+ send_message Msgproto::AccessControlList.new(
106
+ type: Msgproto::MsgType::ACL,
107
+ id: SecureRandom.uuid,
108
+ command: Msgproto::ACLCommand::PERMIT,
109
+ payload: payload,
110
+ )
111
+ end
112
+
113
+ # Blocks incoming messages from specified identities
114
+ #
115
+ # @params payload [string] base64 encoded payload to be sent
116
+ def remove_acl_rule(payload)
117
+ send_message Msgproto::AccessControlList.new(
118
+ type: Msgproto::MsgType::ACL,
119
+ id: SecureRandom.uuid,
120
+ command: Msgproto::ACLCommand::REVOKE,
121
+ payload: payload,
122
+ )
123
+ end
124
+
125
+ # Lists acl rules
126
+ def list_acl_rules
127
+ wait_for 'acl_list' do
128
+ send_raw Msgproto::AccessControlList.new(
129
+ type: Msgproto::MsgType::ACL,
130
+ id: SecureRandom.uuid,
131
+ command: Msgproto::ACLCommand::LIST,
132
+ )
133
+ end
134
+ end
135
+
136
+ # Sends a message and waits for the response
137
+ #
138
+ # @params msg [Msgproto::Message] message object to be sent
139
+ def send_and_wait_for_response(msg, original)
140
+ wait_for msg.id, original do
141
+ send_message msg
142
+ end
143
+ end
144
+
145
+ # Executes the given block and waits for the message id specified on
146
+ # the uuid.
147
+ #
148
+ # @params uuid [string] unique identifier for a conversation
149
+ def wait_for(uuid, msg = nil)
150
+ SelfSDK.logger.info "sending #{uuid}"
151
+ @mon.synchronize do
152
+ @messages[uuid] = {
153
+ waiting_cond: @mon.new_cond,
154
+ waiting: true,
155
+ timeout: SelfSDK::Time.now + @timeout,
156
+ original_message: msg,
157
+ }
158
+ end
159
+
160
+ yield
161
+
162
+ SelfSDK.logger.info "waiting for client to respond #{uuid}"
163
+ @mon.synchronize do
164
+ @messages[uuid][:waiting_cond].wait_while do
165
+ @messages[uuid][:waiting]
166
+ end
167
+ end
168
+
169
+ SelfSDK.logger.info "response received for #{uuid}"
170
+ @messages[uuid][:response]
171
+ ensure
172
+ @messages.delete(uuid)
173
+ end
174
+
175
+ # Send a message through self network
176
+ #
177
+ # @params msg [Msgproto::Message] message object to be sent
178
+ def send_message(msg)
179
+ uuid = msg.id
180
+ @mon.synchronize do
181
+ @acks[uuid] = {
182
+ waiting_cond: @mon.new_cond,
183
+ waiting: true,
184
+ timeout: SelfSDK::Time.now + @ack_timeout,
185
+ }
186
+ end
187
+ send_raw(msg)
188
+ SelfSDK.logger.info "waiting for acknowledgement #{uuid}"
189
+ @mon.synchronize do
190
+ @acks[uuid][:waiting_cond].wait_while do
191
+ @acks[uuid][:waiting]
192
+ end
193
+ end
194
+ SelfSDK.logger.info "acknowledged #{uuid}"
195
+ true
196
+ ensure
197
+ @acks.delete(uuid)
198
+ false
199
+ end
200
+
201
+ def clean_observers
202
+ live = {}
203
+ @uuid_observer.clone.each do |id, msg|
204
+ if msg[:timeout] < SelfSDK::Time.now
205
+ message = SelfSDK::Messages::Base.new(self)
206
+ message.status = "errored"
207
+
208
+ @uuid_observer[id][:block].call(message)
209
+ @uuid_observer.delete(id)
210
+ else
211
+ live[id] = msg
212
+ end
213
+ end
214
+ @uuid_observer = live
215
+ end
216
+
217
+ # Notify the type observer for the given message
218
+ def notify_observer(message)
219
+ if @uuid_observer.include? message.id
220
+ observer = @uuid_observer[message.id]
221
+ message.validate!(observer[:original_message]) if observer.include?(:original_message)
222
+ Thread.new do
223
+ @uuid_observer[message.id][:block].call(message)
224
+ @uuid_observer.delete(message.id)
225
+ end
226
+ return
227
+ end
228
+
229
+ # Return if there is no observer setup for this kind of message
230
+ return unless @type_observer.include? message.typ
231
+
232
+ Thread.new do
233
+ @type_observer[message.typ][:block].call(message)
234
+ end
235
+ end
236
+
237
+ def set_observer(original, options = {}, &block)
238
+ request_timeout = options.fetch(:timeout, @timeout)
239
+ @uuid_observer[original.id] = { original_message: original, block: block, timeout: SelfSDK::Time.now + request_timeout }
240
+ end
241
+
242
+ def subscribe(type, &block)
243
+ type = SelfSDK::message_type(type) if type.is_a? Symbol
244
+ @type_observer[type] = { block: block }
245
+ end
246
+
247
+ private
248
+
249
+ # Start sthe websocket listener
250
+ def start
251
+ SelfSDK.logger.info "starting"
252
+ @mon.synchronize do
253
+ @acks["authentication"] = { waiting_cond: @mon.new_cond,
254
+ waiting: true,
255
+ timeout: SelfSDK::Time.now + @ack_timeout }
256
+ end
257
+
258
+ Thread.new do
259
+ EM.run start_connection
260
+ end
261
+
262
+ Thread.new do
263
+ loop { sleep 10; clean_timeouts }
264
+ end
265
+
266
+ Thread.new do
267
+ loop { sleep 30; ping }
268
+ end
269
+
270
+ @mon.synchronize do
271
+ @acks["authentication"][:waiting_cond].wait_while { @acks["authentication"][:waiting] }
272
+ @acks.delete("authentication")
273
+ end
274
+ end
275
+
276
+ # Cleans expired messages
277
+ def clean_timeouts
278
+ clean_observers
279
+ clean_timeouts_for(@messages)
280
+ clean_timeouts_for(@acks)
281
+ end
282
+
283
+ def clean_timeouts_for(list)
284
+ list.clone.each do |uuid, _msg|
285
+ next unless list[uuid][:timeout] < SelfSDK::Time.now
286
+
287
+ @mon.synchronize do
288
+ SelfSDK.logger.info "message response timed out #{uuid}"
289
+ list[uuid][:waiting] = false
290
+ list[uuid][:waiting_cond].broadcast
291
+ end
292
+ end
293
+ end
294
+
295
+ # Creates a websocket connection and sets up its callbacks.
296
+ def start_connection
297
+ SelfSDK.logger.info "starting listener"
298
+ @ws = Faye::WebSocket::Client.new(@url)
299
+ SelfSDK.logger.info "initialized"
300
+
301
+ @ws.on :open do |_event|
302
+ SelfSDK.logger.info "websocket connection established"
303
+ authenticate
304
+ end
305
+
306
+ @ws.on :message do |event|
307
+ on_message(event)
308
+ end
309
+
310
+ @ws.on :close do |event|
311
+ if event.code == ON_DEMAND_CLOSE_CODE
312
+ puts "client closed connection"
313
+ else
314
+ if !@auto_reconnect
315
+ raise StandardError "websocket connection closed"
316
+ end
317
+ if !@reconnection_delay.nil?
318
+ SelfSDK.logger.info "websocket connection closed (#{event.code}) #{event.reason}"
319
+ sleep @reconnection_delay
320
+ SelfSDK.logger.info "reconnecting..."
321
+ end
322
+ @reconnection_delay = 3
323
+ start_connection
324
+ end
325
+ end
326
+ end
327
+
328
+ # Pings the websocket server to keep the connection alive.
329
+ def ping
330
+ # SelfSDK.logger.info "ping"
331
+ @ws&.ping
332
+ end
333
+
334
+ # Process an event when it arrives through the websocket connection.
335
+ def on_message(event)
336
+ input = Msgproto::Message.decode(event.data.pack('c*'))
337
+ SelfSDK.logger.info " - received #{input.id} (#{input.type})"
338
+ case input.type
339
+ when :ERR
340
+ SelfSDK.logger.info "error #{input.sender} on #{input.id}"
341
+ mark_as_arrived(input.id)
342
+ when :ACK
343
+ SelfSDK.logger.info "#{input.id} acknowledged"
344
+ mark_as_acknowledged input.id
345
+ when :ACL
346
+ SelfSDK.logger.info "ACL received"
347
+ process_incomming_acl input
348
+ when :MSG
349
+ SelfSDK.logger.info "Message #{input.id} received"
350
+ process_incomming_message input
351
+ end
352
+ rescue TypeError
353
+ SelfSDK.logger.info "invalid array message"
354
+ end
355
+
356
+ def process_incomming_acl(input)
357
+ list = JSON.parse(input.recipient)
358
+
359
+ @messages['acl_list'][:response] = list
360
+ mark_as_arrived 'acl_list'
361
+ rescue StandardError => e
362
+ p "Error processing incoming ACL #{input.to_json}"
363
+ SelfSDK.logger.info e
364
+ SelfSDK.logger.info e.backtrace
365
+ nil
366
+ end
367
+
368
+ def process_incomming_message(input)
369
+ message = SelfSDK::Messages.parse(input, self)
370
+
371
+ if @messages.include? message.id
372
+ message.validate! @messages[message.id][:original_message]
373
+ @messages[message.id][:response] = message
374
+ mark_as_arrived message.id
375
+ else
376
+ SelfSDK.logger.info "Received async message #{input.id}"
377
+ message.validate! @uuid_observer[message.id][:original_message] if @uuid_observer.include? message.id
378
+ notify_observer(message)
379
+ end
380
+
381
+ @offset = input.offset
382
+ write_offset(@offset)
383
+ rescue StandardError => e
384
+ p "Error processing incoming message #{input.to_json}"
385
+ SelfSDK.logger.info e
386
+ p e.backtrace
387
+ nil
388
+ end
389
+
390
+ # Authenticates current client on the websocket server.
391
+ def authenticate
392
+ SelfSDK.logger.info "authenticating"
393
+ send_raw Msgproto::Auth.new(
394
+ type: Msgproto::MsgType::AUTH,
395
+ id: "authentication",
396
+ token: @jwt.auth_token,
397
+ device: @device_id,
398
+ offset: @offset,
399
+ )
400
+ end
401
+
402
+ def send_raw(msg)
403
+ @ws.send(msg.to_proto.bytes)
404
+ end
405
+
406
+ # Marks a message as arrived.
407
+ def mark_as_arrived(id)
408
+ # Return if no one is waiting for this message
409
+ return unless @messages.include? id
410
+
411
+ @mon.synchronize do
412
+ @messages[id][:waiting] = false
413
+ @messages[id][:waiting_cond].broadcast
414
+ end
415
+ end
416
+
417
+ # Marks a message as acknowledged by the server.
418
+ def mark_as_acknowledged(id)
419
+ return unless @acks.include? id
420
+
421
+ @mon.synchronize do
422
+ @acks[id][:waiting] = false
423
+ @acks[id][:waiting_cond].broadcast
424
+ end
425
+ end
426
+
427
+ def read_offset
428
+ return 0 unless File.exist? @offset_file
429
+
430
+ File.open(@offset_file, 'rb') do |f|
431
+ return f.read.unpack('q')[0]
432
+ end
433
+ end
434
+
435
+ def write_offset(offset)
436
+ File.open(@offset_file, 'wb') do |f|
437
+ f.write([offset].pack('q'))
438
+ end
439
+ end
440
+ end
441
+ end