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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35f85751c76226260916fb5b417180f8c10a1fe53eb8913295895aec83da8eaa
4
- data.tar.gz: 7808ea07ce71dd2576b168acf9cb1c429c0f9f4dc098570c13ffa81d416e6ffc
3
+ metadata.gz: f20a2bcbd4611945405ce166826e978eabfe051ee9b1d5bc51ab07f9dde2e5d2
4
+ data.tar.gz: 894c5e39df14c75ce020427d0ea9294550b17dee242c728aaa1067fadfd1d683
5
5
  SHA512:
6
- metadata.gz: 933f35d3a0d28a1dfdb87eb826874e88b150738b333bfb2518a9366f61cd72b24c5fb7e64fe30e73a92086320d6d3df8bfb88b3d8047f9565eab9dd995780087
7
- data.tar.gz: 5384de39a95c61302cd37f6a6bcdfbcd54f1f9b047a7588c990afef5b89a8e572007dab3abab3e35547d8dc35c6a43d4f2ec72997122e70ea86ea0a3cb21fc7d
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
- mesh_tx_rows = 5
92
- mesh_header_rows = 5
93
- body_height = [Curses.lines - mesh_tx_rows - mesh_header_rows, 4].max
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(body_height - 2, Curses.cols - 4, body_start_row + 1, 2)
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
- tx_height = mesh_header_rows + body_height
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
- case kind
554
- when :serial
555
- tx = { serial_obj: obj, to: dest, text: text, want_ack: true }
556
- tx[:channel] = radio unless radio.nil?
557
- Meshtastic::Serial.send_text(tx)
558
- when :bluetooth
559
- tx = { bluetooth_obj: obj, to: dest, text: text, want_ack: true }
560
- tx[:channel] = radio unless radio.nil?
561
- Meshtastic::Bluetooth.send_text(tx)
562
- when :tcp
563
- tx = { tcp_obj: obj, to: dest, text: text }
564
- tx[:channel] = radio unless radio.nil?
565
- Meshtastic::TCP.send_text(tx)
566
- else
567
- send_psks = psks
568
- send_psks = mesh_channel_psks(env: env) if send_psks.nil? || send_psks.empty?
569
- Meshtastic::MQTT.send_text(
570
- mqtt_obj: obj,
571
- from: from,
572
- to: dest,
573
- region: mesh_mqtt_region(region: opts[:region], env: env),
574
- topic: mesh_mqtt_topic(env: env, topic: opts[:topic]),
575
- channel: channel,
576
- text: text,
577
- psks: send_psks
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
- nil
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.734'
4
+ VERSION = '0.5.736'
5
5
  end
@@ -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.734
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