meshtastic 0.0.115 → 0.0.116

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: caa92569cfc04914d4775054daefc5382265ccdcde9791caa0c7efcb27322ce3
4
- data.tar.gz: f06f9ec597ee5636fa7faed5bfaa6023f731d561b4d46341d9486e311eb29ea1
3
+ metadata.gz: 46e1db876551e7f78c1b28f5dbd0d066346890aa31ea7e5969165b8c9142d84b
4
+ data.tar.gz: fb7c1b7ee22e0cb7b86cdcaaefc5c89d49fcd7c36f2dbcc9ac5185424bc81276
5
5
  SHA512:
6
- metadata.gz: 7ba94636af81ca286cdf239ed65249093457a666df9dd003975417e729c4179c8b010e28480424a2e263670174283326e40672ea591dbc11d50bad5a1b4043ec
7
- data.tar.gz: 575743047507a3d99fbe9556c348f09b4606c3247565d50aaf668662dd0eec3f970640a4db19bddfcbf0e71d2981355ccc1125ea77ada3cae62d78267f1c7f61
6
+ metadata.gz: ffb5a5731c4d63e46816eec4de5538aa8b28637d00c79159811bea652757aa9260f3587bd8c9db7fed2842766e28fd17943a5bfe917d6d684ada85b4bef50592
7
+ data.tar.gz: 4145d728651ca25dfaffc8f99d2cfdc3ebd17904be545c5c395a1839287207a3fbd05696f90be8755982ca574be405f77f7377577bb0d6e8ab278c8f127e1601
data/Gemfile CHANGED
@@ -18,5 +18,5 @@ gem 'rubocop', '>=1.72.1'
18
18
  gem 'rubocop-rake', '>=0.7.0'
19
19
  gem 'rubocop-rspec', '>=3.4.0'
20
20
  gem 'rvm', '1.11.3.9'
21
- gem 'tty-prompt', '0.23.1'
21
+ gem 'uart', '1.0.0'
22
22
  gem 'yard', '0.9.37'
@@ -6,7 +6,6 @@ require 'json'
6
6
  require 'mqtt'
7
7
  require 'openssl'
8
8
  require 'securerandom'
9
- require 'tty-prompt'
10
9
 
11
10
  # Avoiding Namespace Collisions
12
11
  MQTTClient = MQTT::Client
@@ -15,7 +14,7 @@ MQTTClient = MQTT::Client
15
14
  module Meshtastic
16
15
  module MQTT
17
16
  # Supported Method Parameters::
18
- # mqtt_obj = Meshtastic::MQQT.connect(
17
+ # mqtt_obj = Meshtastic::MQTT.connect(
19
18
  # host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
20
19
  # port: 'optional - mqtt port (defaults: 1883)',
21
20
  # tls: 'optional - use TLS (default: false)',
@@ -58,7 +57,7 @@ module Meshtastic
58
57
  end
59
58
 
60
59
  # Supported Method Parameters::
61
- # Meshtastic::MQQT.subscribe(
60
+ # Meshtastic::MQTT.subscribe(
62
61
  # mqtt_obj: 'required - mqtt_obj returned from #connect method'
63
62
  # root_topic: 'optional - root topic (default: msh)',
64
63
  # region: 'optional - region e.g. 'US/VA', etc (default: US)',
@@ -145,6 +144,11 @@ module Meshtastic
145
144
  if encrypted_message.to_s.length.positive? &&
146
145
  message[:topic]
147
146
 
147
+ # if message[:pki_encrypted]
148
+ # # TODO: Display Decrypted PKI Message
149
+ # public_key = message[:public_key]
150
+ # dec_public_key = Base64.strict_decode64(public_key)
151
+ # else
148
152
  packet_id = message[:id]
149
153
  packet_from = message[:from]
150
154
 
@@ -164,6 +168,7 @@ module Meshtastic
164
168
  cipher.iv = nonce
165
169
 
166
170
  decrypted = cipher.update(encrypted_message) + cipher.final
171
+ # end
167
172
  message[:decoded] = Meshtastic::Data.decode(decrypted).to_h
168
173
  message[:encrypted] = :decrypted
169
174
  end
