Validate stream snapshot counts and verify native metadata mappings
This commit is contained in:
1 parent
a1dcf28bf9
commit
ffa5dece4f
2 files changed
+51
-2
No files matched your search
@@ -7,14 +7,17 @@ data class StreamBufferPositions(val first: Long = 0, val head: Long = 0, val to
|
||||
else ((next.coerceIn(first, head) - first).toDouble() / (head - first)).toFloat()
|
||||
}
|
||||
|
||||
/** Все поля в байтах; ACK — десять завершённых интервалов по 100ms, для всех соединений ядра. */
|
||||
/** TX snapshot: sampleUs — MONOTONIC мкс, счётчики данных — байты, connections — число соединений.
|
||||
* acked1s — десять завершённых интервалов по 100 мс для всех соединений ядра.
|
||||
* buffer.first/head и peer worst/best — seq пачек, total — все получатели, peers — до 16 отставших.
|
||||
* fromNative читает 13 + 4*peerCount полей JNI; peer ID сохраняет uint64, ошибки формата бросаются caller. */
|
||||
data class StreamTxStats(val id: Long, val sampleUs: Long, val videoBytes: Long, val audioBytes: Long,
|
||||
val streamBytes: Long, val transportBytes: Long, val inflightBytes: Long,
|
||||
val acked1s: Long, val totalBytes: Long, val connections: Long, val buffer: StreamBufferPositions) {
|
||||
companion object {
|
||||
fun fromNative(id: Long, data: LongArray): StreamTxStats {
|
||||
require(data.size >= 13 && data.take(13).all { it >= 0 } && data[12] <= 16)
|
||||
require(data.size == 13 + data[12].toInt() * 4 && data[9] <= data[10] && data[12] <= data[11])
|
||||
require(data.size == 13 + data[12].toInt() * 4 && data[9] <= data[10] && data[12] <= data[11] && data[11] <= Int.MAX_VALUE)
|
||||
val peers = List(data[12].toInt()) { i ->
|
||||
val offset = 13 + i * 4
|
||||
require(data[offset + 1] >= 0 && data[offset + 2] >= data[offset + 1] && data[offset + 3] in 1..255)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.utun.chat.data
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
|
||||
class StreamMetadataTest {
|
||||
@Test fun sourceIdsAndEpochsPreserveUnsigned64BitsAndEmptyNamesUseTheId() {
|
||||
val source = StreamSource.parse("""[{"id":"18446744073709551615","epoch":"ffffffffffffffff","name":"","self":1}]""").single()
|
||||
assertEquals(-1L, source.id); assertEquals(-1L, source.epoch)
|
||||
assertEquals("18446744073709551615", source.displayName); assertTrue(source.self)
|
||||
}
|
||||
|
||||
@Test fun sourceSnapshotsRejectZeroAndDuplicateIdentities() {
|
||||
for (json in listOf("""[{"id":"0","epoch":"1","name":"a","self":0}]""",
|
||||
"""[{"id":"1","epoch":"0","name":"a","self":0}]""",
|
||||
"""[{"id":"1","epoch":"a","name":"a","self":0},{"id":"1","epoch":"b","name":"b","self":1}]""")) {
|
||||
assertThrows(IllegalArgumentException::class.java) { StreamSource.parse(json) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun transmitSnapshotMapsUnitsAndKeepsUnsignedPeerId() {
|
||||
val data = longArrayOf(100, 1, 2, 3, 4, 5, 6, 7, 8, 10, 20, 2, 1, -1, 12, 18, 3)
|
||||
val stats = StreamTxStats.fromNative(42, data)
|
||||
assertEquals(100L, stats.sampleUs); assertEquals(8L, stats.connections); assertEquals(6L, stats.acked1s)
|
||||
assertEquals(2, stats.buffer.total); assertEquals(StreamBufferPeer(-1, 12, 18, 3), stats.buffer.peers.single())
|
||||
assertEquals(0f, stats.buffer.fraction(0), 0f); assertEquals(0.5f, stats.buffer.fraction(15), 0f)
|
||||
assertEquals(1f, stats.buffer.fraction(30), 0f); assertEquals(1f, StreamBufferPositions().fraction(0), 0f)
|
||||
}
|
||||
|
||||
@Test fun malformedStatsCannotOverflowCountsOrReverseTheBufferWindow() {
|
||||
val valid = longArrayOf(100, 1, 2, 3, 4, 5, 6, 7, 8, 10, 20, 2, 1, -1, 12, 18, 3)
|
||||
for (data in listOf(valid.copyOf(12), valid + 0, valid.copyOf().also { it[11] = Int.MAX_VALUE.toLong() + 1 },
|
||||
valid.copyOf().also { it[9] = 21 }, valid.copyOf().also { it[12] = 17 },
|
||||
valid.copyOf().also { it[15] = 11 }, valid.copyOf().also { it[16] = 256 })) {
|
||||
assertThrows(IllegalArgumentException::class.java) { StreamTxStats.fromNative(42, data) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun nativeTelemetryKeepsKnownUnknownAndLocalReserveSeparate() {
|
||||
val data = """{"self":"local","nodes":[{"name":"relay","flags":7,"us":2000000,"frames":4},{"name":"source","flags":0,"us":0,"frames":0}]}"""
|
||||
val stats = StreamPlaybackTelemetry.fromNative(data.toByteArray(), 500000, 1100)
|
||||
assertEquals(3, stats.nodes.size); assertEquals(2_000_000L, stats.nodes[0].durationUs)
|
||||
assertTrue(stats.nodes[0].stale); assertTrue(stats.nodes[0].down); assertNull(stats.nodes[1].durationUs)
|
||||
assertEquals(500_000L, stats.nodes[2].durationUs); assertEquals("1.1x", stats.speed)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user