Skip to content

Commit 5278226

Browse files
jamesarichCopilot
andcommitted
refactor(remote-shell): use proto fields for replay/heartbeat metadata
Align with firmware refactor (meshtastic/firmware#10123) and protobufs PR meshtastic/protobufs#894 which adds last_tx_seq and last_rx_seq fields to the RemoteShell message. - ACK: use last_rx_seq proto field instead of encoding replay-from seq into payload bytes - PING: use last_tx_seq/last_rx_seq proto fields instead of encoding heartbeat status as 8-byte payload - PONG: read last_tx_seq/last_rx_seq from proto fields instead of decoding payload - SentFrame: store flags/lastTxSeq/lastRxSeq for faithful replay - Remove unused encodeUint32BE, encodeHeartbeatStatus, decodeHeartbeatStatus helpers and associated constants Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2761c17 commit 5278226

1 file changed

Lines changed: 17 additions & 29 deletions

File tree

feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/terminal/RemoteShellViewModel.kt

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -84,38 +84,19 @@ private const val HEARTBEAT_POLL_MS = 250L
8484
/** Minimum ms between re-requesting the same missing sequence number. */
8585
private const val MISSING_SEQ_RETRY_MS = 1_000L
8686

87-
/** Size in bytes of an encoded replay-request payload (big-endian uint32). */
88-
private const val REPLAY_REQUEST_BYTES = 4
89-
9087
/** Maximum configurable flush window in milliseconds. */
9188
private const val MAX_FLUSH_WINDOW_MS = 5_000L
9289

93-
/** Size in bytes of a heartbeat-status payload (two big-endian uint32s). */
94-
private const val HEARTBEAT_STATUS_BYTES = 8
95-
9690
// ---------------------------------------------------------------------------
9791
// Helpers
9892
// ---------------------------------------------------------------------------
9993

100-
private fun encodeUint32BE(value: Int): ByteArray = Buffer().apply { writeInt(value) }.readByteArray()
101-
10294
/** Size in bytes of an encoded uint32 (big-endian). */
10395
private const val UINT32_BYTES = 4
10496

10597
private fun decodeUint32BE(bytes: ByteArray, offset: Int = 0): Int =
10698
Buffer().apply { write(bytes, offset, UINT32_BYTES) }.readInt()
10799

108-
private fun encodeHeartbeatStatus(lastTxSeq: Int, lastRxSeq: Int): ByteArray = Buffer()
109-
.apply {
110-
writeInt(lastTxSeq)
111-
writeInt(lastRxSeq)
112-
}
113-
.readByteArray()
114-
115-
private fun decodeHeartbeatStatus(bytes: ByteArray): Pair<Int, Int>? = bytes
116-
.takeIf { it.size >= HEARTBEAT_STATUS_BYTES }
117-
?.let { Buffer().apply { write(it) }.let { buf -> buf.readInt() to buf.readInt() } }
118-
119100
// ---------------------------------------------------------------------------
120101
// Sent-frame record for retransmission history
121102
// ---------------------------------------------------------------------------
@@ -128,6 +109,9 @@ private data class SentFrame(
128109
val payload: ByteString,
129110
val cols: Int,
130111
val rows: Int,
112+
val flags: Int = 0,
113+
val lastTxSeq: Int = 0,
114+
val lastRxSeq: Int = 0,
131115
)
132116

133117
// ---------------------------------------------------------------------------
@@ -237,6 +221,9 @@ class RemoteShellViewModel(
237221
payload = frame.payload,
238222
cols = frame.cols,
239223
rows = frame.rows,
224+
flags = frame.flags,
225+
last_tx_seq = frame.lastTxSeq,
226+
last_rx_seq = frame.lastRxSeq,
240227
),
241228
remember = false,
242229
)
@@ -547,7 +534,8 @@ class RemoteShellViewModel(
547534
session_id = sessionId.value,
548535
seq = allocSeq(),
549536
ack_seq = currentAckSeq(),
550-
payload = encodeHeartbeatStatus(highestSentSeq(), currentAckSeq()).toByteString(),
537+
last_tx_seq = highestSentSeq(),
538+
last_rx_seq = currentAckSeq(),
551539
),
552540
isHeartbeat = true,
553541
)
@@ -576,9 +564,8 @@ class RemoteShellViewModel(
576564
pruneSentFrames(frame.ack_seq)
577565

578566
if (frame.op == RemoteShell.OpCode.ACK) {
579-
val payload = frame.payload.toByteArray()
580-
if (payload.size >= REPLAY_REQUEST_BYTES) {
581-
replayFrom(decodeUint32BE(payload))
567+
if (frame.last_rx_seq > 0) {
568+
replayFrom(frame.last_rx_seq + 1)
582569
}
583570
return
584571
}
@@ -640,11 +627,10 @@ class RemoteShellViewModel(
640627
_sessionState.update { SessionState.CLOSED }
641628
}
642629
RemoteShell.OpCode.PONG -> {
643-
val payload = frame.payload.toByteArray()
644-
if (payload.isEmpty()) return
645-
val (peerLastTxSeq, peerLastRxSeq) = decodeHeartbeatStatus(payload) ?: return
630+
val peerLastTxSeq = frame.last_tx_seq
631+
val peerLastRxSeq = frame.last_rx_seq
646632
val ourHighestTx = highestSentSeq()
647-
if (peerLastRxSeq < ourHighestTx) {
633+
if (peerLastRxSeq in 1..<ourHighestTx) {
648634
replayFrom(peerLastRxSeq + 1)
649635
}
650636
if (peerLastTxSeq > currentAckSeq()) {
@@ -661,14 +647,13 @@ class RemoteShellViewModel(
661647
// region --- Frame dispatch ---
662648

663649
private suspend fun sendAck(replayFrom: Int? = null) {
664-
val payload = replayFrom?.let { encodeUint32BE(it).toByteString() } ?: ByteString.EMPTY
665650
sendFrame(
666651
RemoteShell(
667652
op = RemoteShell.OpCode.ACK,
668653
session_id = sessionId.value,
669654
seq = 0,
670655
ack_seq = currentAckSeq(),
671-
payload = payload,
656+
last_rx_seq = replayFrom?.let { it - 1 } ?: 0,
672657
),
673658
remember = false,
674659
)
@@ -685,6 +670,9 @@ class RemoteShellViewModel(
685670
payload = frame.payload,
686671
cols = frame.cols,
687672
rows = frame.rows,
673+
flags = frame.flags,
674+
lastTxSeq = frame.last_tx_seq,
675+
lastRxSeq = frame.last_rx_seq,
688676
),
689677
)
690678
}

0 commit comments

Comments
 (0)