This commit is contained in:
mikecarper
2026-09-03 02:29:23 +00:00
parent bc8c29d2ed
commit 88e70a2515
4 changed files with 77 additions and 14 deletions
+11 -1
View File
@@ -6629,7 +6629,9 @@ get clock.sync.status
<h4 id="view-or-set-the-direct-path-override-for-the-current-remote-client">View or set the direct path override for the current remote client</h4>
<p><strong>Usage:</strong>
- <code>get outpath</code>
- <code>get outpath path</code>
- <code>set outpath &lt;hop1_hex,hop2_hex,...&gt;</code>
- <code>set outpath path</code>
- <code>set outpath direct</code>
- <code>set outpath clear</code>
- <code>set outpath flood</code></p>
@@ -6637,8 +6639,16 @@ get clock.sync.status
- <code>hopN_hex</code>: Hop hash with <code>2</code>, <code>4</code>, or <code>6</code> hexadecimal characters. Every hop must use the same width.</p>
<p><strong>Notes:</strong>
- These commands require remote client context and update the caller's ACL entry.
- <code>get outpath path</code> reports the reciprocal <code>PAYLOAD_TYPE_PATH</code> received after
the caller's latest flood login without changing the selected output route.
Because that packet is asynchronous, an immediate query can report
<code>&gt; path pending</code>; retry shortly. The observation window expires after one
minute, and the captured login path is not automatically selected.
- <code>set outpath path</code> copies that observed route to <code>outpath</code> and saves it. It
returns an error without changing <code>outpath</code> if no route was received.
- <code>direct</code> selects a zero-hop route for a directly reachable caller.
- <code>clear</code> forgets the override so normal path discovery can repopulate it.
- <code>clear</code> forgets the override, replies <code>&gt; outpath cleared</code>, and allows normal
path discovery to repopulate it.
- <code>flood</code> forces replies to use flood packets until the client logs in again.</p>
<hr />
<h4 id="view-or-change-this-nodes-repeat-flag">View or change this node's repeat flag</h4>
+12 -3
View File
@@ -1818,7 +1818,9 @@ hex characters, and all hops in one path must use the same width. Hex input is
case-insensitive. Replies use uppercase hex and retain the commas, so the value
can be copied directly into another <code>set outpath</code> or <code>set altpath</code> command.</p>
<pre><code class="language-text">get outpath
get outpath path
set outpath A1B2C3,D4E5F6
set outpath path
set outpath direct
set outpath clear
set outpath flood
@@ -1832,10 +1834,17 @@ set altpath clear
</code></pre>
<p>The first CLI word is case-insensitive (<code>set</code>, <code>Set</code>, and <code>SET</code> are the same,
as are <code>get</code>, <code>Get</code>, and the other command verbs). Argument case is preserved.</p>
<p><code>get outpath path</code> reports the reciprocal path received after the latest flood
login without changing <code>outpath</code>. That packet arrives asynchronously, so retry
shortly if the first query reports <code>&gt; path pending</code>; the observation window
expires after one minute. The login path is not automatically selected.
<code>set outpath path</code> copies the observed route into <code>outpath</code> and saves it; it
leaves the current route unchanged when no observed route is available.</p>
<p><code>set outpath direct</code> sets a zero-hop direct route for a client reachable without
repeaters. <code>set outpath clear</code> forgets the override and lets normal path
discovery fill it again. <code>set outpath flood</code> forces replies to use flood packets
until the client logs in again.</p>
repeaters. <code>set outpath clear</code> forgets the override, replies
<code>&gt; outpath cleared</code>, and lets normal path discovery fill it again.
<code>set outpath flood</code> forces replies to use flood packets until the client logs in
again.</p>
<p>When <code>outpath</code> is a valid direct path and <code>altpath</code> is also a valid, different
direct path, repeater DM replies send two packets: one on <code>outpath</code> and one on
<code>altpath</code>. The secondary <code>altpath</code> copy does not create its own direct-retry
+53 -9
View File
@@ -2594,8 +2594,13 @@ label and stage a different manifest.</p>
<pre><code>OTA_GET_MANIFEST: manifest_id[4] want_mask(uint16) # bit k = send manifest fragment k; 0xFFFF = all
OTA_MANIFEST: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up to OTA_MF_FRAG=176 B/frag
OTA_REQ: manifest_id[4] { block_idx(uint16) want_mask(uint16) }[1..4]
# one or more rows; bit k = send fragment k of that block
OTA_DATA: manifest_id[4] block_idx(uint16) frag_off(uint16) data[] # up to OTA_FRAG_DATA=160 B
# legacy: bit k requests its 160-byte fragment
# v2: bit15=marker, bit14=allow transport DEFLATE, bits0..12=fragment bitmap
OTA_DATA legacy: manifest_id[4] block_idx(uint16) frag_off(uint16) data[&lt;=160]
OTA_DATA v2: manifest_id[4] block_idx(uint16) descriptor(uint16)
stream_id[4] data[&lt;=171]
# descriptor: bit15=marker, bit14=DEFLATE, bits13..10=fragment,
# bits9..0=complete encoded length minus one
OTA_REQ_PROOF: manifest_id[4] block_idx(uint16)
OTA_PROOF: manifest_id[4] block_idx(uint16) n_proof(1) proof[] # n_proof x 4 bytes
OTA_GET_LEAVES: manifest_id[4] want_mask(uint16) # bit k = send leaves fragment k; 0xFFFF = all
@@ -2617,11 +2622,31 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
is answering <code>OTA_GET_LEAVES</code> with leaves the node already holds, so any node's firmware can be captured.</p>
</li>
<li>
<p><strong>Block &lt;-&gt; fragments:</strong> a 1 KB block remains split into self-describing <code>OTA_DATA</code> fragments. <code>frag_off</code> is the
byte offset of <code>data</code> within the block, so the global position is <code>block_idx*block_size + frag_off</code> -
a fragment is self-placing when returned by the source. The fetcher tracks a
per-block slice bitmap and reassembles before requesting the proof.</p>
<p><strong>Negotiated 171-byte fragments:</strong> the deployed profile remains unchanged: <code>frag_off</code> is a byte offset and
<code>data[]</code> carries at most 160 bytes. A new fetcher marks an <code>OTA_REQ</code> row as v2 and includes all seven legacy
bits on its first request. Old sources ignore/mask the high flags and can therefore return a complete legacy
block immediately; receipt of an untagged <code>OTA_DATA</code> switches that fetch session to the old geometry. If no
v2 data appears by the first adaptive deadline, the fetcher retries with an ordinary legacy mask. New
sources answer v2 with a packed descriptor, a repeated 4-byte representation ID, and exactly 171 data bytes
except the final fragment. A raw 1 KiB block therefore falls from seven data packets to six. Message type
IDs do not change, so multi-hop relays continue to forward request/data/proof packets opaquely with the same
priority.</p>
</li>
<li><strong>Transport-only DEFLATE:</strong> a fetcher sets the v2 DEFLATE-permission bit only when its application includes
and configures the decoder. A source may encode each logical block as an independent raw RFC 1951 stream
with full stored, fixed-Huffman, and dynamic-Huffman support (<code>BTYPE=0/1/2</code>). Compression is used only when
strictly smaller, otherwise the source returns raw v2 data. The
receiver inflates to the manifest-derived block length, then performs the unchanged Merkle proof and writes
the original bytes to staging. Thus existing <code>.mota</code> containers, signatures, resume markers, and legacy
bootloader apply remain unchanged. The 4-byte <code>stream_id</code> is SHA-256:4 of the complete raw or compressed
representation and appears in every fragment. A receiver locks <code>{encoding, encoded length, stream_id}</code> for
a block, preventing fragments from independently encoded seeders from being mixed. After one sparse retry,
a second stalled interval clears the full in-flight window and switches the session to legacy geometry, so
a vanished v2 seeder cannot prevent an older source from taking over; proof verification remains unchanged.
Every <code>ENABLE_OTA</code> MeshCore application registers the vendored tinf 1.2.1 full raw decoder; its wrapper
bounds output to the exact logical block length and rejects truncation, malformed streams, and trailing
whole bytes. Builds without OTA do not link the decoder. This application capability is independent of the
bootloader because transport data is inflated before the unchanged staged container is written.</li>
<li><strong>Adaptive flight size is not signed block size.</strong> The container continues to use 1 KiB Merkle leaves and
each slot is one existing 1 KiB block. A clean link changes how many of those blocks one <code>OTA_REQ</code> names:
1, then 2, then 3, then the compiled cap. The manifest stores <code>block_size_log2</code>, so 3 KiB is not a valid
@@ -2642,7 +2667,8 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
half-duplex radios: re-requesting a whole multi-fragment burst let the periodic retry (a transmit) collide
with the tail of the in-flight burst and drop the same fragment forever - a hang. Requesting only the hole
removes the burst, so there is nothing to collide with. The block/manifest mask matches the 16-bit
reassembly bitmap (&lt;=16 fragments/block; 1 KB blocks = 7). <code>OTA_PROOF</code> is a single packet and needs no mask.</li>
reassembly bitmap (&lt;=16 fragments/block; legacy 1 KB blocks = 7, raw v2 = 6). <code>OTA_PROOF</code> is a single packet
and needs no mask.</li>
<li><strong>Data and proof remain separate packets, without a normal extra round trip.</strong> A server retains requested
blocks in a bounded descriptor queue, admits at most one response per main-loop pass, and sends one
<code>OTA_PROOF</code> after each block's requested fragments. Before admitting that proactive proof, the source leaves
@@ -2665,11 +2691,16 @@ OTA_LEAVES: manifest_id[4] frag_idx(1) frag_total(1) bytes[] # up
</thead>
<tbody>
<tr>
<td><code>OTA_DATA</code></td>
<td><code>OTA_DATA</code> legacy</td>
<td>9 B (type+mid4+idx2+off2)</td>
<td><code>OTA_FRAG_DATA = 160</code> -&gt; 7 frags per 1 KB block</td>
</tr>
<tr>
<td><code>OTA_DATA</code> v2</td>
<td>13 B (legacy header + stream ID)</td>
<td><code>OTA_FRAG_DATA_V2 = 171</code> -&gt; 6 raw frags per 1 KB block; fewer when compressed</td>
</tr>
<tr>
<td><code>OTA_MANIFEST</code></td>
<td>7 B</td>
<td><code>OTA_MF_FRAG = 176</code> -&gt; signed manifest ~ 2 frags</td>
@@ -2779,12 +2810,15 @@ a folder destination.</p>
drives USB-serial, BLE, a WiFi URL list, an NFS/samba mount, etc. - only <code>read()</code> differs.</p>
<pre><code class="language-cpp">struct MotaDesc { // catalog metadata + region offsets (no whole image in RAM)
uint8_t mid[4]; uint32_t target_id, fw_version; uint8_t codec_id, flags;
uint8_t block_size_log2, source_caps;
uint32_t total_size, leaves_off, block_count, payload_off, payload_size;
};
class MotaSource {
virtual uint8_t count(); // # mOTAs offered
virtual bool describe(uint8_t idx, MotaDesc&amp; out); // metadata + offsets
virtual bool read(uint8_t idx, uint32_t off, uint8_t* buf, uint32_t len); // random-access bytes
virtual bool read_deflated_block(uint8_t idx, uint16_t block,
uint8_t* buf, uint16_t cap, uint16_t* len); // optional raw RFC 1951
};
</code></pre>
<p>To serve an external mota the node reads its manifest-minus-leaves + <code>leaves[]</code> into RAM (&lt;=4 KB for &lt;=1024
@@ -2804,9 +2838,11 @@ response (host -&gt; device): 'm' 's' op(1) status(1) payload... xsum(1 =
OP_COUNT 0x01 args: - -&gt; payload: count(1)
OP_DESCRIBE 0x02 args: idx(1) -&gt; payload: MotaDesc wire (38 B)
OP_READ 0x03 args: idx(1) off(4) len(2) -&gt; payload: len bytes
OP_DEFLATE_BLOCK 0x09 args: idx(1) block(2) off(2) len(2)
-&gt; payload: total_encoded_len(2) + requested bytes
MotaDesc wire (38 B): mid[4] target_id(4) fw_version(4) codec(1) flags(1)
total_size(4) leaves_off(4) block_count(4) payload_off(4) payload_size(4)
block_size_log2(1) reserved(3)
block_size_log2(1) source_caps(1) reserved(2)
status: 0 = OK, non-zero = error (out of range / past EOF).
</code></pre>
<p><code>SerialMotaSource</code> splits logical reads into replies of at most 192 payload
@@ -2815,6 +2851,14 @@ normally 1 KiB; requesting either in one transaction can overrun common USB
CDC/UART receive rings even though the host successfully wrote the complete
reply. Chunking is internal to the transport and does not change <code>OP_READ</code> or
the <code>MotaSource</code> random-access contract.</p>
<p><code>OP_DEFLATE_BLOCK</code> lets a host-folder source perform the optional transport compression without linking an
encoder into the embedded seeder. <code>len=0, off=0</code> queries the exact encoded length; subsequent chunks are at
most 190 bytes, keeping the total response payload at 192 bytes. The host independently encodes each manifest
payload block as ordinary raw RFC 1951 at level 9. It returns an error for an invalid range, an unsupported
operation, or a result that is not smaller than the raw block; <code>SerialMotaSource</code> then serves the ordinary raw
v2 representation. A supporting host sets <code>source_caps bit 0</code> in every descriptor. Older hosts leave that
formerly-reserved byte zero, so upgraded firmware skips the optional request instead of waiting for an old
daemon that silently ignores unknown operations.</p>
<p>Manifest fragments are retained as bounded response jobs and admitted one at
a time. Their source-side gap follows the active maximum packet airtime and
dispatcher duty spacing, clamped to 100-1000 ms. The 100 ms floor protects
File diff suppressed because one or more lines are too long