meshtastic 0.0.40 → 0.0.42

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: 9dc432f90a8545cad1027f5514f0676a60d776f617db87b394436e1657693e98
4
- data.tar.gz: 5a461b32da2516b164ab789f2a27653b5f46a4f7d36020ddca1ba161931162e0
3
+ metadata.gz: 3ffe0a1e8528090281aedce5b132f1f57aa82db0c8c46131cff53d00004e4dcd
4
+ data.tar.gz: 56bb82c58cdcbced0c401802180931aa4625e4314e4954ab30349811a614d399
5
5
  SHA512:
6
- metadata.gz: 1094355139b2e703dda03bf60ff7034aeb94e1d7d335ba8cd73aae0d9c3bae4c64e61cfb3b8e2dcbdd8894174ab5e3f77b29fde04617fedc3f578b5e469a2678
7
- data.tar.gz: d5677d5b337b4f1b8cbff41da88eafaeab8377f3235085f6ba2df384b7c19dd24cbece877d1d6c39f4cf3c4b9d6674f93ed6cb783bc62bd739e12379f3a8a552
6
+ metadata.gz: dd84aa7e0607992de00be53a0f8ccb2d69077004dd21ebc2838be38582064a797c0f13317c0abd375ab67199dd494640187895384ea74110c77c9b478a218654
7
+ data.tar.gz: a1135c741f44f38765421ea2020173b40395478fffdbb24da760d75f8483920f99cf2f0d110f7173fb9617e7522a644dc418f937aef79a35f1b04d89ed94737c
data/Gemfile CHANGED
@@ -13,7 +13,7 @@ gemspec
13
13
  # to review these custom flags (e.g. pg, serialport, etc).
14
14
  gem 'bundler', '>=2.5.3'
15
15
  gem 'bundle-audit', '0.1.0'
16
- gem 'geocoder', '1.8.2'
16
+ gem 'geocoder', '1.8.3'
17
17
  gem 'google-protobuf', '3.21.12'
18
18
  gem 'mqtt', '0.6.0'
19
19
  gem 'rake', '13.2.1'
@@ -21,6 +21,7 @@ gem 'rdoc', '6.6.3.1'
21
21
  gem 'rspec', '3.13.0'
22
22
  gem 'rubocop', '1.63.4'
23
23
  gem 'rubocop-rake', '0.6.0'
24
- gem 'rubocop-rspec', '2.29.1'
24
+ gem 'rubocop-rspec', '2.29.2'
25
25
  gem 'rvm', '1.11.3.9'
26
+ gem 'tty-prompt', '0.23.1'
26
27
  gem 'yard', '0.9.36'
@@ -6,6 +6,7 @@ require 'json'
6
6
  require 'mqtt'
7
7
  require 'openssl'
8
8
  require 'securerandom'
9
+ require 'tty-prompt'
9
10
 
10
11
  # Avoiding Namespace Collisions
11
12
  MQTTClient = MQTT::Client
@@ -42,16 +43,156 @@ module Meshtastic
42
43
  raise e
43
44
  end
44
45
 
