From d55c438f3a0125f304c425849a8b7165648d82ac Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Mon, 27 Jul 2026 14:33:49 -0400 Subject: [PATCH] fix(tips): pad tip code payload with incrementing digits instead of zeros The tip code frame wrote a 16-byte user id and left the 3 reserved trailing bytes as zeros. The native Kik scanner strips trailing zero bytes on decode, so those frames came back short. Fill the reserved space with incrementing digits (1, 2, 3) so the frame ends non-zero and round-trips at full length. --- .../opencode/model/core/OpenCodePayloadTests.kt | 5 +++-- .../getcode/opencode/model/core/OpenCodePayload.kt | 4 +++- .../com/getcode/opencode/model/core/PayloadKind.kt | 13 +++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/services/opencode/src/androidTest/kotlin/com/getcode/opencode/model/core/OpenCodePayloadTests.kt b/services/opencode/src/androidTest/kotlin/com/getcode/opencode/model/core/OpenCodePayloadTests.kt index 99eb964cc..e949832eb 100644 --- a/services/opencode/src/androidTest/kotlin/com/getcode/opencode/model/core/OpenCodePayloadTests.kt +++ b/services/opencode/src/androidTest/kotlin/com/getcode/opencode/model/core/OpenCodePayloadTests.kt @@ -60,7 +60,8 @@ class OpenCodePayloadTests { assertEquals(PayloadKind.Tip.value, encoded[0].toInt()) assertEquals(userId, encoded.subList(1, 17)) - // Trailing bytes reserved / zero. - assertEquals(listOf(0, 0, 0), encoded.subList(17, 20)) + // Trailing reserved bytes are filled with incrementing digits (1, 2, 3) rather than + // zeros, so the native scanner can't drop them as trailing padding. + assertEquals(listOf(1, 2, 3), encoded.subList(17, 20)) } } \ No newline at end of file diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/OpenCodePayload.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/OpenCodePayload.kt index 7416e1d38..cb4604f24 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/OpenCodePayload.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/OpenCodePayload.kt @@ -85,10 +85,12 @@ data class OpenCodePayload( 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ - | T | User ID (16 bytes) | reserved (0) | + | T | User ID (16 bytes) | 1 | 2 | 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ (T) Type (1 byte) — PayloadKind.Tip (2). User ID (16 bytes) — the recipient's user id (a UUID) so others can tip them. Raw bytes, no rendezvous keypair. Matches the iOS TipCode.Payload frame. + Reserved (3 bytes) — filled with incrementing digits (1, 2, 3) instead of zeros so the + native scanner doesn't strip them as trailing zero padding. */ diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/PayloadKind.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/PayloadKind.kt index 14725b9cd..94ee2c192 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/PayloadKind.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/core/PayloadKind.kt @@ -70,8 +70,9 @@ sealed interface PayloadKind { /** * A profile "tip code": the recipient's [UserId] (a 16-byte UUID) written raw at offset 1, - * with the trailing bytes reserved. Carries no nonce and derives no rendezvous — matches the - * iOS `TipCode.Payload` frame. + * with the trailing reserved bytes filled with incrementing digits (1, 2, 3, ...) so the + * native scanner can't drop them as trailing zero padding. Carries no nonce and derives no + * rendezvous — matches the iOS `TipCode.Payload` frame. */ data object Tip : PayloadKind { override val value: Int = 2 @@ -84,6 +85,14 @@ sealed interface PayloadKind { userId.take(OpenCodePayload.USER_ID_LENGTH).forEachIndexed { index, byte -> data[index + OpenCodePayload.OFFSET_USER_ID] = byte } + + // Fill the reserved trailing space with incrementing digits (1, 2, 3, ...) rather + // than zeros, so `Scanner.decode` can't drop them as trailing zero padding and the + // frame round-trips at full length. + val reservedStart = OpenCodePayload.OFFSET_USER_ID + OpenCodePayload.USER_ID_LENGTH + for (index in reservedStart until OpenCodePayload.LENGTH) { + data[index] = (index - reservedStart + 1).toByte() + } return data }