diff --git a/Package.swift b/Package.swift index 646bb341..1cbc526a 100644 --- a/Package.swift +++ b/Package.swift @@ -17,10 +17,7 @@ let package = Package( targets: [ .executableTarget( name: "bitchat", - path: "bitchat", - resources: [ - .process("Info.plist") - ] + path: "bitchat" ), ] ) \ No newline at end of file diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index 1ff73c49..0f403266 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -3,12 +3,13 @@ archiveVersion = 1; classes = { }; - objectVersion = 63; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; }; 1D9674FA5F998503831DC281 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.swift */; }; + 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */; }; 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; }; 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; }; 739429DFDE5C5829CF70DA7D /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.swift */; }; @@ -21,6 +22,7 @@ D450CF41F207BDE1A1AAA56E /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; }; D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; }; DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.swift */; }; + F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,8 +30,9 @@ 3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = ""; }; 7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 997D512074C64904D75DDD40 /* bitchat.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; }; A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = ""; }; D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = ""; }; E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewModel.swift; sourceTree = ""; }; EA706D8E5097785414646A8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; @@ -87,6 +90,7 @@ ADD53BCDA233C02E53458926 /* Protocols */ = { isa = PBXGroup; children = ( + A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */, 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */, ); path = Protocols; @@ -154,6 +158,7 @@ ProvisioningStyle = Automatic; }; AF077EA0474EDEDE2C72716C = { + DevelopmentTeam = ""; ProvisioningStyle = Automatic; }; }; @@ -168,6 +173,7 @@ ); mainGroup = 18198ED912AAF495D8AF7763; minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 54; projectDirPath = ""; projectRoot = ""; targets = ( @@ -201,6 +207,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */, 6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */, 923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */, 7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */, @@ -214,6 +221,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */, 10E68BB889356219189E38EC /* BitchatApp.swift in Sources */, 6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */, D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */, @@ -233,7 +241,6 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -242,7 +249,7 @@ "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app; + PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app; PRODUCT_NAME = bitchat; SDKROOT = iphoneos; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; @@ -258,7 +265,6 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = L3N5LHJD5Y; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = bitchat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -267,7 +273,7 @@ "@executable_path/Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app; + PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app; PRODUCT_NAME = bitchat; SDKROOT = iphoneos; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; diff --git a/bitchat/Protocols/BinaryProtocol.swift b/bitchat/Protocols/BinaryProtocol.swift new file mode 100644 index 00000000..ed250cc2 --- /dev/null +++ b/bitchat/Protocols/BinaryProtocol.swift @@ -0,0 +1,320 @@ +import Foundation + +// Binary Protocol Format: +// Header (Fixed 13 bytes): +// - Version: 1 byte +// - Type: 1 byte +// - TTL: 1 byte +// - Timestamp: 8 bytes (UInt64) +// - Flags: 1 byte (bit 0: hasRecipient, bit 1: hasSignature) +// - PayloadLength: 2 bytes (UInt16) +// +// Variable sections: +// - SenderID: 8 bytes (fixed) +// - RecipientID: 8 bytes (if hasRecipient flag set) +// - Payload: Variable length +// - Signature: 64 bytes (if hasSignature flag set) + +struct BinaryProtocol { + static let headerSize = 13 + static let senderIDSize = 8 + static let recipientIDSize = 8 + static let signatureSize = 64 + + struct Flags { + static let hasRecipient: UInt8 = 0x01 + static let hasSignature: UInt8 = 0x02 + } + + // Encode BitchatPacket to binary format + static func encode(_ packet: BitchatPacket) -> Data? { + var data = Data() + + // Header + data.append(packet.version) + data.append(packet.type) + data.append(packet.ttl) + + // Timestamp (8 bytes, big-endian) + withUnsafeBytes(of: packet.timestamp.bigEndian) { bytes in + data.append(contentsOf: bytes) + } + + // Flags + var flags: UInt8 = 0 + if packet.recipientID != nil { + flags |= Flags.hasRecipient + } + if packet.signature != nil { + flags |= Flags.hasSignature + } + data.append(flags) + + // Payload length (2 bytes, big-endian) + let payloadLength = UInt16(packet.payload.count) + withUnsafeBytes(of: payloadLength.bigEndian) { bytes in + data.append(contentsOf: bytes) + } + + // SenderID (exactly 8 bytes) + let senderBytes = packet.senderID.prefix(senderIDSize) + data.append(senderBytes) + if senderBytes.count < senderIDSize { + data.append(Data(repeating: 0, count: senderIDSize - senderBytes.count)) + } + + // RecipientID (if present) + if let recipientID = packet.recipientID { + let recipientBytes = recipientID.prefix(recipientIDSize) + data.append(recipientBytes) + if recipientBytes.count < recipientIDSize { + data.append(Data(repeating: 0, count: recipientIDSize - recipientBytes.count)) + } + } + + // Payload + data.append(packet.payload) + + // Signature (if present) + if let signature = packet.signature { + data.append(signature.prefix(signatureSize)) + } + + return data + } + + // Decode binary data to BitchatPacket + static func decode(_ data: Data) -> BitchatPacket? { + guard data.count >= headerSize + senderIDSize else { return nil } + + var offset = 0 + + // Header + _ = data[offset]; offset += 1 // version + let type = data[offset]; offset += 1 + let ttl = data[offset]; offset += 1 + + // Timestamp + let timestamp = data[offset..= expectedSize else { return nil } + + // SenderID + let senderID = data[offset.. Data? { + var data = Data() + + // Message format: + // - Flags: 1 byte (bit 0: isRelay, bit 1: isPrivate, bit 2: hasOriginalSender, bit 3: hasRecipientNickname, bit 4: hasSenderPeerID) + // - Timestamp: 8 bytes (seconds since epoch) + // - ID length: 1 byte + // - ID: variable + // - Sender length: 1 byte + // - Sender: variable + // - Content length: 2 bytes + // - Content: variable + // Optional fields based on flags: + // - Original sender length + data + // - Recipient nickname length + data + // - Sender peer ID length + data + + var flags: UInt8 = 0 + if isRelay { flags |= 0x01 } + if isPrivate { flags |= 0x02 } + if originalSender != nil { flags |= 0x04 } + if recipientNickname != nil { flags |= 0x08 } + if senderPeerID != nil { flags |= 0x10 } + + data.append(flags) + + // Timestamp + let timestampSeconds = UInt64(timestamp.timeIntervalSince1970) + withUnsafeBytes(of: timestampSeconds.bigEndian) { bytes in + data.append(contentsOf: bytes) + } + + // ID + if let idData = id.data(using: .utf8) { + data.append(UInt8(min(idData.count, 255))) + data.append(idData.prefix(255)) + } else { + data.append(0) + } + + // Sender + if let senderData = sender.data(using: .utf8) { + data.append(UInt8(min(senderData.count, 255))) + data.append(senderData.prefix(255)) + } else { + data.append(0) + } + + // Content + if let contentData = content.data(using: .utf8) { + let length = UInt16(min(contentData.count, 65535)) + withUnsafeBytes(of: length.bigEndian) { bytes in + data.append(contentsOf: bytes) + } + data.append(contentData.prefix(Int(length))) + } else { + data.append(contentsOf: [0, 0]) + } + + // Optional fields + if let originalSender = originalSender, let origData = originalSender.data(using: .utf8) { + data.append(UInt8(min(origData.count, 255))) + data.append(origData.prefix(255)) + } + + if let recipientNickname = recipientNickname, let recipData = recipientNickname.data(using: .utf8) { + data.append(UInt8(min(recipData.count, 255))) + data.append(recipData.prefix(255)) + } + + if let senderPeerID = senderPeerID, let peerData = senderPeerID.data(using: .utf8) { + data.append(UInt8(min(peerData.count, 255))) + data.append(peerData.prefix(255)) + } + + return data + } + + static func fromBinaryPayload(_ data: Data) -> BitchatMessage? { + guard data.count >= 13 else { return nil } // Minimum size + + var offset = 0 + + // Flags + let flags = data[offset]; offset += 1 + let isRelay = (flags & 0x01) != 0 + let isPrivate = (flags & 0x02) != 0 + let hasOriginalSender = (flags & 0x04) != 0 + let hasRecipientNickname = (flags & 0x08) != 0 + let hasSenderPeerID = (flags & 0x10) != 0 + + // Timestamp + let timestampSeconds = data[offset.. BitchatPacket? { - try? JSONDecoder().decode(BitchatPacket.self, from: data) + BinaryProtocol.decode(data) } } diff --git a/bitchat/Services/BluetoothMeshService.swift b/bitchat/Services/BluetoothMeshService.swift index 71a5b27b..f26c2a35 100644 --- a/bitchat/Services/BluetoothMeshService.swift +++ b/bitchat/Services/BluetoothMeshService.swift @@ -176,7 +176,7 @@ class BluetoothMeshService: NSObject { originalSender: nil ) - if let messageData = try? JSONEncoder().encode(message) { + if let messageData = message.toBinaryPayload() { let packet = BitchatPacket( type: MessageType.message.rawValue, senderID: self.myPeerID.data(using: .utf8)!, @@ -211,7 +211,7 @@ class BluetoothMeshService: NSObject { senderPeerID: self.myPeerID ) - if let messageData = try? JSONEncoder().encode(message) { + if let messageData = message.toBinaryPayload() { let packet = BitchatPacket( type: MessageType.privateMessage.rawValue, senderID: self.myPeerID.data(using: .utf8)!, @@ -322,7 +322,7 @@ class BluetoothMeshService: NSObject { // For any message type, if we have a nickname in the payload, update it immediately if packet.type == MessageType.message.rawValue || packet.type == MessageType.privateMessage.rawValue { - if let message = try? JSONDecoder().decode(BitchatMessage.self, from: packet.payload), + if let message = BitchatMessage.fromBinaryPayload(packet.payload), senderID != "unknown" && senderID != myPeerID { // Update nickname mapping immediately if peerNicknames[senderID] != message.sender { @@ -339,7 +339,7 @@ class BluetoothMeshService: NSObject { switch MessageType(rawValue: packet.type) { case .message: - if let message = try? JSONDecoder().decode(BitchatMessage.self, from: packet.payload) { + if let message = BitchatMessage.fromBinaryPayload(packet.payload) { // Ignore our own messages if let senderID = String(data: packet.senderID, encoding: .utf8), senderID == myPeerID { return @@ -365,7 +365,8 @@ class BluetoothMeshService: NSObject { // Use senderID from packet for consistency if let senderID = String(data: packet.senderID, encoding: .utf8) { print("[DEBUG] Received key exchange from \(senderID)") - if let publicKeyData = try? JSONDecoder().decode(Data.self, from: packet.payload) { + if packet.payload.count > 0 { + let publicKeyData = packet.payload try? encryptionService.addPeerPublicKey(senderID, publicKeyData: publicKeyData) // Track this peer temporarily @@ -460,7 +461,7 @@ class BluetoothMeshService: NSObject { } case .privateMessage: - if let message = try? JSONDecoder().decode(BitchatMessage.self, from: packet.payload) { + if let message = BitchatMessage.fromBinaryPayload(packet.payload) { // Check if this private message is for us if let recipientID = packet.recipientID, let recipientIDString = String(data: recipientID, encoding: .utf8), @@ -632,7 +633,7 @@ extension BluetoothMeshService: CBPeripheralDelegate { senderID: self.myPeerID.data(using: .utf8)!, recipientID: nil, timestamp: UInt64(Date().timeIntervalSince1970), - payload: try! JSONEncoder().encode(publicKeyData), + payload: publicKeyData, signature: nil, ttl: 1 ) @@ -736,7 +737,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate { senderID: self.myPeerID.data(using: .utf8)!, recipientID: peerID.data(using: .utf8), timestamp: UInt64(Date().timeIntervalSince1970), - payload: try! JSONEncoder().encode(publicKeyData), + payload: publicKeyData, signature: nil, ttl: 1 )