package com.bitchat.android.model import com.bitchat.android.sync.SyncDefaults /** * REQUEST_SYNC payload using GCS (Golomb-Coded Set) parameters. * TLV (type, length16, value), types: * - 0x01: P (uint8) — Golomb-Rice parameter * - 0x02: M (uint32, big-endian) — hash range (N * 2^P) * - 0x03: data (opaque) — GR bitstream bytes */ data class RequestSyncPacket( val p: Int, val m: Long, val data: ByteArray, val wantedTypes: List? = null, val minTimestamp: ULong? = null ) { fun encode(): ByteArray { val out = ArrayList() fun putTLV(t: Int, v: ByteArray) { out.add(t.toByte()) val len = v.size out.add(((len ushr 8) and 0xFF).toByte()) out.add((len and 0xFF).toByte()) out.addAll(v.toList()) } // P putTLV(0x01, byteArrayOf(p.toByte())) // M (uint32) val m32 = m.coerceAtMost(0xffff_ffffL) putTLV( 0x02, byteArrayOf( ((m32 ushr 24) and 0xFF).toByte(), ((m32 ushr 16) and 0xFF).toByte(), ((m32 ushr 8) and 0xFF).toByte(), (m32 and 0xFF).toByte() ) ) // data putTLV(0x03, data) // wantedTypes wantedTypes?.let { types -> if (types.isNotEmpty()) { val typesBytes = types.map { it.toByte() }.toByteArray() putTLV(0x04, typesBytes) } } // minTimestamp minTimestamp?.let { ts -> val tsLong = ts.toLong() val tsBytes = byteArrayOf( ((tsLong ushr 56) and 0xFF).toByte(), ((tsLong ushr 48) and 0xFF).toByte(), ((tsLong ushr 40) and 0xFF).toByte(), ((tsLong ushr 32) and 0xFF).toByte(), ((tsLong ushr 24) and 0xFF).toByte(), ((tsLong ushr 16) and 0xFF).toByte(), ((tsLong ushr 8) and 0xFF).toByte(), (tsLong and 0xFF).toByte() ) putTLV(0x05, tsBytes) } return out.toByteArray() } companion object { // Receiver-side safety limit (configurable constant) const val MAX_ACCEPT_FILTER_BYTES: Int = SyncDefaults.MAX_ACCEPT_FILTER_BYTES fun decode(data: ByteArray): RequestSyncPacket? { var off = 0 var p: Int? = null var m: Long? = null var payload: ByteArray? = null var wantedTypes: List? = null var minTimestamp: ULong? = null while (off + 3 <= data.size) { val t = (data[off].toInt() and 0xFF); off += 1 val len = ((data[off].toInt() and 0xFF) shl 8) or (data[off+1].toInt() and 0xFF); off += 2 if (off + len > data.size) return null val v = data.copyOfRange(off, off + len); off += len when (t) { 0x01 -> if (len == 1) p = (v[0].toInt() and 0xFF) 0x02 -> if (len == 4) { val mm = ((v[0].toLong() and 0xFF) shl 24) or ((v[1].toLong() and 0xFF) shl 16) or ((v[2].toLong() and 0xFF) shl 8) or (v[3].toLong() and 0xFF) m = mm } 0x03 -> { if (v.size > MAX_ACCEPT_FILTER_BYTES) return null payload = v } 0x04 -> { val typesList = mutableListOf() for (b in v) { typesList.add(b.toUByte()) } wantedTypes = typesList } 0x05 -> { if (len == 8) { var tsVal = 0L for (i in 0 until 8) { tsVal = (tsVal shl 8) or (v[i].toLong() and 0xFF) } minTimestamp = tsVal.toULong() } } } } val pp = p ?: return null val mm = m ?: return null val dd = payload ?: return null if (pp < 1 || mm <= 0L) return null return RequestSyncPacket(pp, mm, dd, wantedTypes, minTimestamp) } } }