46
+ # Supported Method Parameters::
47
+ # Meshtastic::MQQT.get_cipher_keys(
48
+ # psks: 'required - hash of channel / pre-shared key value pairs'
49
+ # )
50
+
51
+ private_class_method def self.get_cipher_keys(opts = {})
52
+ psks = opts[:psks]
53
+
54
+ psks.each_key do |key|
55
+ psk = psks[key]
56
+ padded_psk = psk.ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
57
+ replaced_psk = padded_psk.gsub('-', '+').gsub('_', '/')
58
+ psks[key] = replaced_psk
59
+ end
60
+
61
+ psks
62
+ rescue StandardError => e
63
+ raise e
64
+ end
65
+
66
+ # Supported Method Parameters::
67
+ # Meshtastic::MQQT.recursively_decode_payloads(
68
+ # object: 'required - object to recursively decode',
69
+ # msg_type: 'required - message type (e.g. :TEXT_MESSAGE_APP)',
70
+ # gps_metadata: 'optional - include GPS metadata in output (default: false)'
71
+ # )
72
+
73
+ private_class_method def self.recursively_decode_payloads(opts = {})
74
+ object = opts[:object]
75
+ msg_type = opts[:msg_type]
76
+ gps_metadata = opts[:gps_metadata]
77
+
78
+ # puts "OBJECT: #{object.inspect}"
79
+ object = Meshtastic::Data.decode(object).to_h if object.is_a?(String)
80
+
81
+ object.each do |key, value|
82
+ if value.is_a?(Hash)
83
+ # Recursive call if the value is a hash
84
+ recursively_decode_payloads(object: value)
85
+ elsif value.is_a?(Array)
86
+ # Process each element if it's an array
87
+ value.each do |item|
88
+ recursively_decode_payloads(object: item) if item.is_a?(Hash)
89
+ end
90
+ end
91
+ # Check if the key matches 'payload' and decode
92
+ case key
93
+ when :latitude_i
94
+ object[:latitude] = object[:latitude_i] * 0.0000001
95
+ when :longitude_i
96
+ object[:longitude] = object[:longitude_i] * 0.0000001
97
+ when :macaddr
98
+ object[key] = object[key].bytes.map { |byte| byte.to_s(16).rjust(2, '0') }.join(':')
99
+ when :payload
100
+ case msg_type
101
+ when :ADMIN_APP
102
+ decoder = Meshtastic::AdminMessage.decode(payload)
103
+ when :ATAK_FORWARDER, :ATAK_PLUGIN
104
+ decoder = Meshtastic::TAKPacket
105
+ # when :AUDIO_APP
106
+ # decoder = Meshtastic::Audio
107
+ when :DETECTION_SENSOR_APP
108
+ decoder = Meshtastic::DeviceState
109
+ # when :IP_TUNNEL_APP
110
+ # decoder = Meshtastic::IpTunnel
111
+ when :MAP_REPORT_APP
112
+ decoder = Meshtastic::MapReport
113
+ # when :MAX
114
+ # decoder = Meshtastic::Max
115
+ when :NEIGHBORINFO_APP
116
+ decoder = Meshtastic::NeighborInfo
117
+ when :NODEINFO_APP
118
+ decoder = Meshtastic::User
119
+ when :PAXCOUNTER_APP
120
+ decoder = Meshtastic::Paxcount
121
+ when :POSITION_APP
122
+ decoder = Meshtastic::Position
123
+ # when :PRIVATE_APP
124
+ # decoder = Meshtastic::Private
125
+ when :RANGE_TEST_APP
126
+ # Unsure if this is the correct protobuf object
127
+ decoder = Meshtastic::FromRadio
128
+ when :REMOTE_HARDWARE_APP
129
+ decoder = Meshtastic::HardwareMessage
130
+ # when :REPLY_APP
131
+ # decoder = Meshtastic::Reply
132
+ when :ROUTING_APP
133
+ decoder = Meshtastic::Routing
134
+ when :SERIAL_APP
135
+ decoder = Meshtastic::SerialConnectionStatus
136
+ when :SIMULATOR_APP
137
+ decoder = Meshtastic::Compressed
138
+ when :STORE_FORWARD_APP
139
+ decoder = Meshtastic::StoreAndForward
140
+ when :TEXT_MESSAGE_APP
141
+ # Unsure if this is the correct protobuf object
142
+ # decoder = Meshtastic::MqttClientProxyMessage
143
+ decoder = Meshtastic::Data
144
+ when :TELEMETRY_APP
145
+ decoder = Meshtastic::Telemetry
146
+ when :TRACEROUTE_APP
147
+ decoder = Meshtastic::RouteDiscovery
148
+ when :UNKNOWN_APP
149
+ decoder = Meshtastic::Data.decode
150
+ when :WAYPOINT_APP
151
+ decoder = Meshtastic::Waypoint
152
+ # when :ZPS_APP
153
+ # decoder = Meshtastic::Zps
154
+ else
155
+ puts "WARNING: Unknown message type: #{msg_type}"
156
+ decoder = Meshtastic::Data.decode
157
+ end
158
+
159
+ # begin
160
+ # # If the value is base64 encoded, base64 decode and then decode
161
+ # base64_decoded_value = Base64.strict_decode64(value)
162
+ # object[key] = decoder.decode(base64_decoded_value).to_h
163
+ # rescue ArgumentError => e
164
+ # # Otherwise, just decode the value
165
+ object[key] = decoder.decode(value).to_h
166
+ # end
167
+
168
+ end
169
+
170
+ next unless gps_metadata && object[:latitude] && object[:longitude]
171
+
172
+ object[:gps_metadata] = gps_search(
173
+ lat: object[:latitude],
174
+ lon: object[:longitude]
175
+ ).first.data
176
+ end
177
+
178
+ object
179
+ rescue Google::Protobuf::ParseError
180
+ object
181
+ rescue StandardError => e
182
+ raise e
183
+ end
184
+
45
185
  # Supported Method Parameters::
