From 74150391836164722105d740e28221fd521774ed Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:34:24 +0200 Subject: [PATCH] use v2 packet for FILE_TRANSFER --- bitchat/Features/voice/VoiceRecorder.swift | 18 +++++ bitchat/Models/BitchatPacket.swift | 16 ++++- bitchat/Protocols/BinaryProtocol.swift | 76 +++++++++++++++------- bitchat/Services/BLEService.swift | 1 + 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/bitchat/Features/voice/VoiceRecorder.swift b/bitchat/Features/voice/VoiceRecorder.swift index baca582e..c45ff990 100644 --- a/bitchat/Features/voice/VoiceRecorder.swift +++ b/bitchat/Features/voice/VoiceRecorder.swift @@ -1,4 +1,6 @@ import Foundation + +#if os(iOS) import AVFoundation final class VoiceRecorder: NSObject, AVAudioRecorderDelegate { @@ -48,4 +50,20 @@ enum VoiceRecorderPaths { return folder.appendingPathComponent(name) } } +#else +/// Minimal macOS-compatible stubs so the macOS target builds without AVFAudio. +final class VoiceRecorder: NSObject { + private(set) var isRecording = false + func startRecording(to url: URL) throws { + // Voice recording is iOS-only in this project + throw NSError(domain: "VoiceRecorder", code: -1, userInfo: [NSLocalizedDescriptionKey: "Voice recording is not supported on macOS in this build."]) + } + func stopRecording(completion: @escaping () -> Void) { completion() } +} +enum VoiceRecorderPaths { + static func outgoingURL() throws -> URL { + throw NSError(domain: "VoiceRecorder", code: -2, userInfo: [NSLocalizedDescriptionKey: "Voice recording is not supported on macOS in this build."]) + } +} +#endif diff --git a/bitchat/Models/BitchatPacket.swift b/bitchat/Models/BitchatPacket.swift index 0fc2a05a..249b0720 100644 --- a/bitchat/Models/BitchatPacket.swift +++ b/bitchat/Models/BitchatPacket.swift @@ -21,7 +21,20 @@ struct BitchatPacket: Codable { let payload: Data var signature: Data? var ttl: UInt8 - + + // Full initialization with explicit version + init(version: UInt8, type: UInt8, senderID: Data, recipientID: Data?, timestamp: UInt64, payload: Data, signature: Data?, ttl: UInt8) { + self.version = version + self.type = type + self.senderID = senderID + self.recipientID = recipientID + self.timestamp = timestamp + self.payload = payload + self.signature = signature + self.ttl = ttl + } + + // Backward compatible constructor (defaults to v1) init(type: UInt8, senderID: Data, recipientID: Data?, timestamp: UInt64, payload: Data, signature: Data?, ttl: UInt8) { self.version = 1 self.type = type @@ -74,6 +87,7 @@ struct BitchatPacket: Codable { // Create a copy without signature and with fixed TTL for signing // TTL must be excluded because it changes during relay let unsignedPacket = BitchatPacket( + version: version, // Preserve packet version for signing type: type, senderID: senderID, recipientID: recipientID, diff --git a/bitchat/Protocols/BinaryProtocol.swift b/bitchat/Protocols/BinaryProtocol.swift index 390ec6ef..92f90cd0 100644 --- a/bitchat/Protocols/BinaryProtocol.swift +++ b/bitchat/Protocols/BinaryProtocol.swift @@ -22,11 +22,12 @@ /// /// ## Wire Format /// ``` -/// Header (Fixed 13 bytes): -/// +--------+------+-----+-----------+-------+----------------+ -/// |Version | Type | TTL | Timestamp | Flags | PayloadLength | -/// |1 byte |1 byte|1byte| 8 bytes | 1 byte| 2 bytes | -/// +--------+------+-----+-----------+-------+----------------+ +/// Header (13 bytes for v1, 15 bytes for v2): +/// +--------+------+-----+-----------+-------+---------------+ +/// |Version | Type | TTL | Timestamp | Flags | PayloadLength | +/// |1 byte |1 byte|1byte| 8 bytes | 1 byte| 2 bytes (v1) | +/// | | | | | | 4 bytes (v2) | +/// +--------+------+-----+-----------+-------+---------------+ /// /// Variable sections: /// +----------+-------------+---------+------------+ @@ -56,9 +57,10 @@ /// - Bits 3-7: Reserved for future use /// /// ## Size Constraints -/// - Maximum packet size: 65,535 bytes (16-bit length field) +/// - Maximum packet size: 4.2GB (32-bit length field for v2+) /// - Typical packet size: < 512 bytes (BLE MTU) /// - Minimum packet size: 21 bytes (header + sender ID) +/// - Legacy v1 packets: max 65,535 bytes (16-bit length field) /// /// ## Encoding Process /// 1. Construct header with fixed fields @@ -105,16 +107,22 @@ extension Data { /// their binary wire format representation. /// - Note: All multi-byte values use network byte order (big-endian) struct BinaryProtocol { - static let headerSize = 13 + static let headerSizeV1 = 13 + static let headerSizeV2 = 15 static let senderIDSize = 8 static let recipientIDSize = 8 static let signatureSize = 64 - + struct Flags { static let hasRecipient: UInt8 = 0x01 static let hasSignature: UInt8 = 0x02 static let isCompressed: UInt8 = 0x04 } + + /// Get header size based on protocol version + static func getHeaderSize(_ version: UInt8) -> Int { + return version >= 2 ? headerSizeV2 : headerSizeV1 + } // Encode BitchatPacket to binary format static func encode(_ packet: BitchatPacket, padding: Bool = true) -> Data? { @@ -138,9 +146,10 @@ struct BinaryProtocol { } else { } - // Header + // Header with version-dependent size + let headerSize = getHeaderSize(packet.version) // Reserve capacity to reduce reallocations. Estimate base size conservatively. - // header(13) + sender(8) + opt recipient(8) + opt originalSize(2) + payload + opt signature(64) + up to 255 pad + // header + sender(8) + opt recipient(8) + opt originalSize(2) + payload + opt signature(64) + up to 255 pad let estimatedPayload = payload.count + (isCompressed ? 2 : 0) let estimated = headerSize + senderIDSize + (packet.recipientID == nil ? 0 : recipientIDSize) + estimatedPayload + (packet.signature == nil ? 0 : signatureSize) + 255 data.reserveCapacity(estimated) @@ -166,13 +175,20 @@ struct BinaryProtocol { } data.append(flags) - // Payload length (2 bytes, big-endian) - includes original size if compressed + // Payload length - version-dependent (2 bytes for v1, 4 bytes for v2) let payloadDataSize = payload.count + (isCompressed ? 2 : 0) - let payloadLength = UInt16(payloadDataSize) - - - data.append(UInt8((payloadLength >> 8) & 0xFF)) - data.append(UInt8(payloadLength & 0xFF)) + if packet.version >= 2 { + // v2+: 4 bytes big-endian + data.append(UInt8((payloadDataSize >> 24) & 0xFF)) + data.append(UInt8((payloadDataSize >> 16) & 0xFF)) + data.append(UInt8((payloadDataSize >> 8) & 0xFF)) + data.append(UInt8(payloadDataSize & 0xFF)) + } else { + // v1: 2 bytes big-endian + let payloadLength16 = UInt16(payloadDataSize) + data.append(UInt8((payloadLength16 >> 8) & 0xFF)) + data.append(UInt8(payloadLength16 & 0xFF)) + } // SenderID (exactly 8 bytes) let senderBytes = packet.senderID.prefix(senderIDSize) @@ -227,8 +243,8 @@ struct BinaryProtocol { // Core decoding implementation used by decode(_:) with and without padding removal private static func decodeCore(_ raw: Data) -> BitchatPacket? { - // Minimum size: header + senderID - guard raw.count >= headerSize + senderIDSize else { return nil } + // Minimum size: smallest header (v1) + senderID + guard raw.count >= headerSizeV1 + senderIDSize else { return nil } return raw.withUnsafeBytes { (buf: UnsafeRawBufferPointer) -> BitchatPacket? in guard let base = buf.baseAddress else { return nil } @@ -249,6 +265,14 @@ struct BinaryProtocol { offset += 2 return v } + // Read big-endian 32-bit + func read32() -> UInt32? { + guard require(4) else { return nil } + let p = base.advanced(by: offset).assumingMemoryBound(to: UInt8.self) + let v = (UInt32(p[0]) << 24) | (UInt32(p[1]) << 16) | (UInt32(p[2]) << 8) | UInt32(p[3]) + offset += 4 + return v + } // Copy N bytes into Data func readData(_ n: Int) -> Data? { guard require(n) else { return nil } @@ -258,8 +282,8 @@ struct BinaryProtocol { return d } - // Version - guard let version = read8(), version == 1 else { return nil } + // Version - accept both v1 and v2+ + guard let version = read8(), version >= 1 else { return nil } guard let type = read8() else { return nil } guard let ttl = read8() else { return nil } @@ -277,8 +301,15 @@ struct BinaryProtocol { let hasSignature = (flags & Flags.hasSignature) != 0 let isCompressed = (flags & Flags.isCompressed) != 0 - // Payload length - guard let payloadLen = read16(), payloadLen <= 65535 else { return nil } + // Payload length - version-dependent (2 bytes for v1, 4 bytes for v2+) + let payloadLen: UInt32 + if version >= 2 { + guard let len32 = read32(), len32 <= 4_294_967_295 else { return nil } + payloadLen = len32 + } else { + guard let len16 = read16(), len16 <= 65535 else { return nil } + payloadLen = UInt32(len16) + } // SenderID guard let senderID = readData(senderIDSize) else { return nil } @@ -317,6 +348,7 @@ struct BinaryProtocol { guard offset <= buf.count else { return nil } return BitchatPacket( + version: version, type: type, senderID: senderID, recipientID: recipientID, diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 88b309bf..b1c2fbb5 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -1283,6 +1283,7 @@ final class BLEService: NSObject { // Build packet let recipientData: Data? = recipientPeerID.flatMap { Data(hexString: $0) } var packet = BitchatPacket( + version: 2, // FILE_TRANSFER uses v2 for 4-byte payload length to support large files type: MessageType.fileTransfer.rawValue, senderID: myPeerIDData, recipientID: recipientData,