pwn 0.5.734 → 0.5.736
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 +4 -4
- data/lib/pwn/plugins/repl/mesh.rb +197 -52
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/plugins/repl_mesh_chunk_spec.rb +111 -0
- data/spec/lib/pwn/plugins/repl_mesh_layout_spec.rb +29 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f20a2bcbd4611945405ce166826e978eabfe051ee9b1d5bc51ab07f9dde2e5d2
|
|
4
|
+
data.tar.gz: 894c5e39df14c75ce020427d0ea9294550b17dee242c728aaa1067fadfd1d683
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c512159a8249c30b3ae191d3e4d159d8f30da33b48dbc99fdb2d6e753c4811444f5db70492b8ca712a7dd26396818365c0940d225d9c9011247109a7cfd3a47f
|
|
7
|
+
data.tar.gz: 0a9bff32912c36b10baca436caf72a18f132f9f9e4779df8f823b0aa66f785e3c905473762b85953916dbc20b51bfcae1989697ed6a57282f2d9da1d114761de
|
|
@@ -88,9 +88,10 @@ module PWN
|
|
|
88
88
|
PWN.const_set(:MeshColors, [20, 23, 21])
|
|
89
89
|
PWN.const_set(:MeshLastColor, 20)
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
layout = PWN::Plugins::REPL.send(:mesh_layout, lines: Curses.lines, cols: Curses.cols)
|
|
92
|
+
mesh_tx_rows = layout[:tx]
|
|
93
|
+
mesh_header_rows = layout[:header]
|
|
94
|
+
body_height = layout[:body]
|
|
94
95
|
rx_header_win = Curses::Window.new(mesh_header_rows, Curses.cols, 0, 0)
|
|
95
96
|
rx_header_win.scrollok(false)
|
|
96
97
|
rx_header_win.nodelay = true
|
|
@@ -111,14 +112,18 @@ module PWN
|
|
|
111
112
|
frame.addstr(' CONVERSATION ')
|
|
112
113
|
frame.refresh
|
|
113
114
|
PWN.const_set(:MeshRxFrameWin, frame)
|
|
114
|
-
rx_body_win = Curses::Window.new(
|
|
115
|
+
rx_body_win = Curses::Window.new(
|
|
116
|
+
[body_height - 2, 1].max,
|
|
117
|
+
[Curses.cols - 4, 1].max,
|
|
118
|
+
body_start_row + (body_height > 2 ? 1 : 0),
|
|
119
|
+
Curses.cols > 4 ? 2 : 0
|
|
120
|
+
)
|
|
115
121
|
rx_body_win.scrollok(true)
|
|
116
122
|
rx_body_win.nodelay = true
|
|
117
123
|
rx_body_win.refresh
|
|
118
124
|
PWN.const_set(:MeshRxBodyWin, rx_body_win)
|
|
119
125
|
|
|
120
|
-
|
|
121
|
-
tx_win = Curses::Window.new(mesh_tx_rows, Curses.cols, tx_height, 0)
|
|
126
|
+
tx_win = Curses::Window.new(mesh_tx_rows, Curses.cols, layout[:tx_top], 0)
|
|
122
127
|
tx_win.scrollok(false)
|
|
123
128
|
tx_win.nodelay = true
|
|
124
129
|
PWN::Plugins::REPL.send(:mesh_box!, win: tx_win)
|
|
@@ -165,6 +170,41 @@ module PWN
|
|
|
165
170
|
t == :auto ? PWN_MESH_TRANSPORTS.first : t
|
|
166
171
|
end
|
|
167
172
|
|
|
173
|
+
# Row counts for header, CONVERSATION, and COMPOSE so the TUI fits the terminal.
|
|
174
|
+
def mesh_layout(opts = {})
|
|
175
|
+
lines = opts[:lines]
|
|
176
|
+
cols = opts[:cols]
|
|
177
|
+
lines = lines.nil? ? Curses.lines : Integer(lines)
|
|
178
|
+
cols = cols.nil? ? Curses.cols : Integer(cols)
|
|
179
|
+
lines = 1 if lines < 1
|
|
180
|
+
cols = 1 if cols < 1
|
|
181
|
+
header = 5
|
|
182
|
+
tx = 5
|
|
183
|
+
min_header = 3
|
|
184
|
+
min_tx = 3
|
|
185
|
+
min_body = 3
|
|
186
|
+
body = lines - header - tx
|
|
187
|
+
while body < min_body && header > min_header
|
|
188
|
+
header -= 1
|
|
189
|
+
body = lines - header - tx
|
|
190
|
+
end
|
|
191
|
+
while body < min_body && tx > min_tx
|
|
192
|
+
tx -= 1
|
|
193
|
+
body = lines - header - tx
|
|
194
|
+
end
|
|
195
|
+
body = [body, 1].max
|
|
196
|
+
overflow = header + body + tx - lines
|
|
197
|
+
body -= overflow if overflow.positive? && body > 1
|
|
198
|
+
body = 1 if body < 1
|
|
199
|
+
{
|
|
200
|
+
header: header,
|
|
201
|
+
body: body,
|
|
202
|
+
tx: tx,
|
|
203
|
+
cols: cols,
|
|
204
|
+
tx_top: header + body
|
|
205
|
+
}
|
|
206
|
+
end
|
|
207
|
+
|
|
168
208
|
# Explicit Unicode avoids ACS falling back to ASCII on some terminals.
|
|
169
209
|
def mesh_box!(opts = {})
|
|
170
210
|
win = opts[:win]
|
|
@@ -536,10 +576,107 @@ module PWN
|
|
|
536
576
|
end
|
|
537
577
|
|
|
538
578
|
# Send a text frame on the active mesh transport.
|
|
579
|
+
def mesh_text_payload_max(opts = {})
|
|
580
|
+
return 0 unless opts.is_a?(Hash)
|
|
581
|
+
|
|
582
|
+
cap = opts[:max] || Meshtastic::Constants::DATA_PAYLOAD_LEN
|
|
583
|
+
# DATA_PAYLOAD_LEN is the protobuf Data.payload max. A MeshPacket
|
|
584
|
+
# around a full-size payload is ~260 bytes and will not fit a 256-byte
|
|
585
|
+
# LoRa frame, so the radio accepts ToRadio then silently drops TX.
|
|
586
|
+
lora = 256
|
|
587
|
+
overhead = 32
|
|
588
|
+
[cap, lora - overhead].min
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def mesh_payload_fits?(opts = {})
|
|
592
|
+
return false unless opts.is_a?(Hash)
|
|
593
|
+
|
|
594
|
+
text = opts[:text].to_s
|
|
595
|
+
max = opts[:max] || mesh_text_payload_max
|
|
596
|
+
text.length <= max && text.bytesize <= max
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def mesh_tx_row_ready?(opts = {})
|
|
600
|
+
return false unless opts.is_a?(Hash)
|
|
601
|
+
|
|
602
|
+
row = opts[:row]
|
|
603
|
+
return false unless row.is_a?(Hash)
|
|
604
|
+
|
|
605
|
+
packet = row[:packet] || row['packet']
|
|
606
|
+
return false unless packet.is_a?(Hash)
|
|
607
|
+
|
|
608
|
+
decoded = packet[:decoded] || packet['decoded']
|
|
609
|
+
return false unless decoded.is_a?(Hash)
|
|
610
|
+
|
|
611
|
+
port = decoded[:portnum] || decoded['portnum']
|
|
612
|
+
%w[5 ROUTING_APP].include?(port.to_s) || port == :ROUTING_APP
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def mesh_wait_tx_slot(opts = {})
|
|
616
|
+
return unless opts.is_a?(Hash)
|
|
617
|
+
|
|
618
|
+
obj = opts[:obj]
|
|
619
|
+
return unless obj.is_a?(Hash)
|
|
620
|
+
|
|
621
|
+
timeout = opts[:timeout]
|
|
622
|
+
timeout = 8 if timeout.nil?
|
|
623
|
+
timeout = Float(timeout)
|
|
624
|
+
return if timeout <= 0
|
|
625
|
+
|
|
626
|
+
since = opts[:since].to_i
|
|
627
|
+
deadline = Time.now + timeout
|
|
628
|
+
loop do
|
|
629
|
+
rows = if obj[:rx_mutex]
|
|
630
|
+
obj[:rx_mutex].synchronize { Array(obj[:proto_data]).dup }
|
|
631
|
+
else
|
|
632
|
+
Array(obj[:proto_data])
|
|
633
|
+
end
|
|
634
|
+
ready = rows.drop(since).any? do |row|
|
|
635
|
+
mesh_tx_row_ready?(row: row)
|
|
636
|
+
end
|
|
637
|
+
return if ready
|
|
638
|
+
return if Time.now >= deadline
|
|
639
|
+
|
|
640
|
+
sleep 0.05
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def mesh_text_chunks(opts = {})
|
|
645
|
+
return [] unless opts.is_a?(Hash)
|
|
646
|
+
|
|
647
|
+
text = opts[:text].to_s
|
|
648
|
+
max = mesh_text_payload_max
|
|
649
|
+
return [text] if mesh_payload_fits?(text: text, max: max)
|
|
650
|
+
|
|
651
|
+
n = 2
|
|
652
|
+
loop do
|
|
653
|
+
prefix = "(#{n}/#{n}) "
|
|
654
|
+
body_max = max - prefix.length
|
|
655
|
+
raise ArgumentError, "DATA_PAYLOAD_LEN #{max} cannot hold a chunk prefix" if body_max < 1
|
|
656
|
+
|
|
657
|
+
bodies = []
|
|
658
|
+
offset = 0
|
|
659
|
+
while offset < text.length
|
|
660
|
+
take = body_max
|
|
661
|
+
piece = text[offset, take].to_s
|
|
662
|
+
while !mesh_payload_fits?(text: "#{prefix}#{piece}", max: max) && take > 1
|
|
663
|
+
take -= 1
|
|
664
|
+
piece = text[offset, take].to_s
|
|
665
|
+
end
|
|
666
|
+
bodies << piece
|
|
667
|
+
offset += piece.length
|
|
668
|
+
end
|
|
669
|
+
if bodies.size <= n
|
|
670
|
+
total = bodies.size
|
|
671
|
+
return bodies.each_with_index.map { |body, i| "(#{i + 1}/#{total}) #{body}" }
|
|
672
|
+
end
|
|
673
|
+
n = bodies.size
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
539
677
|
def mesh_send_text(opts = {})
|
|
540
678
|
env = opts[:env] || {}
|
|
541
679
|
obj = opts[:obj]
|
|
542
|
-
text = opts[:text]
|
|
543
680
|
from = opts[:from]
|
|
544
681
|
channel = opts[:channel]
|
|
545
682
|
psks = opts[:psks]
|
|
@@ -550,49 +687,57 @@ module PWN
|
|
|
550
687
|
radio = opts[:radio]
|
|
551
688
|
radio = mesh_radio_index_for_name(env: env, obj: obj, name: channel_name) if radio.nil? && !channel_name.empty?
|
|
552
689
|
radio = mesh_radio_channel(env: env, obj: obj) if radio.nil? && %i[serial bluetooth tcp].include?(kind)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
690
|
+
chunks = mesh_text_chunks(text: opts[:text])
|
|
691
|
+
chunks.each_with_index do |piece, idx|
|
|
692
|
+
since = obj.is_a?(Hash) ? Array(obj[:proto_data]).size : 0
|
|
693
|
+
case kind
|
|
694
|
+
when :serial
|
|
695
|
+
tx = { serial_obj: obj, to: dest, text: piece, want_ack: true }
|
|
696
|
+
tx[:channel] = radio unless radio.nil?
|
|
697
|
+
Meshtastic::Serial.send_text(tx)
|
|
698
|
+
when :bluetooth
|
|
699
|
+
tx = { bluetooth_obj: obj, to: dest, text: piece, want_ack: true }
|
|
700
|
+
tx[:channel] = radio unless radio.nil?
|
|
701
|
+
Meshtastic::Bluetooth.send_text(tx)
|
|
702
|
+
when :tcp
|
|
703
|
+
tx = { tcp_obj: obj, to: dest, text: piece }
|
|
704
|
+
tx[:channel] = radio unless radio.nil?
|
|
705
|
+
Meshtastic::TCP.send_text(tx)
|
|
706
|
+
else
|
|
707
|
+
send_psks = psks
|
|
708
|
+
send_psks = mesh_channel_psks(env: env) if send_psks.nil? || send_psks.empty?
|
|
709
|
+
Meshtastic::MQTT.send_text(
|
|
710
|
+
mqtt_obj: obj,
|
|
711
|
+
from: from,
|
|
712
|
+
to: dest,
|
|
713
|
+
region: mesh_mqtt_region(region: opts[:region], env: env),
|
|
714
|
+
topic: mesh_mqtt_topic(env: env, topic: opts[:topic]),
|
|
715
|
+
channel: channel,
|
|
716
|
+
text: piece,
|
|
717
|
+
psks: send_psks
|
|
718
|
+
)
|
|
719
|
+
end
|
|
720
|
+
from_id = from.to_s
|
|
721
|
+
from_id = mesh_self_node_id(env: env, obj: obj) if from_id.empty?
|
|
722
|
+
PWN.send(:remove_const, :MeshLastTx) if PWN.const_defined?(:MeshLastTx)
|
|
723
|
+
PWN.const_set(:MeshLastTx, { from: from_id, to: dest, text: piece.to_s, at: Time.now })
|
|
724
|
+
mesh_handle_rx(
|
|
725
|
+
local: true,
|
|
726
|
+
channel_name: channel_name,
|
|
727
|
+
msg: {
|
|
728
|
+
packet: {
|
|
729
|
+
channel: radio,
|
|
730
|
+
node_id_from: from_id,
|
|
731
|
+
node_id_to: dest,
|
|
732
|
+
decoded: { portnum: :TEXT_MESSAGE_APP, payload: piece.to_s }
|
|
733
|
+
}
|
|
734
|
+
}
|
|
578
735
|
)
|
|
736
|
+
next unless idx + 1 < chunks.size
|
|
737
|
+
next unless %i[serial bluetooth tcp].include?(kind)
|
|
738
|
+
|
|
739
|
+
mesh_wait_tx_slot(obj: obj, since: since)
|
|
579
740
|
end
|
|
580
|
-
from_id = from.to_s
|
|
581
|
-
from_id = mesh_self_node_id(env: env, obj: obj) if from_id.empty?
|
|
582
|
-
PWN.send(:remove_const, :MeshLastTx) if PWN.const_defined?(:MeshLastTx)
|
|
583
|
-
PWN.const_set(:MeshLastTx, { from: from_id, to: dest, text: text.to_s, at: Time.now })
|
|
584
|
-
mesh_handle_rx(
|
|
585
|
-
local: true,
|
|
586
|
-
channel_name: channel_name,
|
|
587
|
-
msg: {
|
|
588
|
-
packet: {
|
|
589
|
-
channel: radio,
|
|
590
|
-
node_id_from: from_id,
|
|
591
|
-
node_id_to: dest,
|
|
592
|
-
decoded: { portnum: :TEXT_MESSAGE_APP, payload: text.to_s }
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
)
|
|
596
741
|
end
|
|
597
742
|
|
|
598
743
|
# Close the Meshtastic session opened by #mesh_connect.
|
|
@@ -1268,8 +1413,8 @@ module PWN
|
|
|
1268
1413
|
channel: slot[:channel_num] || channel_name,
|
|
1269
1414
|
psks: mesh_channel_psks(env: env)
|
|
1270
1415
|
)
|
|
1271
|
-
rescue StandardError
|
|
1272
|
-
|
|
1416
|
+
rescue StandardError => e
|
|
1417
|
+
mesh_ui_puts(text: "TX failed: #{e.class}: #{e.message}")
|
|
1273
1418
|
ensure
|
|
1274
1419
|
PWN.send(:remove_const, :MeshDispatchLock) if PWN.const_defined?(:MeshDispatchLock)
|
|
1275
1420
|
end
|
|
@@ -1703,9 +1848,9 @@ module PWN
|
|
|
1703
1848
|
false
|
|
1704
1849
|
end
|
|
1705
1850
|
|
|
1706
|
-
private :mesh_transport, :mesh_bound_transport, :mesh_box!, :mesh_mqtt_region, :mesh_mqtt_topic, :mesh_active_psks, :mesh_device_channel_meta, :mesh_device_channels
|
|
1851
|
+
private :mesh_transport, :mesh_bound_transport, :mesh_layout, :mesh_box!, :mesh_mqtt_region, :mesh_mqtt_topic, :mesh_active_psks, :mesh_device_channel_meta, :mesh_device_channels
|
|
1707
1852
|
private :mesh_radio_channel, :mesh_radio_index_for_name, :mesh_channel_name_for_index, :mesh_unassigned_slot_map, :mesh_psk_b64, :mesh_psk_same?, :mesh_env_channel_name_for_psk, :mesh_whitelist_name_for_index, :mesh_channel_name_from_topic, :mesh_link_label
|
|
1708
|
-
private :mesh_connect_one, :mesh_connect, :mesh_mqtt_tls?, :mesh_subscribe, :mesh_send_text, :mesh_disconnect, :mesh_compose_send, :mesh_channel_names, :mesh_env_hash
|
|
1853
|
+
private :mesh_connect_one, :mesh_connect, :mesh_mqtt_tls?, :mesh_subscribe, :mesh_text_payload_max, :mesh_payload_fits?, :mesh_text_chunks, :mesh_tx_row_ready?, :mesh_wait_tx_slot, :mesh_send_text, :mesh_disconnect, :mesh_compose_send, :mesh_channel_names, :mesh_env_hash
|
|
1709
1854
|
private :mesh_submit, :mesh_console_loop, :mesh_drain_events, :mesh_draw_input, :mesh_wrap_text, :mesh_ui_puts, :mesh_menu_root, :mesh_list_devices
|
|
1710
1855
|
private :mesh_start_rx!, :mesh_channel_psks, :mesh_text_app?, :mesh_rx_text, :mesh_self_node_id, :mesh_decorate_local_id, :mesh_public_psk?, :mesh_channel_securely_encrypted?
|
|
1711
1856
|
private :mesh_ai_whitelisted?, :mesh_ai_prompt, :mesh_broadcast?, :mesh_reply_target_label, :mesh_handle_rx, :mesh_maybe_dispatch_to_pwn_ai, :mesh_refresh_ui!, :mesh_reconnect!
|
data/lib/pwn/version.rb
CHANGED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'meshtastic'
|
|
5
|
+
|
|
6
|
+
describe PWN::Plugins::REPL, 'mesh payload chunking' do
|
|
7
|
+
let(:max) { Meshtastic::Constants::DATA_PAYLOAD_LEN }
|
|
8
|
+
let(:env) { { transport: 'serial' } }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
PWN.send(:remove_const, :MeshTransport) if PWN.const_defined?(:MeshTransport)
|
|
12
|
+
PWN.const_set(:MeshTransport, :serial)
|
|
13
|
+
allow(described_class).to receive(:mesh_handle_rx)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
after do
|
|
17
|
+
PWN.send(:remove_const, :MeshTransport) if PWN.const_defined?(:MeshTransport)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'sends text that fits DATA_PAYLOAD_LEN as a single unprefixed payload' do
|
|
21
|
+
expect(Meshtastic::Serial).to receive(:send_text).once.with(
|
|
22
|
+
hash_including(text: 'hi', to: '!ffffffff')
|
|
23
|
+
)
|
|
24
|
+
described_class.send(:mesh_send_text, env: env, obj: :serial_obj, text: 'hi', to: '!ffffffff')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'chunks text longer than DATA_PAYLOAD_LEN as (i/N) payloads until complete' do
|
|
28
|
+
body = 'A' * (max + 10)
|
|
29
|
+
sent = []
|
|
30
|
+
allow(Meshtastic::Serial).to receive(:send_text) { |h| sent << h[:text] }
|
|
31
|
+
|
|
32
|
+
described_class.send(:mesh_send_text, env: env, obj: :serial_obj, text: body, to: '!ffffffff')
|
|
33
|
+
|
|
34
|
+
expect(sent.size).to be > 1
|
|
35
|
+
n = sent.size
|
|
36
|
+
expect(sent[0]).to start_with("(1/#{n}) ")
|
|
37
|
+
expect(sent[-1]).to start_with("(#{n}/#{n}) ")
|
|
38
|
+
expect(sent).to all(satisfy { |piece| piece.length <= max && piece.bytesize <= max })
|
|
39
|
+
reconstructed = sent.map { |piece| piece.sub(%r{\A\(\d+/\d+\) }, '') }.join
|
|
40
|
+
expect(reconstructed).to eq(body)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'keeps each chunk small enough that the encoded ToRadio fits a 256-byte LoRa frame' do
|
|
44
|
+
body = 'A' * max
|
|
45
|
+
chunks = described_class.send(:mesh_text_chunks, text: body)
|
|
46
|
+
expect(chunks.size).to be > 1
|
|
47
|
+
mui = Meshtastic::MeshInterface.new
|
|
48
|
+
chunks.each do |piece|
|
|
49
|
+
expect(piece.bytesize).to be <= max
|
|
50
|
+
proto = mui.send_text(
|
|
51
|
+
from: '!00000b0b',
|
|
52
|
+
to: '!ffffffff',
|
|
53
|
+
channel: 0,
|
|
54
|
+
text: piece,
|
|
55
|
+
want_ack: true,
|
|
56
|
+
psks: nil,
|
|
57
|
+
via: :radio
|
|
58
|
+
)
|
|
59
|
+
body_bytes = proto.is_a?(String) ? proto.b.bytesize : proto.to_proto.bytesize
|
|
60
|
+
expect(body_bytes).to be <= 256
|
|
61
|
+
end
|
|
62
|
+
reconstructed = chunks.map { |piece| piece.sub(%r{\A\(\d+/\d+\) }, '') }.join
|
|
63
|
+
expect(reconstructed).to eq(body)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'sends chunked frames with want_ack so the radio reports when each chunk is on the air' do
|
|
67
|
+
body = 'A' * (max + 10)
|
|
68
|
+
sent = []
|
|
69
|
+
allow(Meshtastic::Serial).to receive(:send_text) { |h| sent << h[:want_ack] }
|
|
70
|
+
described_class.send(:mesh_send_text, env: env, obj: :serial_obj, text: body, to: '!ffffffff')
|
|
71
|
+
expect(sent.size).to be > 1
|
|
72
|
+
expect(sent).to all(eq(true))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'waits for a free device TX slot before sending the next chunk' do
|
|
76
|
+
body = 'A' * (max + 10)
|
|
77
|
+
obj = { proto_data: [], rx_mutex: Mutex.new }
|
|
78
|
+
allow(Meshtastic::Serial).to receive(:send_text)
|
|
79
|
+
allow(described_class).to receive(:mesh_wait_tx_slot)
|
|
80
|
+
described_class.send(:mesh_send_text, env: env, obj: obj, text: body, to: '!ffffffff')
|
|
81
|
+
n = described_class.send(:mesh_text_chunks, text: body).size
|
|
82
|
+
expect(described_class).to have_received(:mesh_wait_tx_slot).exactly(n - 1).times
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'does not wait for a TX slot after a single payload' do
|
|
86
|
+
allow(Meshtastic::Serial).to receive(:send_text)
|
|
87
|
+
allow(described_class).to receive(:mesh_wait_tx_slot)
|
|
88
|
+
described_class.send(:mesh_send_text, env: env, obj: { proto_data: [] }, text: 'hi', to: '!ffffffff')
|
|
89
|
+
expect(described_class).not_to have_received(:mesh_wait_tx_slot)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'does not treat QueueStatus free as the chunk being on the air' do
|
|
93
|
+
obj = { proto_data: [{ queueStatus: { free: 15, maxlen: 16 } }], rx_mutex: Mutex.new }
|
|
94
|
+
t = Time.now
|
|
95
|
+
described_class.send(:mesh_wait_tx_slot, obj: obj, since: 0, timeout: 0.25)
|
|
96
|
+
expect(Time.now - t).to be >= 0.2
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'treats a ROUTING_APP FromRadio as a TX slot without a fixed sleep' do
|
|
100
|
+
obj = { proto_data: [], rx_mutex: Mutex.new }
|
|
101
|
+
Thread.new do
|
|
102
|
+
sleep 0.05
|
|
103
|
+
obj[:rx_mutex].synchronize do
|
|
104
|
+
obj[:proto_data] << { packet: { decoded: { portnum: :ROUTING_APP, request_id: 1 } } }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
t = Time.now
|
|
108
|
+
described_class.send(:mesh_wait_tx_slot, obj: obj, since: 0, timeout: 1)
|
|
109
|
+
expect(Time.now - t).to be < 0.5
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe PWN::Plugins::REPL, 'mesh terminal layout' do
|
|
6
|
+
it 'keeps a 5-row header and 5-row compose on a 24-line terminal' do
|
|
7
|
+
layout = described_class.send(:mesh_layout, lines: 24, cols: 80)
|
|
8
|
+
expect(layout[:header]).to eq(5)
|
|
9
|
+
expect(layout[:tx]).to eq(5)
|
|
10
|
+
expect(layout[:body]).to eq(14)
|
|
11
|
+
expect(layout[:tx_top]).to eq(19)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'shrinks conversation so header, conversation and compose fit a 12-line terminal' do
|
|
15
|
+
layout = described_class.send(:mesh_layout, lines: 12, cols: 40)
|
|
16
|
+
expect(layout[:header] + layout[:body] + layout[:tx]).to eq(12)
|
|
17
|
+
expect(layout[:tx_top] + layout[:tx]).to eq(12)
|
|
18
|
+
expect(layout[:header]).to be >= 3
|
|
19
|
+
expect(layout[:tx]).to be >= 3
|
|
20
|
+
expect(layout[:body]).to be >= 3
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'keeps compose on-screen on a 10-line terminal by shrinking conversation first' do
|
|
24
|
+
layout = described_class.send(:mesh_layout, lines: 10, cols: 40)
|
|
25
|
+
expect(layout[:header] + layout[:body] + layout[:tx]).to eq(10)
|
|
26
|
+
expect(layout[:tx_top]).to be < 10
|
|
27
|
+
expect(layout[:tx_top] + layout[:tx]).to eq(10)
|
|
28
|
+
end
|
|
29
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pwn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.736
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -6911,9 +6911,11 @@ files:
|
|
|
6911
6911
|
- spec/lib/pwn/plugins/repl/mesh_spec.rb
|
|
6912
6912
|
- spec/lib/pwn/plugins/repl/vault_spec.rb
|
|
6913
6913
|
- spec/lib/pwn/plugins/repl_mesh_channel_route_spec.rb
|
|
6914
|
+
- spec/lib/pwn/plugins/repl_mesh_chunk_spec.rb
|
|
6914
6915
|
- spec/lib/pwn/plugins/repl_mesh_console_spec.rb
|
|
6915
6916
|
- spec/lib/pwn/plugins/repl_mesh_dispatch_spec.rb
|
|
6916
6917
|
- spec/lib/pwn/plugins/repl_mesh_dm_spec.rb
|
|
6918
|
+
- spec/lib/pwn/plugins/repl_mesh_layout_spec.rb
|
|
6917
6919
|
- spec/lib/pwn/plugins/repl_mesh_rx_paint_spec.rb
|
|
6918
6920
|
- spec/lib/pwn/plugins/repl_pwn_vault_spec.rb
|
|
6919
6921
|
- spec/lib/pwn/plugins/repl_spec.rb
|