@@ -0,0 +1,351 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'geocoder'
5
+ require 'io/wait'
6
+ require 'json'
7
+ require 'openssl'
8
+ require 'securerandom'
9
+ require 'uart'
10
+
11
+ # Plugin used to interact with Meshtastic nodes
12
+ module Meshtastic
13
+ module Serial
14
+ @session_data = []
15
+
16
+ # Supported Method Parameters::
17
+ # serial_obj = Meshtastic::Serial.connect(
18
+ # block_dev: 'optional - serial block device path (defaults to /dev/ttyUSB0)',
19
+ # baud: 'optional - (defaults to 9600)',
20
+ # data_bits: 'optional - (defaults to 8)',
21
+ # stop_bits: 'optional - (defaults to 1)',
22
+ # parity: 'optional - :even|:mark|:odd|:space|:none (defaults to :none)'
23
+ # )
24
+
25
+ public_class_method def self.connect(opts = {})
26
+ block_dev = opts[:block_dev] ||= '/dev/ttyUSB0'
27
+ raise "Invalid block device: #{block_dev}" unless File.exist?(block_dev)
28
+
29
+ baud = opts[:baud] ||= 9_600
30
+ data_bits = opts[:data_bits] ||= 8
31
+ stop_bits = opts[:stop_bits] ||= 1
32
+ parity = opts[:parity] ||= :none
33
+
34
+ case parity.to_s.to_sym
35
+ when :even
36
+ parity = 'E'
37
+ when :odd
38
+ parity = 'O'
39
+ when :none
40
+ parity = 'N'
41
+ end
42
+ raise "Invalid parity: #{opts[:parity]}" if parity.nil?
43
+
44
+ mode = "#{data_bits}#{parity}#{stop_bits}"
45
+ puts mode
46
+
47
+ serial_conn = UART.open(
48
+ block_dev,
49
+ baud,
50
+ mode
51
+ )
52
+
53
+ serial_obj = {}
54
+ serial_obj[:serial_conn] = serial_conn
55
+ serial_obj[:session_thread] = init_session_thread(
56
+ serial_conn: serial_conn
57
+ )
58
+
59
+ serial_obj
60
+ rescue StandardError => e
61
+ disconnect(serial_obj: serial_obj) unless serial_obj.nil?
62
+ raise e
63
+ end
64
+
65
+ # Supported Method Parameters::
66
+ # Meshtastic::Serial.subscribe(
67
+ # serial_obj: 'required - serial_obj returned from #connect method'
68
+ # root_topic: 'optional - root topic (default: msh)',
69
+ # region: 'optional - region e.g. 'US/VA', etc (default: US)',
70
+ # channel_topic: 'optional - channel ID path e.g. "2/stat/#" (default: "2/e/LongFast/#")',
71
+ # psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: "AQ==" })',
72
+ # qos: 'optional - quality of service (default: 0)',
73
+ # exclude: 'optional - comma-delimited string(s) to exclude in message (default: nil)',
74
+ # include: 'optional - comma-delimited string(s) to include on in message (default: nil)',
75
+ # gps_metadata: 'optional - include GPS metadata in output (default: false)',
76
+ # include_raw: 'optional - include raw packet data in output (default: false)'
77
+ # )
78
+
79
+ public_class_method def self.subscribe(opts = {})
80
+ serial_obj = opts[:serial_obj]
81
+ root_topic = opts[:root_topic] ||= 'msh'
82
+ region = opts[:region] ||= 'US'
83
+ channel_topic = opts[:channel_topic] ||= '2/e/LongFast/#'
84
+ # TODO: Support Array of PSKs and attempt each until decrypted
85
+
86
+ public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
87
+ psks = opts[:psks] ||= { LongFast: public_psk }
88
+ raise 'ERROR: psks parameter must be a hash of :channel_id => psk key value pairs' unless psks.is_a?(Hash)
89
+
90
+ psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
91
+ psks = Meshtastic.get_cipher_keys(psks: psks)
92
+
93
+ qos = opts[:qos] ||= 0
94
+ json = opts[:json] ||= false
95
+ exclude = opts[:exclude]
96
+ include = opts[:include]
97
+ gps_metadata = opts[:gps_metadata] ||= false
98
+ include_raw = opts[:include_raw] ||= false
99
+
100
+ # NOTE: Use MQTT Explorer for topic discovery
101
+ full_topic = "#{root_topic}/#{region}/#{channel_topic}"
102
+ full_topic = "#{root_topic}/#{region}" if region == '#'
103
+ puts "Subscribing to: #{full_topic}"
104
+ serial_obj.subscribe(full_topic, qos)
105
+
106
+ # MQTT::ProtocolException: No Ping Response received for 23 seconds (MQTT::ProtocolException)
107
+
108
+ include_arr = include.to_s.split(',').map(&:strip)
109
+ exclude_arr = exclude.to_s.split(',').map(&:strip)
110
+ serial_obj.get_packet do |packet_bytes|
111
+ raw_packet = packet_bytes.to_s if include_raw
112
+ raw_topic = packet_bytes.topic ||= ''
113
+ raw_payload = packet_bytes.payload ||= ''
114
+
115
+ begin
116
+ disp = false
117
+ decoded_payload_hash = {}
118
+ message = {}
119
+ stdout_message = ''
120
+
121
+ if json
122
+ decoded_payload_hash = JSON.parse(raw_payload, symbolize_names: true)
123
+ else
124
+ # decoded_payload = Meshtastic::ToRadio.decode(raw_payload)
125
+ decoded_payload = Meshtastic::FromRadio.decode(raw_payload)
126
+ decoded_payload_hash = decoded_payload.to_h
127
+ end
128
+
129
+ next unless decoded_payload_hash[:packet].is_a?(Hash)
130
+
131
+ message = decoded_payload_hash[:packet] if decoded_payload_hash.keys.include?(:packet)
132
+ message[:topic] = raw_topic
133
+ message[:node_id_from] = "!#{message[:from].to_i.to_s(16)}"
134
+ message[:node_id_to] = "!#{message[:to].to_i.to_s(16)}"
135
+ if message.keys.include?(:rx_time)
136
+ rx_time_int = message[:rx_time]
137
+ if rx_time_int.is_a?(Integer)
138
+ rx_time_utc = Time.at(rx_time_int).utc.to_s
139
+ message[:rx_time_utc] = rx_time_utc
140
+ end
141
+ end
142
+
143
+ if message.keys.include?(:public_key)
144
+ raw_public_key = message[:public_key]
145
+ message[:public_key] = Base64.strict_encode64(raw_public_key)
146
+ end
147
+
148
+ # If encrypted_message is not nil, then decrypt
149
+ # the message prior to decoding.
150
+ encrypted_message = message[:encrypted]
151
+ if encrypted_message.to_s.length.positive? &&
152
+ message[:topic]
153
+
154
+ # if message[:pki_encrypted]
155
+ # # TODO: Display Decrypted PKI Message
156
+ # public_key = message[:public_key]
157
+ # dec_public_key = Base64.strict_decode64(public_key)
158
+ # else
159
+ packet_id = message[:id]
160
+ packet_from = message[:from]
161
+
162
+ nonce_packet_id = [packet_id].pack('V').ljust(8, "\x00")
163
+ nonce_from_node = [packet_from].pack('V').ljust(8, "\x00")
164
+ nonce = "#{nonce_packet_id}#{nonce_from_node}"
165
+
166
+ psk = psks[:LongFast]
167
+ target_channel = message[:topic].split('/')[-2].to_sym
168
+ psk = psks[target_channel] if psks.keys.include?(target_channel)
169
+ dec_psk = Base64.strict_decode64(psk)
170
+
171
+ cipher = OpenSSL::Cipher.new('AES-128-CTR')
172
+ cipher = OpenSSL::Cipher.new('AES-256-CTR') if dec_psk.length == 32
173
+ cipher.decrypt
174
+ cipher.key = dec_psk
175
+ cipher.iv = nonce
176
+
177
+ decrypted = cipher.update(encrypted_message) + cipher.final
178
+ # end
179
+ message[:decoded] = Meshtastic::Data.decode(decrypted).to_h
180
+ message[:encrypted] = :decrypted
181
+ end
182
+
183
+ if message[:decoded]
184
+ # payload = Meshtastic::Data.decode(message[:decoded][:payload]).to_h
185
+ payload = message[:decoded][:payload]
186
+ msg_type = message[:decoded][:portnum]
187
+ message[:decoded][:payload] = Meshtastic.decode_payload(
188
+ payload: payload,
189
+ msg_type: msg_type,
190
+ gps_metadata: gps_metadata
191
+ )
192
+ end
193
+
194
+ message[:raw_packet] = raw_packet if include_raw
195
+ decoded_payload_hash[:packet] = message
196
+ unless block_given?
197
+ message[:stdout] = 'pretty'
198
+ stdout_message = JSON.pretty_generate(decoded_payload_hash)
199
+ end
200
+ rescue Encoding::CompatibilityError,
201
+ Google::Protobuf::ParseError,
202
+ JSON::GeneratorError,
203
+ ArgumentError => e
204
+
205
+ unless e.is_a?(Encoding::CompatibilityError)
206
+ message[:decrypted] = e.message if e.message.include?('key must be')
207
+ message[:decrypted] = 'unable to decrypt - psk?' if e.message.include?('occurred during parsing')
208
+ decoded_payload_hash[:packet] = message
209
+ unless block_given?
210
+ puts "WARNING: #{e.inspect} - MSG IS >>>"
211
+ # puts e.backtrace
212
+ message[:stdout] = 'inspect'
213
+ stdout_message = decoded_payload_hash.inspect
214
+ end
215
+ end
216
+
217
+ next
218
+ ensure
219
+ include_arr = [message[:id].to_s] if include_arr.empty?
220
+ if message.is_a?(Hash)
221
+ flat_message = message.values.join(' ')
222
+
223
+ disp = true if exclude_arr.none? { |exclude| flat_message.include?(exclude) } && (
224
+ include_arr.first == message[:id] ||
225
+ include_arr.all? { |include| flat_message.include?(include) }
226
+ )
227
+
228
+ if disp
229
+ if block_given?
230
+ yield decoded_payload_hash
231
+ else
232
+ puts "\n"
233
+ puts '-' * 80
234
+ puts 'MSG:'
235
+ puts stdout_message
236
+ puts '-' * 80
237
+ puts "\n\n\n"
238
+ end
239
+ # else
240
+ # print '.'
241
+ end
242
+ end
243
+ end
244
+ end
245
+ rescue Interrupt
246
+ puts "\nCTRL+C detected. Exiting..."
247
+ rescue StandardError => e
248
+ raise e
249
+ ensure
250
+ serial_obj.disconnect if serial_obj
251
+ end
252
+
253
+ # Supported Method Parameters::
254
+ # Meshtastic.send_text(
255
+ # serial_obj: 'required - serial_obj returned from #connect method',
256
+ # from: 'required - From ID (String or Integer) (Default: "!00000b0b")',
257
+ # to: 'optional - Destination ID (Default: "!ffffffff")',
258
+ # topic: 'optional - topic to publish to (Default: "msh/US/2/e/LongFast/1")',
259
+ # channel: 'optional - channel (Default: 6)',
260
+ # text: 'optional - Text Message (Default: SYN)',
261
+ # want_ack: 'optional - Want Acknowledgement (Default: false)',
262
+ # want_response: 'optional - Want Response (Default: false)',
263
+ # hop_limit: 'optional - Hop Limit (Default: 3)',
264
+ # on_response: 'optional - Callback on Response',
265
+ # psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: "AQ==" })'
266
+ # )
267
+ public_class_method def self.send_text(opts = {})
268
+ serial_obj = opts[:serial_obj]
269
+ topic = opts[:topic] ||= 'msh/US/2/e/LongFast/#'
270
+ opts[:via] = :radio
271
+
272
+ # TODO: Implement chunked message to deal with large messages
273
+ protobuf_text = Meshtastic.send_text(opts)
274
+
275
+ serial_obj.publish(topic, protobuf_text)
276
+ rescue StandardError => e
277
+ raise e
278
+ end
279
+
280
+ # Supported Method Parameters::
281
+ # serial_obj = Meshtastic.disconnect(
282
+ # serial_obj: 'required - serial_obj returned from #connect method'
283
+ # )
284
+ public_class_method def self.disconnect(opts = {})
285
+ serial_obj = opts[:serial_obj]
286
+
287
+ serial_obj.disconnect if serial_obj
288
+ nil
289
+ rescue StandardError => e
290
+ raise e
291
+ end
292
+
293
+ # Author(s):: 0day Inc. <support@0dayinc.com>
294
+
295
+ public_class_method def self.authors
296
+ "AUTHOR(S):
297
+ 0day Inc. <support@0dayinc.com>
298
+ "
299
+ end
300
+
301
+ # Display Usage for this Module
302
+
303
+ public_class_method def self.help
304
+ puts "USAGE:
305
+ serial_obj = #{self}.connect(
306
+ host: 'optional - mqtt host (default: mqtt.meshtastic.org)',
307
+ port: 'optional - mqtt port (defaults: 1883)',
308
+ tls: 'optional - use TLS (default: false)',
309
+ username: 'optional - mqtt username (default: meshdev)',
310
+ password: 'optional - (default: large4cats)',
311
+ client_id: 'optional - client ID (default: random 4-byte hex string)',
312
+ keep_alive: 'optional - keep alive interval (default: 15)',
313
+ ack_timeout: 'optional - acknowledgement timeout (default: 30)'
314
+ )
315
+
316
+ #{self}.subscribe(
317
+ serial_obj: 'required - serial_obj object returned from #connect method',
318
+ root_topic: 'optional - root topic (default: msh)',
319
+ region: 'optional - region e.g. 'US/VA', etc (default: US)',
320
+ channel_topic: 'optional - channel ID path e.g. '2/stat/#' (default: '2/e/LongFast/#')',
321
+ psks: 'optional - hash of :channel_id => psk key value pairs (default: { LongFast: 'AQ==' })',
322
+ qos: 'optional - quality of service (default: 0)',
323
+ json: 'optional - JSON output (default: false)',
324
+ exclude: 'optional - comma-delimited string(s) to exclude in message (default: nil)',
325
+ include: 'optional - comma-delimited string(s) to include on in message (default: nil)',
326
+ gps_metadata: 'optional - include GPS metadata in output (default: false)'
327
+ )
328
+
329
+ #{self}.send_text(
330
+ serial_obj: 'required - serial_obj returned from #connect method',
331
+ from: 'required - From ID (String or Integer) (Default: \"!00000b0b\")',
332
+ to: 'optional - Destination ID (Default: \"!ffffffff\")',
333
+ topic: 'optional - topic to publish to (default: 'msh/US/2/e/LongFast/1')',
334
+ channel: 'optional - channel (Default: 6)',
335
+ text: 'optional - Text Message (Default: SYN)',
336
+ want_ack: 'optional - Want Acknowledgement (Default: false)',
337
+ want_response: 'optional - Want Response (Default: false)',
338
+ hop_limit: 'optional - Hop Limit (Default: 3)',
339
+ on_response: 'optional - Callback on Response',
340
+ psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: 'AQ==' })'
341
+ )
342
+
343
+ serial_obj = #{self}.disconnect(
344
+ serial_obj: 'required - serial_obj object returned from #connect method'
345
+ )
346
+
347
+ #{self}.authors
348
+ "
349
+ end
350
+ end
351
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Meshtastic
4
- VERSION = '0.0.115'
4
+ VERSION = '0.0.116'
5
5
  end
