mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 12:45:20 +00:00
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:
@@ -0,0 +1,230 @@
|
||||
//
|
||||
// BinaryProtocolVersionTests.swift
|
||||
// bitchatTests
|
||||
//
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
// For more information, see <https://unlicense.org>
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import bitchat
|
||||
|
||||
class BinaryProtocolVersionTests: XCTestCase {
|
||||
|
||||
// MARK: - Version Support Tests
|
||||
|
||||
func testCurrentVersionIsSupported() {
|
||||
// Current version should always be supported
|
||||
XCTAssertTrue(ProtocolVersion.isSupported(ProtocolVersion.current))
|
||||
}
|
||||
|
||||
func testVersion1IsSupported() {
|
||||
// Version 1 must be supported for backward compatibility
|
||||
XCTAssertTrue(ProtocolVersion.isSupported(1))
|
||||
}
|
||||
|
||||
func testUnsupportedVersionsRejected() {
|
||||
// Test various unsupported versions
|
||||
XCTAssertFalse(ProtocolVersion.isSupported(0))
|
||||
XCTAssertFalse(ProtocolVersion.isSupported(2))
|
||||
XCTAssertFalse(ProtocolVersion.isSupported(99))
|
||||
XCTAssertFalse(ProtocolVersion.isSupported(255))
|
||||
}
|
||||
|
||||
// MARK: - Binary Protocol Version Handling
|
||||
|
||||
func testBinaryProtocolRejectsUnsupportedVersion() {
|
||||
// Create a packet with unsupported version
|
||||
var data = Data()
|
||||
|
||||
// Header
|
||||
data.append(99) // Unsupported version
|
||||
data.append(MessageType.message.rawValue)
|
||||
data.append(5) // TTL
|
||||
|
||||
// Timestamp (8 bytes)
|
||||
let timestamp = UInt64(Date().timeIntervalSince1970 * 1000)
|
||||
for i in (0..<8).reversed() {
|
||||
data.append(UInt8((timestamp >> (i * 8)) & 0xFF))
|
||||
}
|
||||
|
||||
// Flags (no recipient, no signature)
|
||||
data.append(0)
|
||||
|
||||
// Payload length (2 bytes)
|
||||
let payload = Data("test".utf8)
|
||||
let payloadLength = UInt16(payload.count)
|
||||
data.append(UInt8((payloadLength >> 8) & 0xFF))
|
||||
data.append(UInt8(payloadLength & 0xFF))
|
||||
|
||||
// SenderID (8 bytes)
|
||||
data.append(Data(repeating: 0x01, count: 8))
|
||||
|
||||
// Payload
|
||||
data.append(payload)
|
||||
|
||||
// Try to decode - should fail due to unsupported version
|
||||
let decoded = BinaryProtocol.decode(data)
|
||||
XCTAssertNil(decoded, "Should reject packet with unsupported version")
|
||||
}
|
||||
|
||||
func testBinaryProtocolAcceptsVersion1() {
|
||||
// Create a valid version 1 packet
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.message.rawValue,
|
||||
senderID: Data("sender12".utf8),
|
||||
recipientID: nil,
|
||||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||||
payload: Data("Hello".utf8),
|
||||
signature: nil,
|
||||
ttl: 3
|
||||
)
|
||||
|
||||
// Encode
|
||||
guard let encoded = packet.toBinaryData() else {
|
||||
XCTFail("Failed to encode version 1 packet")
|
||||
return
|
||||
}
|
||||
|
||||
// Decode
|
||||
guard let decoded = BitchatPacket.from(encoded) else {
|
||||
XCTFail("Failed to decode version 1 packet")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(decoded.version, 1)
|
||||
XCTAssertEqual(decoded.payload, Data("Hello".utf8))
|
||||
}
|
||||
|
||||
// MARK: - Version Message Type Tests
|
||||
|
||||
func testVersionHelloMessageType() {
|
||||
let hello = VersionHello(
|
||||
clientVersion: "1.0.0",
|
||||
platform: "iOS"
|
||||
)
|
||||
|
||||
guard let helloData = hello.encode() else {
|
||||
XCTFail("Failed to encode VersionHello")
|
||||
return
|
||||
}
|
||||
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.versionHello.rawValue,
|
||||
ttl: 1,
|
||||
senderID: "testpeer",
|
||||
payload: helloData
|
||||
)
|
||||
|
||||
XCTAssertEqual(packet.type, MessageType.versionHello.rawValue)
|
||||
XCTAssertEqual(MessageType.versionHello.description, "versionHello")
|
||||
}
|
||||
|
||||
func testVersionAckMessageType() {
|
||||
let ack = VersionAck(
|
||||
agreedVersion: 1,
|
||||
serverVersion: "1.0.0",
|
||||
platform: "macOS"
|
||||
)
|
||||
|
||||
guard let ackData = ack.encode() else {
|
||||
XCTFail("Failed to encode VersionAck")
|
||||
return
|
||||
}
|
||||
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.versionAck.rawValue,
|
||||
ttl: 1,
|
||||
senderID: "testpeer",
|
||||
payload: ackData
|
||||
)
|
||||
|
||||
XCTAssertEqual(packet.type, MessageType.versionAck.rawValue)
|
||||
XCTAssertEqual(MessageType.versionAck.description, "versionAck")
|
||||
}
|
||||
|
||||
// MARK: - Compression Compatibility Tests
|
||||
|
||||
func testCompressedPacketWithVersion() {
|
||||
// Create a large payload that will trigger compression
|
||||
let largeContent = String(repeating: "Hello World! ", count: 100)
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.message.rawValue,
|
||||
senderID: Data("sender12".utf8),
|
||||
recipientID: nil,
|
||||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||||
payload: Data(largeContent.utf8),
|
||||
signature: nil,
|
||||
ttl: 3
|
||||
)
|
||||
|
||||
// Encode (should compress)
|
||||
guard let encoded = packet.toBinaryData() else {
|
||||
XCTFail("Failed to encode packet with compression")
|
||||
return
|
||||
}
|
||||
|
||||
// Decode
|
||||
guard let decoded = BitchatPacket.from(encoded) else {
|
||||
XCTFail("Failed to decode compressed packet")
|
||||
return
|
||||
}
|
||||
|
||||
// Verify version is preserved
|
||||
XCTAssertEqual(decoded.version, 1)
|
||||
XCTAssertEqual(decoded.payload, Data(largeContent.utf8))
|
||||
}
|
||||
|
||||
// MARK: - Future Version Migration Tests
|
||||
|
||||
func testVersionSetConsistency() {
|
||||
// Ensure version constants are consistent
|
||||
XCTAssertTrue(ProtocolVersion.supportedVersions.contains(ProtocolVersion.current))
|
||||
XCTAssertTrue(ProtocolVersion.supportedVersions.contains(ProtocolVersion.minimum))
|
||||
XCTAssertGreaterThanOrEqual(ProtocolVersion.current, ProtocolVersion.minimum)
|
||||
XCTAssertLessThanOrEqual(ProtocolVersion.current, ProtocolVersion.maximum)
|
||||
}
|
||||
|
||||
func testVersionNegotiationAlwaysPicksHighest() {
|
||||
// When multiple versions are supported, should pick highest
|
||||
let clientVersions: [UInt8] = [1, 2, 3, 4, 5]
|
||||
let serverVersions: [UInt8] = [3, 4, 5, 6, 7]
|
||||
|
||||
let agreed = ProtocolVersion.negotiateVersion(
|
||||
clientVersions: clientVersions,
|
||||
serverVersions: serverVersions
|
||||
)
|
||||
|
||||
XCTAssertEqual(agreed, 5) // Highest common version
|
||||
}
|
||||
|
||||
// MARK: - Packet Size Tests with Version Negotiation
|
||||
|
||||
func testVersionNegotiationPacketsAreSmall() {
|
||||
// Version negotiation should use minimal bandwidth
|
||||
let hello = VersionHello(
|
||||
clientVersion: "1.0.0",
|
||||
platform: "iOS"
|
||||
)
|
||||
|
||||
guard let helloData = hello.encode() else {
|
||||
XCTFail("Failed to encode hello")
|
||||
return
|
||||
}
|
||||
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.versionHello.rawValue,
|
||||
ttl: 1,
|
||||
senderID: "12345678",
|
||||
payload: helloData
|
||||
)
|
||||
|
||||
guard let encoded = packet.toBinaryData() else {
|
||||
XCTFail("Failed to encode packet")
|
||||
return
|
||||
}
|
||||
|
||||
// Version negotiation packets should be reasonably small
|
||||
XCTAssertLessThan(encoded.count, 512, "Version negotiation packet too large")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user