46
186
  # Meshtastic::MQQT.subscribe(
47
187
  # mqtt_obj: 'required - mqtt_obj returned from #connect method'
48
188
  # root_topic: 'optional - root topic (default: msh)',
49
189
  # region: 'optional - region e.g. 'US/VA', etc (default: US)',
50
190
  # channel: 'optional - channel name e.g. "2/stat/#" (default: "2/e/LongFast/#")',
51
- # psk: 'optional - channel pre-shared key (default: AQ==)',
191
+ # psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: "AQ==" })',
52
192
  # qos: 'optional - quality of service (default: 0)',
53
193
  # filter: 'optional - comma-delimited string(s) to filter on in message (default: nil)',
54
- # gps_metadata: 'optional - include GPS metadata in output (default: false)'
194
+ # gps_metadata: 'optional - include GPS metadata in output (default: false)',
195
+ # include_raw: 'optional - include raw packet data in output (default: false)'
55
196
  # )
56
197
 
57
198
  public_class_method def self.subscribe(opts = {})
@@ -60,34 +201,30 @@ module Meshtastic
60
201
  region = opts[:region] ||= 'US'
61
202
  channel = opts[:channel] ||= '2/e/LongFast/#'
62
203
  # TODO: Support Array of PSKs and attempt each until decrypted
63
- psk = opts[:psk] ||= 'AQ=='
204
+
205
+ public_psk = '1PG7OiApB1nwvP+rz05pAQ=='
206
+ psks = opts[:psks] ||= { LongFast: public_psk }
207
+ raise 'ERROR: psks parameter must be a hash of :channel => psk key value pairs' unless psks.is_a?(Hash)
208
+
209
+ psks[:LongFast] = public_psk if psks[:LongFast] == 'AQ=='
210
+ psks = get_cipher_keys(psks: psks)
211
+
64
212
  qos = opts[:qos] ||= 0
65
213
  json = opts[:json] ||= false
66
214
  filter = opts[:filter]
67
215
  gps_metadata = opts[:gps_metadata] ||= false
216
+ include_raw = opts[:include_raw] ||= false
68
217
 
69
- # TODO: Find JSON URI for this
218
+ # NOTE: Use MQTT Explorer for topic discovery
70
219
  full_topic = "#{root_topic}/#{region}/#{channel}"
71
220
  puts "Subscribing to: #{full_topic}"
72
221
  mqtt_obj.subscribe(full_topic, qos)
73
222
 
74
- # Decrypt the message
75
- # Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification.
76
-
77
- # Actual pre-shared key for LongFast channel
78
- psk = '1PG7OiApB1nwvP+rz05pAQ==' if psk == 'AQ=='
79
- padded_psk = psk.ljust(psk.length + ((4 - (psk.length % 4)) % 4), '=')
80
- replaced_psk = padded_psk.gsub('-', '+').gsub('_', '/')
81
- psk = replaced_psk
82
- dec_psk = Base64.strict_decode64(psk)
83
-
84
- # cipher = OpenSSL::Cipher.new('AES-256-CTR')
85
- cipher = OpenSSL::Cipher.new('AES-128-CTR')
86
223
  filter_arr = filter.to_s.split(',').map(&:strip)
87
224
  mqtt_obj.get_packet do |packet_bytes|
88
- # raw_packet = packet_bytes.to_s.b
225
+ raw_packet = packet_bytes.to_s.b if include_raw
89
226
  raw_topic = packet_bytes.topic ||= ''
90
- raw_payload = packet_bytes.payload
227
+ raw_payload = packet_bytes.payload ||= ''
91
228
 
92
229
  begin
93
230
  disp = false
@@ -107,116 +244,45 @@ module Meshtastic
107
244
  message[:node_id_from] = "!#{message[:from].to_i.to_s(16)}"
108
245
  message[:node_id_to] = "!#{message[:to].to_i.to_s(16)}"
109
246
 
247
+ # If encrypted_message is not nil, then decrypt
248
+ # the message prior to decoding.
110
249
  encrypted_message = message[:encrypted]
111
- # If encrypted_message is not nil, then decrypt the message
112
250
  if encrypted_message.to_s.length.positive?
113
251
  packet_id = message[:id]
114
252
  packet_from = message[:from]
253
+
115
254
  nonce_packet_id = [packet_id].pack('V').ljust(8, "\x00")
116
255
  nonce_from_node = [packet_from].pack('V').ljust(8, "\x00")
117
256
  nonce = "#{nonce_packet_id}#{nonce_from_node}".b
118
257
 
