Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ data class OpenCodePayload(
}

val kind = PayloadKind.from(frame[0].toInt())
return OpenCodePayload(kind, kind.decode(frame), kind.decodeNonce(frame))

// `decode` returns null for a frame this kind can't parse — e.g. a foreign or corrupt
// Kik code whose currency byte is out of range (Bugsnag 6a5a4523:
// IndexOutOfBoundsException on CurrencyCode.entries[232], a 171-entry enum). Treat it
// as Empty so the scanner ignores it instead of crashing or attempting a bogus grab.
val value = kind.decode(frame) ?: return Empty
return OpenCodePayload(kind, value, kind.decodeNonce(frame))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ sealed interface PayloadKind {
/** Encodes [value] (and [nonce], where applicable) into the fixed-length scan frame. */
fun encode(value: PayloadValue, nonce: List<Byte>): List<Byte>

/** Reads this kind's [PayloadValue] out of a (zero-padded) scan [frame]. */
fun decode(frame: List<Byte>): PayloadValue
/**
* Reads this kind's [PayloadValue] out of a (zero-padded) scan [frame], or null if the frame
* can't be parsed as this kind (e.g. a cash frame whose currency byte is out of range).
*/
fun decode(frame: List<Byte>): PayloadValue?

/** Reads the nonce out of a scan [frame]; empty for kinds that carry none. */
fun decodeNonce(frame: List<Byte>): List<Byte>
Expand All @@ -45,8 +48,11 @@ sealed interface PayloadKind {
return data
}

override fun decode(frame: List<Byte>): PayloadValue {
val currency = CurrencyCode.entries[frame[1].byteToUnsignedInt()]
override fun decode(frame: List<Byte>): PayloadValue? {
// Byte 1 is an index into CurrencyCode. A foreign or corrupt Kik code can carry a byte
// outside that range; such a frame isn't a valid cash code, so fail decoding rather
// than throw (Bugsnag 6a5a4523: IndexOutOfBoundsException on a 171-entry enum).
val currency = CurrencyCode.entries.getOrNull(frame[1].byteToUnsignedInt()) ?: return null
val quarks = frame.subList(OpenCodePayload.OFFSET_QUARKS, OpenCodePayload.OFFSET_NONCE)
.toByteArray().byteArrayToLong()
return Fiat(currencyCode = currency, quarks = quarks)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,34 @@ class OpenCodePayloadTest {
assertIs<Fiat>(decoded.value)
}

@Test
fun `fromList does not throw on out-of-range currency byte`() {
// A foreign / corrupt Kik code can pass error-correction yet carry a currency byte
// outside CurrencyCode's range. Regression for IndexOutOfBoundsException (Bugsnag
// 6a5a4523): `CurrencyCode.entries[232]` on a 171-element enum. Such a frame is not a
// valid Flipcash cash code, so it must decode to Unknown (which the scanner ignores).
val frame = MutableList<Byte>(OpenCodePayload.LENGTH) { 0 }
frame[0] = PayloadKind.Cash.value.toByte()
frame[1] = 232.toByte() // >= CurrencyCode.entries.size

val decoded = OpenCodePayload.fromList(frame)

assertEquals(PayloadKind.Unknown, decoded.kind)
}

@Test
fun `fromList decodes highest valid currency index`() {
// Boundary: the last valid ordinal must still decode as Cash, not be rejected.
val lastCurrency = CurrencyCode.entries.last()
val fiat = Fiat(quarks = 7L, currencyCode = lastCurrency)
val encoded = encodePayload(PayloadKind.Cash, fiat, List(10) { 0.toByte() })

val decoded = OpenCodePayload.fromList(encoded)

assertEquals(PayloadKind.Cash, decoded.kind)
assertEquals(lastCurrency, decoded.fiat!!.currencyCode)
}

@Test
fun `fromList preserves nonce bytes`() {
val nonce = List(10) { (it * 3).toByte() }
Expand Down
Loading