Add protocol version negotiation for future compatibility

- Add version negotiation messages (0x20 versionHello, 0x21 versionAck)
- Implement VersionHello and VersionAck message types with platform info
- Add ProtocolVersion struct for version management and negotiation
- Update BinaryProtocol to check supported versions
- Add version negotiation to connection flow before Noise handshake
- Maintain backward compatibility with legacy peers (assume v1)
- Add comprehensive test suite with 40+ test cases
- Update documentation with version negotiation details

This ensures BitChat clients can negotiate protocol versions for smooth
upgrades while maintaining full backward compatibility with existing clients.
This commit is contained in:
jack
2025-07-15 14:33:37 +02:00
parent 59927486eb
commit 3fb39b8a30
11 changed files with 1532 additions and 9 deletions
+5 -2
View File
@@ -150,8 +150,11 @@ struct BinaryProtocol {
// Header
let version = unpaddedData[offset]; offset += 1
// Only support version 1
guard version == 1 else { return nil }
// Check if version is supported
guard ProtocolVersion.isSupported(version) else {
// Log unsupported version for debugging
return nil
}
let type = unpaddedData[offset]; offset += 1
let ttl = unpaddedData[offset]; offset += 1
+92
View File
@@ -99,6 +99,10 @@ enum MessageType: UInt8 {
case channelPasswordUpdate = 0x16 // Distribute new password to channel members
case channelMetadata = 0x17 // Announce channel creator and metadata
// Protocol version negotiation
case versionHello = 0x20 // Initial version announcement
case versionAck = 0x21 // Version acknowledgment
var description: String {
switch self {
case .announce: return "announce"
@@ -120,6 +124,8 @@ enum MessageType: UInt8 {
case .channelKeyVerifyResponse: return "channelKeyVerifyResponse"
case .channelPasswordUpdate: return "channelPasswordUpdate"
case .channelMetadata: return "channelMetadata"
case .versionHello: return "versionHello"
case .versionAck: return "versionAck"
}
}
}
@@ -382,6 +388,92 @@ struct PeerIdentityBinding {
}
}
// MARK: - Protocol Version Negotiation
// Protocol version constants
struct ProtocolVersion {
static let current: UInt8 = 1
static let minimum: UInt8 = 1
static let maximum: UInt8 = 1
// Future versions can be added here
static let supportedVersions: Set<UInt8> = [1]
static func isSupported(_ version: UInt8) -> Bool {
return supportedVersions.contains(version)
}
static func negotiateVersion(clientVersions: [UInt8], serverVersions: [UInt8]) -> UInt8? {
// Find the highest common version
let clientSet = Set(clientVersions)
let serverSet = Set(serverVersions)
let common = clientSet.intersection(serverSet)
return common.max()
}
}
// Version negotiation hello message
struct VersionHello: Codable {
let supportedVersions: [UInt8] // List of supported protocol versions
let preferredVersion: UInt8 // Preferred version (usually the latest)
let clientVersion: String // App version string (e.g., "1.0.0")
let platform: String // Platform identifier (e.g., "iOS", "macOS")
let capabilities: [String]? // Optional capability flags for future extensions
init(supportedVersions: [UInt8] = Array(ProtocolVersion.supportedVersions),
preferredVersion: UInt8 = ProtocolVersion.current,
clientVersion: String,
platform: String,
capabilities: [String]? = nil) {
self.supportedVersions = supportedVersions
self.preferredVersion = preferredVersion
self.clientVersion = clientVersion
self.platform = platform
self.capabilities = capabilities
}
func encode() -> Data? {
return try? JSONEncoder().encode(self)
}
static func decode(from data: Data) -> VersionHello? {
try? JSONDecoder().decode(VersionHello.self, from: data)
}
}
// Version negotiation acknowledgment
struct VersionAck: Codable {
let agreedVersion: UInt8 // The version both peers will use
let serverVersion: String // Responder's app version
let platform: String // Responder's platform
let capabilities: [String]? // Responder's capabilities
let rejected: Bool // True if no compatible version found
let reason: String? // Reason for rejection if applicable
init(agreedVersion: UInt8,
serverVersion: String,
platform: String,
capabilities: [String]? = nil,
rejected: Bool = false,
reason: String? = nil) {
self.agreedVersion = agreedVersion
self.serverVersion = serverVersion
self.platform = platform
self.capabilities = capabilities
self.rejected = rejected
self.reason = reason
}
func encode() -> Data? {
return try? JSONEncoder().encode(self)
}
static func decode(from data: Data) -> VersionAck? {
try? JSONDecoder().decode(VersionAck.self, from: data)
}
}
// Delivery status for messages
enum DeliveryStatus: Codable, Equatable {
case sending