mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 14:05:18 +00:00
Feature/fragmentation fixes (#453)
* Fix fragmentation + BLE long-write + padding - Accumulate CBATTRequest long writes by offset and decode once per central - Decode original packet after fragment reassembly (preserve flags/compression) - Switch MessagePadding to strict PKCS#7 and validate before unpadding - Make BinaryProtocol.decode robust: try raw first, then unpad fallback - Unpad frames before BLE notify; fragment when exceeding centrals' max update length - Skip notify path when max update length < 21 bytes (protocol minimum) Verified large PMs and announces route without decode errors and peers show reliably. * Tests: fix weak delegate lifetime, legacy constants, and unused vars; fragment unpadded frames - BLEServiceTests: hold strong reference to MockBitchatDelegate - IntegrationTests: fix inline comment braces; replace removed types with test-safe values; use numeric 0x06 for legacy handshake resp checks - BinaryProtocolTests: remove unused minResult variable - BLEService: fragment the unpadded frame so fragments are efficient * tests: add Noise rehandshake recovery test; document in-memory test bus and autoFlood; stabilize large-network broadcast * tests: align suite with current behavior (compression, padding, routing, nonce); deflake and stabilize --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -209,16 +209,23 @@ struct BinaryProtocol {
|
||||
|
||||
// Decode binary data to BitchatPacket
|
||||
static func decode(_ data: Data) -> BitchatPacket? {
|
||||
// Remove padding first and create a defensive copy
|
||||
let unpaddedData = MessagePadding.unpad(data)
|
||||
|
||||
// Try decode as-is first (robust when padding wasn't applied)
|
||||
if let pkt = decodeCore(data) { return pkt }
|
||||
// If that fails, try after removing padding
|
||||
let unpadded = MessagePadding.unpad(data)
|
||||
if unpadded as NSData === data as NSData { return nil }
|
||||
return decodeCore(unpadded)
|
||||
}
|
||||
|
||||
// Core decoding implementation used by decode(_:) with and without padding removal
|
||||
private static func decodeCore(_ raw: Data) -> BitchatPacket? {
|
||||
// Minimum size check: header + senderID
|
||||
guard unpaddedData.count >= headerSize + senderIDSize else {
|
||||
guard raw.count >= headerSize + senderIDSize else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert to array for safer indexed access
|
||||
let dataArray = Array(unpaddedData)
|
||||
let dataArray = Array(raw)
|
||||
var offset = 0
|
||||
|
||||
// Header parsing with bounds checks
|
||||
|
||||
@@ -74,42 +74,27 @@ struct MessagePadding {
|
||||
guard data.count < targetSize else { return data }
|
||||
|
||||
let paddingNeeded = targetSize - data.count
|
||||
|
||||
// PKCS#7 only supports padding up to 255 bytes
|
||||
// If we need more padding than that, don't pad - return original data
|
||||
guard paddingNeeded <= 255 else { return data }
|
||||
// Constrain to 255 to fit a single-byte pad length marker
|
||||
guard paddingNeeded > 0 && paddingNeeded <= 255 else { return data }
|
||||
|
||||
var padded = data
|
||||
|
||||
// Standard PKCS#7 padding
|
||||
var randomBytes = [UInt8](repeating: 0, count: paddingNeeded - 1)
|
||||
_ = SecRandomCopyBytes(kSecRandomDefault, paddingNeeded - 1, &randomBytes)
|
||||
padded.append(contentsOf: randomBytes)
|
||||
padded.append(UInt8(paddingNeeded))
|
||||
|
||||
// PKCS#7: All pad bytes are equal to the pad length
|
||||
padded.append(contentsOf: Array(repeating: UInt8(paddingNeeded), count: paddingNeeded))
|
||||
return padded
|
||||
}
|
||||
|
||||
// Remove padding from data
|
||||
static func unpad(_ data: Data) -> Data {
|
||||
guard !data.isEmpty else { return data }
|
||||
|
||||
// Last byte tells us how much padding to remove
|
||||
let lastIndex = data.count - 1
|
||||
guard lastIndex >= 0 else { return data }
|
||||
|
||||
let paddingLength = Int(data[lastIndex])
|
||||
guard paddingLength > 0 && paddingLength <= data.count else {
|
||||
// No valid padding, return original data
|
||||
return data
|
||||
}
|
||||
|
||||
// Create a new Data object (not a subsequence) for thread safety
|
||||
let unpaddedLength = data.count - paddingLength
|
||||
guard unpaddedLength >= 0 else { return Data() }
|
||||
|
||||
// Return a proper copy, not a subsequence
|
||||
return Data(data.prefix(unpaddedLength))
|
||||
let last = data.last!
|
||||
let paddingLength = Int(last)
|
||||
// Must have at least 1 pad byte and not exceed data length
|
||||
guard paddingLength > 0 && paddingLength <= data.count else { return data }
|
||||
// Verify PKCS#7: all last N bytes equal to pad length
|
||||
let start = data.count - paddingLength
|
||||
let tail = data[start...]
|
||||
for b in tail { if b != last { return data } }
|
||||
return Data(data[..<start])
|
||||
}
|
||||
|
||||
// Find optimal block size for data
|
||||
|
||||
Reference in New Issue
Block a user