mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 15:05:20 +00:00
Convert JSON protocol to efficient binary format
- Implemented compact binary encoding for packets and messages - Reduced packet size significantly (13-byte fixed header + variable payload) - Optimized for BLE's limited MTU with smaller message sizes - Added BinaryProtocol.swift with encode/decode functions - Updated all message types to use binary serialization - Maintains all existing functionality with better performance
This commit is contained in:
+1
-4
@@ -17,10 +17,7 @@ let package = Package(
|
|||||||
targets: [
|
targets: [
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "bitchat",
|
name: "bitchat",
|
||||||
path: "bitchat",
|
path: "bitchat"
|
||||||
resources: [
|
|
||||||
.process("Info.plist")
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 63;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
||||||
1D9674FA5F998503831DC281 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08E03AA0C63E97C91749AEC /* ContentView.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 */; };
|
6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */; };
|
||||||
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF625BB3AD919322C01A46B2 /* BitchatApp.swift */; };
|
||||||
739429DFDE5C5829CF70DA7D /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.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 */; };
|
D450CF41F207BDE1A1AAA56E /* ChatViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */; };
|
||||||
D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; };
|
D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */; };
|
||||||
DDA1DFAB1FF7AADE52DC0F53 /* EncryptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC1563390A15C042D059CF9 /* EncryptionService.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -28,8 +30,9 @@
|
|||||||
3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
3A69677D382F1C3D5ED03F7D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = "<group>"; };
|
6DC1563390A15C042D059CF9 /* EncryptionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionService.swift; sourceTree = "<group>"; };
|
||||||
7EEBDA723E1CFD88758DA4AC /* bitchat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bitchat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
A08E03AA0C63E97C91749AEC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BinaryProtocol.swift; sourceTree = "<group>"; };
|
||||||
D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = "<group>"; };
|
D5C3D880FF8AE1673B20E1E3 /* BluetoothMeshService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMeshService.swift; sourceTree = "<group>"; };
|
||||||
E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewModel.swift; sourceTree = "<group>"; };
|
E6B8F7B7D55092C2540A7996 /* ChatViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewModel.swift; sourceTree = "<group>"; };
|
||||||
EA706D8E5097785414646A8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
EA706D8E5097785414646A8E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@@ -87,6 +90,7 @@
|
|||||||
ADD53BCDA233C02E53458926 /* Protocols */ = {
|
ADD53BCDA233C02E53458926 /* Protocols */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A2136C3E22D02D4A8DBE7EAB /* BinaryProtocol.swift */,
|
||||||
229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */,
|
229F17B68CFF7AB1BC91C847 /* BitchatProtocol.swift */,
|
||||||
);
|
);
|
||||||
path = Protocols;
|
path = Protocols;
|
||||||
@@ -154,6 +158,7 @@
|
|||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
AF077EA0474EDEDE2C72716C = {
|
AF077EA0474EDEDE2C72716C = {
|
||||||
|
DevelopmentTeam = "";
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -168,6 +173,7 @@
|
|||||||
);
|
);
|
||||||
mainGroup = 18198ED912AAF495D8AF7763;
|
mainGroup = 18198ED912AAF495D8AF7763;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
preferredProjectObjectVersion = 54;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -201,6 +207,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */,
|
||||||
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */,
|
6E7761E21C99F28AE2F9BE5F /* BitchatApp.swift in Sources */,
|
||||||
923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */,
|
923027D6F2F417AFA2488127 /* BitchatProtocol.swift in Sources */,
|
||||||
7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */,
|
7A50E2F04A3515A7E90EEAE4 /* BluetoothMeshService.swift in Sources */,
|
||||||
@@ -214,6 +221,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */,
|
||||||
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */,
|
10E68BB889356219189E38EC /* BitchatApp.swift in Sources */,
|
||||||
6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */,
|
6DE056E1EE9850E9FBF50157 /* BitchatProtocol.swift in Sources */,
|
||||||
D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */,
|
D948085736ED8E736C1DE3B0 /* BluetoothMeshService.swift in Sources */,
|
||||||
@@ -233,7 +241,6 @@
|
|||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -242,7 +249,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app;
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
@@ -258,7 +265,6 @@
|
|||||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = L3N5LHJD5Y;
|
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
INFOPLIST_FILE = bitchat/Info.plist;
|
INFOPLIST_FILE = bitchat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||||
@@ -267,7 +273,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.bitchat.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.bitchat.app;
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES;
|
||||||
|
|||||||
@@ -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..<offset+8].withUnsafeBytes { bytes in
|
||||||
|
bytes.load(as: UInt64.self).bigEndian
|
||||||
|
}
|
||||||
|
offset += 8
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
let flags = data[offset]; offset += 1
|
||||||
|
let hasRecipient = (flags & Flags.hasRecipient) != 0
|
||||||
|
let hasSignature = (flags & Flags.hasSignature) != 0
|
||||||
|
|
||||||
|
// Payload length
|
||||||
|
let payloadLength = data[offset..<offset+2].withUnsafeBytes { bytes in
|
||||||
|
bytes.load(as: UInt16.self).bigEndian
|
||||||
|
}
|
||||||
|
offset += 2
|
||||||
|
|
||||||
|
// Calculate expected total size
|
||||||
|
var expectedSize = headerSize + senderIDSize + Int(payloadLength)
|
||||||
|
if hasRecipient {
|
||||||
|
expectedSize += recipientIDSize
|
||||||
|
}
|
||||||
|
if hasSignature {
|
||||||
|
expectedSize += signatureSize
|
||||||
|
}
|
||||||
|
|
||||||
|
guard data.count >= expectedSize else { return nil }
|
||||||
|
|
||||||
|
// SenderID
|
||||||
|
let senderID = data[offset..<offset+senderIDSize]
|
||||||
|
offset += senderIDSize
|
||||||
|
|
||||||
|
// RecipientID
|
||||||
|
var recipientID: Data?
|
||||||
|
if hasRecipient {
|
||||||
|
recipientID = data[offset..<offset+recipientIDSize]
|
||||||
|
offset += recipientIDSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payload
|
||||||
|
let payload = data[offset..<offset+Int(payloadLength)]
|
||||||
|
offset += Int(payloadLength)
|
||||||
|
|
||||||
|
// Signature
|
||||||
|
var signature: Data?
|
||||||
|
if hasSignature {
|
||||||
|
signature = data[offset..<offset+signatureSize]
|
||||||
|
}
|
||||||
|
|
||||||
|
return BitchatPacket(
|
||||||
|
type: type,
|
||||||
|
senderID: senderID,
|
||||||
|
recipientID: recipientID,
|
||||||
|
timestamp: timestamp,
|
||||||
|
payload: payload,
|
||||||
|
signature: signature,
|
||||||
|
ttl: ttl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary encoding for BitchatMessage
|
||||||
|
extension BitchatMessage {
|
||||||
|
func toBinaryPayload() -> 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..<offset+8].withUnsafeBytes { bytes in
|
||||||
|
bytes.load(as: UInt64.self).bigEndian
|
||||||
|
}
|
||||||
|
offset += 8
|
||||||
|
let timestamp = Date(timeIntervalSince1970: TimeInterval(timestampSeconds))
|
||||||
|
|
||||||
|
// ID
|
||||||
|
let idLength = Int(data[offset]); offset += 1
|
||||||
|
guard offset + idLength <= data.count else { return nil }
|
||||||
|
_ = String(data: data[offset..<offset+idLength], encoding: .utf8) ?? UUID().uuidString
|
||||||
|
offset += idLength
|
||||||
|
|
||||||
|
// Sender
|
||||||
|
guard offset < data.count else { return nil }
|
||||||
|
let senderLength = Int(data[offset]); offset += 1
|
||||||
|
guard offset + senderLength <= data.count else { return nil }
|
||||||
|
let sender = String(data: data[offset..<offset+senderLength], encoding: .utf8) ?? "unknown"
|
||||||
|
offset += senderLength
|
||||||
|
|
||||||
|
// Content
|
||||||
|
guard offset + 2 <= data.count else { return nil }
|
||||||
|
let contentLength = data[offset..<offset+2].withUnsafeBytes { bytes in
|
||||||
|
Int(bytes.load(as: UInt16.self).bigEndian)
|
||||||
|
}
|
||||||
|
offset += 2
|
||||||
|
guard offset + contentLength <= data.count else { return nil }
|
||||||
|
let content = String(data: data[offset..<offset+contentLength], encoding: .utf8) ?? ""
|
||||||
|
offset += contentLength
|
||||||
|
|
||||||
|
// Optional fields
|
||||||
|
var originalSender: String?
|
||||||
|
if hasOriginalSender && offset < data.count {
|
||||||
|
let length = Int(data[offset]); offset += 1
|
||||||
|
if offset + length <= data.count {
|
||||||
|
originalSender = String(data: data[offset..<offset+length], encoding: .utf8)
|
||||||
|
offset += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var recipientNickname: String?
|
||||||
|
if hasRecipientNickname && offset < data.count {
|
||||||
|
let length = Int(data[offset]); offset += 1
|
||||||
|
if offset + length <= data.count {
|
||||||
|
recipientNickname = String(data: data[offset..<offset+length], encoding: .utf8)
|
||||||
|
offset += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderPeerID: String?
|
||||||
|
if hasSenderPeerID && offset < data.count {
|
||||||
|
let length = Int(data[offset]); offset += 1
|
||||||
|
if offset + length <= data.count {
|
||||||
|
senderPeerID = String(data: data[offset..<offset+length], encoding: .utf8)
|
||||||
|
offset += length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BitchatMessage(
|
||||||
|
sender: sender,
|
||||||
|
content: content,
|
||||||
|
timestamp: timestamp,
|
||||||
|
isRelay: isRelay,
|
||||||
|
originalSender: originalSender,
|
||||||
|
isPrivate: isPrivate,
|
||||||
|
recipientNickname: recipientNickname,
|
||||||
|
senderPeerID: senderPeerID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,11 +34,11 @@ struct BitchatPacket: Codable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var data: Data? {
|
var data: Data? {
|
||||||
try? JSONEncoder().encode(self)
|
BinaryProtocol.encode(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func from(_ data: Data) -> BitchatPacket? {
|
static func from(_ data: Data) -> BitchatPacket? {
|
||||||
try? JSONDecoder().decode(BitchatPacket.self, from: data)
|
BinaryProtocol.decode(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
originalSender: nil
|
originalSender: nil
|
||||||
)
|
)
|
||||||
|
|
||||||
if let messageData = try? JSONEncoder().encode(message) {
|
if let messageData = message.toBinaryPayload() {
|
||||||
let packet = BitchatPacket(
|
let packet = BitchatPacket(
|
||||||
type: MessageType.message.rawValue,
|
type: MessageType.message.rawValue,
|
||||||
senderID: self.myPeerID.data(using: .utf8)!,
|
senderID: self.myPeerID.data(using: .utf8)!,
|
||||||
@@ -211,7 +211,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
senderPeerID: self.myPeerID
|
senderPeerID: self.myPeerID
|
||||||
)
|
)
|
||||||
|
|
||||||
if let messageData = try? JSONEncoder().encode(message) {
|
if let messageData = message.toBinaryPayload() {
|
||||||
let packet = BitchatPacket(
|
let packet = BitchatPacket(
|
||||||
type: MessageType.privateMessage.rawValue,
|
type: MessageType.privateMessage.rawValue,
|
||||||
senderID: self.myPeerID.data(using: .utf8)!,
|
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
|
// 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 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 {
|
senderID != "unknown" && senderID != myPeerID {
|
||||||
// Update nickname mapping immediately
|
// Update nickname mapping immediately
|
||||||
if peerNicknames[senderID] != message.sender {
|
if peerNicknames[senderID] != message.sender {
|
||||||
@@ -339,7 +339,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
|
|
||||||
switch MessageType(rawValue: packet.type) {
|
switch MessageType(rawValue: packet.type) {
|
||||||
case .message:
|
case .message:
|
||||||
if let message = try? JSONDecoder().decode(BitchatMessage.self, from: packet.payload) {
|
if let message = BitchatMessage.fromBinaryPayload(packet.payload) {
|
||||||
// Ignore our own messages
|
// Ignore our own messages
|
||||||
if let senderID = String(data: packet.senderID, encoding: .utf8), senderID == myPeerID {
|
if let senderID = String(data: packet.senderID, encoding: .utf8), senderID == myPeerID {
|
||||||
return
|
return
|
||||||
@@ -365,7 +365,8 @@ class BluetoothMeshService: NSObject {
|
|||||||
// Use senderID from packet for consistency
|
// Use senderID from packet for consistency
|
||||||
if let senderID = String(data: packet.senderID, encoding: .utf8) {
|
if let senderID = String(data: packet.senderID, encoding: .utf8) {
|
||||||
print("[DEBUG] Received key exchange from \(senderID)")
|
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)
|
try? encryptionService.addPeerPublicKey(senderID, publicKeyData: publicKeyData)
|
||||||
|
|
||||||
// Track this peer temporarily
|
// Track this peer temporarily
|
||||||
@@ -460,7 +461,7 @@ class BluetoothMeshService: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case .privateMessage:
|
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
|
// Check if this private message is for us
|
||||||
if let recipientID = packet.recipientID,
|
if let recipientID = packet.recipientID,
|
||||||
let recipientIDString = String(data: recipientID, encoding: .utf8),
|
let recipientIDString = String(data: recipientID, encoding: .utf8),
|
||||||
@@ -632,7 +633,7 @@ extension BluetoothMeshService: CBPeripheralDelegate {
|
|||||||
senderID: self.myPeerID.data(using: .utf8)!,
|
senderID: self.myPeerID.data(using: .utf8)!,
|
||||||
recipientID: nil,
|
recipientID: nil,
|
||||||
timestamp: UInt64(Date().timeIntervalSince1970),
|
timestamp: UInt64(Date().timeIntervalSince1970),
|
||||||
payload: try! JSONEncoder().encode(publicKeyData),
|
payload: publicKeyData,
|
||||||
signature: nil,
|
signature: nil,
|
||||||
ttl: 1
|
ttl: 1
|
||||||
)
|
)
|
||||||
@@ -736,7 +737,7 @@ extension BluetoothMeshService: CBPeripheralManagerDelegate {
|
|||||||
senderID: self.myPeerID.data(using: .utf8)!,
|
senderID: self.myPeerID.data(using: .utf8)!,
|
||||||
recipientID: peerID.data(using: .utf8),
|
recipientID: peerID.data(using: .utf8),
|
||||||
timestamp: UInt64(Date().timeIntervalSince1970),
|
timestamp: UInt64(Date().timeIntervalSince1970),
|
||||||
payload: try! JSONEncoder().encode(publicKeyData),
|
payload: publicKeyData,
|
||||||
signature: nil,
|
signature: nil,
|
||||||
ttl: 1
|
ttl: 1
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user