selfsdk 0.0.124

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.
@@ -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