data/lib/meshtastic.rb CHANGED
@@ -45,6 +45,7 @@ module Meshtastic
45
45
  autoload :Portnums, 'meshtastic/portnums'
46
46
  autoload :RemoteHardware, 'meshtastic/remote_hardware'
47
47
  autoload :RTTTL, 'meshtastic/rtttl'
48
+ autoload :Serial, 'meshtastic/serial'
48
49
  autoload :Storeforward, 'meshtastic/storeforward'
49
50
  autoload :Telemetry, 'meshtastic/telemetry'
50
51
  autoload :Xmodem, 'meshtastic/xmodem'
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Meshtastic::Serial do
6
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meshtastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.115
4
+ version: 0.0.116
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -178,19 +178,19 @@ dependencies:
178
178
  - !ruby/object:Gem::Version
179
179
  version: 1.11.3.9
180
180
  - !ruby/object:Gem::Dependency
181
- name: tty-prompt
181
+ name: uart
182
182
  requirement: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - '='
185
185
  - !ruby/object:Gem::Version
186
- version: 0.23.1
186
+ version: 1.0.0
187
187
  type: :runtime
188
188
  prerelease: false
189
189
  version_requirements: !ruby/object:Gem::Requirement
190
190
  requirements:
191
191
  - - '='
192
192
  - !ruby/object:Gem::Version
193
- version: 0.23.1
193
+ version: 1.0.0
194
194
  - !ruby/object:Gem::Dependency
195
195
  name: yard
196
196
  requirement: !ruby/object:Gem::Requirement
@@ -267,6 +267,7 @@ files:
267
267
  - lib/meshtastic/remote_hardware_pb.rb
268
268
  - lib/meshtastic/rtttl.rb
269
269
  - lib/meshtastic/rtttl_pb.rb
270
+ - lib/meshtastic/serial.rb
270
271
  - lib/meshtastic/storeforward.rb
271
272
  - lib/meshtastic/storeforward_pb.rb
272
273
  - lib/meshtastic/telemetry.rb
@@ -314,6 +315,7 @@ files:
314
315
  - spec/lib/meshtastic/remote_hardware_spec.rb
315
316
  - spec/lib/meshtastic/rtttl_pb_spec.rb
316
317
  - spec/lib/meshtastic/rtttl_spec.rb
318
+ - spec/lib/meshtastic/serial_spec.rb
317
319
  - spec/lib/meshtastic/storeforward_pb_spec.rb
318
320
  - spec/lib/meshtastic/storeforward_spec.rb
319
321
  - spec/lib/meshtastic/telemetry_pb_spec.rb