119
- # Decrypt the message
120
- # Key must be 32 bytes
121
- # IV mustr be 16 bytes
258
+ psk = psks[:LongFast]
259
+ target_chanel = decoded_payload_hash[:channel_id].to_s.to_sym
260
+ psk = psks[target_chanel] if psks.keys.include?(target_chanel)
261
+ dec_psk = Base64.strict_decode64(psk)
262
+
263
+ cipher = OpenSSL::Cipher.new('AES-128-CTR')
264
+ cipher = OpenSSL::Cipher.new('AES-256-CTR') if dec_psk.length == 32
122
265
  cipher.decrypt
123
266
  cipher.key = dec_psk
124
267
  cipher.iv = nonce
125
268
 
126
269
  decrypted = cipher.update(encrypted_message) + cipher.final
127
- message[:decrypted] = decrypted
270
+ message[:decoded] = Meshtastic::Data.decode(decrypted).to_h
271
+ message[:encrypted] = :decrypted
128
272
  end
129
273
 
130
274
  if message[:decoded]
275
+ # payload = Meshtastic::Data.decode(message[:decoded][:payload]).to_h
131
276
  payload = message[:decoded][:payload]
132
-
133
277
  msg_type = message[:decoded][:portnum]
134
- case msg_type
135
- when :ADMIN_APP
136
- pb_obj = Meshtastic::AdminMessage.decode(payload)
137
- when :ATAK_FORWARDER, :ATAK_PLUGIN
138
- pb_obj = Meshtastic::TAKPacket.decode(payload)
139
- # when :AUDIO_APP
140
- # pb_obj = Meshtastic::Audio.decode(payload)
141
- when :DETECTION_SENSOR_APP
142
- pb_obj = Meshtastic::DeviceState.decode(payload)
143
- # when :IP_TUNNEL_APP
144
- # pb_obj = Meshtastic::IpTunnel.decode(payload)
145
- when :MAP_REPORT_APP
146
- pb_obj = Meshtastic::MapReport.decode(payload)
147
- # when :MAX
148
- # pb_obj = Meshtastic::Max.decode(payload)
149
- when :NEIGHBORINFO_APP
150
- pb_obj = Meshtastic::NeighborInfo.decode(payload)
151
- when :NODEINFO_APP
152
- pb_obj = Meshtastic::User.decode(payload)
153
- when :PAXCOUNTER_APP
154
- pb_obj = Meshtastic::Paxcount.decode(payload)
155
- when :POSITION_APP
156
- pb_obj = Meshtastic::Position.decode(payload)
157
- # when :PRIVATE_APP
158
- # pb_obj = Meshtastic::Private.decode(payload)
159
- when :RANGE_TEST_APP
160
- # Unsure if this is the correct protobuf object
161
- pb_obj = Meshtastic::FromRadio.decode(payload)
162
- when :REMOTE_HARDWARE_APP
163
- pb_obj = Meshtastic::HardwareMessage.decode(payload)
164
- # when :REPLY_APP
165
- # pb_obj = Meshtastic::Reply.decode(payload)
166
- when :ROUTING_APP
167
- pb_obj = Meshtastic::Routing.decode(payload)
168
- when :SERIAL_APP
169
- pb_obj = Meshtastic::SerialConnectionStatus.decode(payload)
170
- when :SIMULATOR_APP,
171
- :TEXT_MESSAGE_COMPRESSED_APP
172
- # Unsure if this is the correct protobuf object
173
- # for TEXT_MESSAGE_COMPRESSED_APP
174
- pb_obj = Meshtastic::Compressed.decode(payload)
175
- when :STORE_FORWARD_APP
176
- pb_obj = Meshtastic::StoreAndForward.decode(payload)
177
- when :TEXT_MESSAGE_APP
178
- # Unsure if this is the correct protobuf object
179
- # pb_obj = Meshtastic::MqttClientProxyMessage.decode(payload)
180
- pb_obj = Meshtastic::Data.decode(payload)
181
- when :TELEMETRY_APP
182
- pb_obj = Meshtastic::Telemetry.decode(payload)
183
- when :TRACEROUTE_APP
184
- pb_obj = Meshtastic::RouteDiscovery.decode(payload)
185
- # when :UNKNOWN_APP
186
- # pb_obj = Meshtastic.Unknown.decode(payload)
187
- when :WAYPOINT_APP
188
- pb_obj = Meshtastic::Waypoint.decode(payload)
189
- # when :ZPS_APP
190
- # pb_obj = Meshtastic::Zps.decode(payload)
191
- else
192
- puts "WARNING: Unknown message type: #{msg_type}"
193
- end
194
- # Overwrite the payload with the decoded protobuf object
195
- # message[:decoded][:payload] = pb_obj.to_h unless msg_type == :TRACEROUTE_APP
196
- message[:decoded][:payload] = pb_obj.to_h
197
- if message[:decoded][:payload].keys.include?(:latitude_i) &&
198
- message[:decoded][:payload].keys.include?(:longitude_i) &&
199
- gps_metadata
200
-
201
- latitude = pb_obj.to_h[:latitude_i] * 0.0000001
202
- longitude = pb_obj.to_h[:longitude_i] * 0.0000001
203
- message[:decoded][:payload][:gps_metadata] = gps_search(
204
- lat: latitude,
205
- lon: longitude
206
- ).first.data
207
- end
208
-
209
- # If we there's a mac address, make it look like one.
210
- if message[:decoded][:payload].keys.include?(:macaddr)
211
- macaddr = message[:decoded][:payload][:macaddr]
212
- macaddr_fmt = macaddr.bytes.map { |byte| byte.to_s(16).rjust(2, '0') }.join(':')
213
- message[:decoded][:payload][:macaddr] = macaddr_fmt
214
- end
215
- # puts pb_obj.public_methods
216
- # message[:decoded][:pb_obj] = pb_obj
278
+ message[:decoded][:payload] = recursively_decode_payloads(
279
+ object: payload,
280
+ msg_type: msg_type,
281
+ gps_metadata: gps_metadata
282
+ )
217
283
  end
218
284
 
219
- # message[:raw_packet] = raw_packet if block_given?
285
+ message[:raw_packet] = raw_packet if include_raw
220
286
  decoded_payload_hash[:packet] = message
221
287
  unless block_given?
222
288
  message[:stdout] = 'pretty'
@@ -227,8 +293,11 @@ module Meshtastic
227
293
  ArgumentError => e
228
294
 
229
295
  message[:decrypted] = e.message if e.message.include?('key must be')
296
+ message[:decrypted] = 'unable to decrypt - psk?' if e.message.include?('occurred during parsing')
230
297
  decoded_payload_hash[:packet] = message
231
298
  unless block_given?
299
+ puts "WARNING: #{e.inspect} - MSG IS >>>"
300
+ # puts e.backtrace
232
301
  message[:stdout] = 'inspect'
233
302
  stdout_message = decoded_payload_hash.inspect
234
303
  end
@@ -320,7 +389,7 @@ module Meshtastic
320
389
  root_topic: 'optional - root topic (default: msh)',
321
390
  region: 'optional - region e.g. 'US/VA', etc (default: US)',
322
391
  channel: 'optional - channel name e.g. '2/stat/#' (default: '2/e/LongFast/#')',
323
- psk: 'optional - channel pre-shared key (default: AQ==)',
392
+ psks: 'optional - hash of :channel => psk key value pairs (default: { LongFast: 'AQ==' })',
324
393
  qos: 'optional - quality of service (default: 0)',
325
394
  json: 'optional - JSON output (default: false)',
326
395
  filter: 'optional - comma-delimited string(s) to filter on in message (default: nil)',
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Meshtastic
4
- VERSION = '0.0.40'
4
+ VERSION = '0.0.42'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meshtastic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.40
4
+ version: 0.0.42
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-01 00:00:00.000000000 Z
11
+ date: 2024-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 1.8.2
47
+ version: 1.8.3
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 1.8.2
54
+ version: 1.8.3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: google-protobuf
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -156,14 +156,14 @@ dependencies:
156
156
  requirements:
157
157
  - - '='
158
158
  - !ruby/object:Gem::Version
159
- version: 2.29.1
159
+ version: 2.29.2
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - '='
165
165
  - !ruby/object:Gem::Version
166
- version: 2.29.1
166
+ version: 2.29.2
167
167
  - !ruby/object:Gem::Dependency
168
168
  name: rvm
169
169
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +178,20 @@ dependencies:
178
178
  - - '='
179
179
  - !ruby/object:Gem::Version
180
180
  version: 1.11.3.9
181
+ - !ruby/object:Gem::Dependency
182
+ name: tty-prompt
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - '='
186
+ - !ruby/object:Gem::Version
187
+ version: 0.23.1
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - '='
193
+ - !ruby/object:Gem::Version
194
+ version: 0.23.1
181
195
  - !ruby/object:Gem::Dependency
182
196
  name: yard
183
197
  requirement: !ruby/object:Gem::Requirement