diff --git a/CMakeLists.txt b/CMakeLists.txt index c8d4234..d9236b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,10 +24,17 @@ target_include_directories(FoundationDB-Swift PUBLIC ) set(STATIC_SWIFT_LIBS - # ${SWIFT_SDK_STATIC_DIR}/libswiftCore.a - # ${SWIFT_SDK_STATIC_DIR}/libswiftDispatch.a - # ${SWIFT_SDK_STATIC_DIR}/libswiftSynchronization.a - # ${SWIFT_SDK_STATIC_DIR}/libswift_Concurrency.a + ${SWIFT_SDK_STATIC_DIR}/libswiftCore.a + ${SWIFT_SDK_STATIC_DIR}/libswiftDispatch.a + ${SWIFT_SDK_STATIC_DIR}/libswiftSynchronization.a + ${SWIFT_SDK_STATIC_DIR}/libswift_Concurrency.a + + ${SWIFT_SDK_STATIC_DIR}/libFoundation.a + ${SWIFT_SDK_STATIC_DIR}/libCoreFoundation.a + ${SWIFT_SDK_STATIC_DIR}/libFoundationInternationalization.a + ${SWIFT_SDK_STATIC_DIR}/libswiftCore.a + ${SWIFT_SDK_STATIC_DIR}/libswift_RegexParser.a + ${SWIFT_SDK_STATIC_DIR}/libswift_StringProcessing.a ) target_link_directories(FoundationDB-Swift PUBLIC diff --git a/Package.swift b/Package.swift index a91215a..41343d2 100644 --- a/Package.swift +++ b/Package.swift @@ -23,8 +23,11 @@ import PackageDescription let package = Package( name: "FoundationDB", + platforms: [ + .macOS(.v14) + ], products: [ - .library(name: "FoundationDB", targets: ["FoundationDB"]), + .library(name: "FoundationDB", targets: ["FoundationDB"]) ], targets: [ .systemLibrary( @@ -33,7 +36,10 @@ let package = Package( .target( name: "FoundationDB", dependencies: ["CFoundationDB"], - path: "Sources/FoundationDB" + path: "Sources/FoundationDB", + linkerSettings: [ + .unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "/usr/local/lib"]) + ] ), .testTarget( name: "FoundationDBTests", diff --git a/Sources/CFoundationDB/fdb_c_wrapper.h b/Sources/CFoundationDB/fdb_c_wrapper.h index eb8f344..77515ab 100644 --- a/Sources/CFoundationDB/fdb_c_wrapper.h +++ b/Sources/CFoundationDB/fdb_c_wrapper.h @@ -21,7 +21,7 @@ #ifndef FDB_C_WRAPPER_H #define FDB_C_WRAPPER_H -#define FDB_API_VERSION 740 +#define FDB_API_VERSION 730 #include #endif diff --git a/Sources/FoundationDB/Client.swift b/Sources/FoundationDB/Client.swift index 3ca6772..88c82b3 100644 --- a/Sources/FoundationDB/Client.swift +++ b/Sources/FoundationDB/Client.swift @@ -37,8 +37,8 @@ import CFoundationDB public class FdbClient { /// FoundationDB API version constants. public enum APIVersion { - /// The current supported API version (740). - public static let current: Int32 = 740 + /// The current supported API version (730). + public static let current: Int32 = 730 } /// Initializes the FoundationDB client with the specified API version. diff --git a/Sources/FoundationDB/Network.swift b/Sources/FoundationDB/Network.swift index e187189..756e557 100644 --- a/Sources/FoundationDB/Network.swift +++ b/Sources/FoundationDB/Network.swift @@ -45,7 +45,7 @@ class FdbNetwork { /// Indicates whether the network has been set up. private var networkSetup = false /// The pthread handle for the network thread. - private var networkThread: pthread_t = .init() + private var networkThread: pthread_t? = nil /// Initializes the FoundationDB network with the specified API version. /// @@ -80,7 +80,7 @@ class FdbNetwork { /// /// This method must be called before starting the network thread. /// - /// - Throws: `FdbError` if network setup fails or if already set up. + /// - Throws: `FdbError` if network setup fails or if already set up.X func setupNetwork() throws { guard !networkSetup else { throw FdbError(.networkError) @@ -100,10 +100,10 @@ class FdbNetwork { /// The network must be set up before calling this method. func startNetwork() { guard networkSetup else { - fatalError("Network must be setup before starting network thread") + fatalError("Network must be setup before starting thread network") } - var thread = pthread_t() + var thread = pthread_t(bitPattern: 0) let result = pthread_create(&thread, nil, { _ in let error = fdb_run_network() if error != 0 { @@ -127,7 +127,7 @@ class FdbNetwork { } if networkSetup { - pthread_join(networkThread, nil) + pthread_join(networkThread!, nil) } networkSetup = false } diff --git a/Sources/FoundationDB/Tuple.swift b/Sources/FoundationDB/Tuple.swift new file mode 100644 index 0000000..76d6b3c --- /dev/null +++ b/Sources/FoundationDB/Tuple.swift @@ -0,0 +1,499 @@ +/* + * Tuple.swift + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2016-2025 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Foundation + +public enum TupleError: Error, Sendable { + case invalidTupleElement + case invalidEncoding + case invalidDecoding(String) + case unsupportedType +} + +public enum TupleTypeCode: UInt8, CaseIterable { + case null = 0x00 + case bytes = 0x01 + case string = 0x02 + case nested = 0x05 + case negativeIntStart = 0x0B + case intZero = 0x14 + case positiveIntEnd = 0x1D + case float = 0x20 + case double = 0x21 + case boolFalse = 0x26 + case boolTrue = 0x27 + case uuid = 0x30 + case versionstamp = 0x33 +} + +public protocol TupleElement: Sendable { + func encodeTuple() -> Fdb.Bytes + static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Self +} + +public struct Tuple: Sendable { + private let elements: [any TupleElement] + + public init(_ elements: any TupleElement...) { + self.elements = elements + } + + public init(_ elements: [any TupleElement]) { + self.elements = elements + } + + public subscript(index: Int) -> (any TupleElement)? { + guard index >= 0 && index < elements.count else { return nil } + return elements[index] + } + + public var count: Int { + return elements.count + } + + public func encode() -> Fdb.Bytes { + var result = Fdb.Bytes() + for element in elements { + result.append(contentsOf: element.encodeTuple()) + } + return result + } + + public static func decode(from bytes: Fdb.Bytes) throws -> [any TupleElement] { + var elements: [any TupleElement] = [] + var offset = 0 + + while offset < bytes.count { + let typeCode = bytes[offset] + offset += 1 + + switch typeCode { + case TupleTypeCode.null.rawValue: + elements.append(TupleNil()) + case TupleTypeCode.bytes.rawValue: + let element = try Fdb.Bytes.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.string.rawValue: + let element = try String.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.boolFalse.rawValue, TupleTypeCode.boolTrue.rawValue: + let element = try Bool.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.float.rawValue: + let element = try Float.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.double.rawValue: + let element = try Double.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.uuid.rawValue: + let element = try UUID.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.intZero.rawValue: + elements.append(0) + case TupleTypeCode.negativeIntStart.rawValue...TupleTypeCode.positiveIntEnd.rawValue: + let element = try Int64.decodeTuple(from: bytes, at: &offset) + elements.append(element) + case TupleTypeCode.nested.rawValue: + let element = try Tuple.decodeTuple(from: bytes, at: &offset) + elements.append(element) + default: + throw TupleError.invalidDecoding("Unknown type code: \(typeCode)") + } + } + + return elements + } +} + +public struct TupleNil: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + return [TupleTypeCode.null.rawValue] + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> TupleNil { + return TupleNil() + } +} + +extension String: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + var encoded = [TupleTypeCode.string.rawValue] + let utf8Bytes = Array(self.utf8) + + for byte in utf8Bytes { + if byte == 0x00 { + encoded.append(contentsOf: [0x00, 0xFF]) + } else { + encoded.append(byte) + } + } + encoded.append(0x00) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> String { + var decoded = Fdb.Bytes() + + while offset < bytes.count { + let byte = bytes[offset] + offset += 1 + + if byte == 0x00 { + if offset < bytes.count && bytes[offset] == 0xFF { + offset += 1 + decoded.append(0x00) + } else { + break + } + } else { + decoded.append(byte) + } + } + + return String(bytes: decoded, encoding: .utf8)! + } +} + +extension Fdb.Bytes: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + var encoded = [TupleTypeCode.bytes.rawValue] + for byte in self { + if byte == 0x00 { + encoded.append(contentsOf: [0x00, 0xFF]) + } else { + encoded.append(byte) + } + } + encoded.append(0x00) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Fdb.Bytes + { + var decoded = Fdb.Bytes() + + while offset < bytes.count { + let byte = bytes[offset] + offset += 1 + + if byte == 0x00 { + if offset < bytes.count && bytes[offset] == 0xFF { + offset += 1 + decoded.append(0x00) + } else { + break + } + } else { + decoded.append(byte) + } + } + + return decoded + } +} + +extension Bool: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + return self ? [TupleTypeCode.boolTrue.rawValue] : [TupleTypeCode.boolFalse.rawValue] + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Bool { + guard offset > 0 else { + throw TupleError.invalidDecoding("Bool decoding requires type code") + } + let typeCode = bytes[offset - 1] + + switch typeCode { + case TupleTypeCode.boolTrue.rawValue: + return true + case TupleTypeCode.boolFalse.rawValue: + return false + default: + throw TupleError.invalidDecoding("Invalid bool type code: \(typeCode)") + } + } +} + +extension Float: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + var encoded = [TupleTypeCode.float.rawValue] + let bitPattern = self.bitPattern + let bytes = withUnsafeBytes(of: bitPattern.bigEndian) { Array($0) } + encoded.append(contentsOf: bytes) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Float { + guard offset + 4 <= bytes.count else { + throw TupleError.invalidDecoding("Not enough bytes for Float") + } + + let floatBytes = Array(bytes[offset.. Fdb.Bytes { + var encoded = [TupleTypeCode.double.rawValue] + let bitPattern = self.bitPattern + let bytes = withUnsafeBytes(of: bitPattern.bigEndian) { Array($0) } + encoded.append(contentsOf: bytes) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Double { + guard offset + 8 <= bytes.count else { + throw TupleError.invalidDecoding("Not enough bytes for Double") + } + + let doubleBytes = Array(bytes[offset.. Fdb.Bytes { + var encoded = [TupleTypeCode.uuid.rawValue] + let (u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15, u16) = self.uuid + encoded.append(contentsOf: [ + u1, u2, u3, u4, u5, u6, u7, u8, u9, u10, u11, u12, u13, u14, u15, u16, + ]) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> UUID { + guard offset + 16 <= bytes.count else { + throw TupleError.invalidDecoding("Not enough bytes for UUID") + } + + let uuidBytes = Array(bytes[offset.. Int { + var n = 0 + while n < sizeLimits.count && sizeLimits[n] < value { + n += 1 + } + return n +} + +extension Int64: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + return encodeInt(self) + } + + private func encodeInt(_ value: Int64) -> Fdb.Bytes { + if value == 0 { + return [TupleTypeCode.intZero.rawValue] + } + + var encoded = Fdb.Bytes() + if value > 0 { + let n = bisectLeft(UInt64(value)) + encoded.append(TupleTypeCode.intZero.rawValue + UInt8(n)) + let bigEndianValue = UInt64(bitPattern: value).bigEndian + let bytes = withUnsafeBytes(of: bigEndianValue) { Array($0) } + encoded.append(contentsOf: bytes.suffix(n)) + } else { + let n = bisectLeft(UInt64(-value)) + encoded.append(TupleTypeCode.intZero.rawValue - UInt8(n)) + + if n < 8 { + let offset = UInt64(sizeLimits[n]) &+ UInt64(bitPattern: value) + let bigEndianValue = offset.bigEndian + let bytes = withUnsafeBytes(of: bigEndianValue) { Array($0) } + encoded.append(contentsOf: bytes.suffix(n)) + } else { + // n == 8 case + let offset = UInt64(bitPattern: value) + let bigEndianValue = offset.bigEndian + let bytes = withUnsafeBytes(of: bigEndianValue) { Array($0) } + encoded.append(contentsOf: bytes) + } + } + + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Int64 { + guard offset > 0 else { + throw TupleError.invalidDecoding("Int64 decoding requires type code") + } + let typeCode = bytes[offset - 1] + + if typeCode == TupleTypeCode.intZero.rawValue { + return 0 + } + + var n = Int(typeCode) - Int(TupleTypeCode.intZero.rawValue) + var neg = false + if n < 0 { + n = -n + neg = true + } + + var bp = [UInt8](repeating: 0, count: 8) + bp.replaceSubrange((8 - n)..<8, with: bytes[offset...(offset+n-1)]) + offset += n + + var ret: Int64 = 0 + for byte in bp { + ret = (ret << 8) | Int64(byte) + } + + if neg { + if n == 8 { + return ret + } else { + return ret - Int64(sizeLimits[n]) + } + } + + if ret > 0 { + return ret + } + + return ret + } +} + +extension Tuple: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + var encoded = [TupleTypeCode.nested.rawValue] + for element in elements { + let elementBytes = element.encodeTuple() + for byte in elementBytes { + if byte == 0x00 { + encoded.append(contentsOf: [0x00, 0xFF]) + } else { + encoded.append(byte) + } + } + } + encoded.append(0x00) + return encoded + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Tuple { + var nestedBytes = Fdb.Bytes() + + while offset < bytes.count { + let byte = bytes[offset] + offset += 1 + + if byte == 0x00 { + if offset < bytes.count && bytes[offset] == 0xFF { + offset += 1 + nestedBytes.append(0x00) + } else { + break + } + } else { + nestedBytes.append(byte) + } + } + + let nestedElements = try Tuple.decode(from: nestedBytes) + return Tuple(nestedElements) + } +} + +extension Int: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + return Int64(self).encodeTuple() + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Int { + let value = try Int64.decodeTuple(from: bytes, at: &offset) + guard value >= Int.min && value <= Int.max else { + throw TupleError.invalidDecoding("Int64 value \(value) out of range for Int") + } + return Int(value) + } +} + +extension Int32: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + return Int64(self).encodeTuple() + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> Int32 { + let value = try Int64.decodeTuple(from: bytes, at: &offset) + guard value >= Int32.min && value <= Int32.max else { + throw TupleError.invalidDecoding("Int64 value \(value) out of range for Int32") + } + return Int32(value) + } +} + +extension UInt64: TupleElement { + public func encodeTuple() -> Fdb.Bytes { + if self <= Int64.max { + return Int64(self).encodeTuple() + } else { + return Int64.max.encodeTuple() + } + } + + public static func decodeTuple(from bytes: Fdb.Bytes, at offset: inout Int) throws -> UInt64 { + let value = try Int64.decodeTuple(from: bytes, at: &offset) + guard value >= 0 else { + throw TupleError.invalidDecoding( + "Negative value \(value) cannot be converted to UInt64") + } + return UInt64(value) + } +} diff --git a/Tests/FoundationDBTests/FoundationDBTupleTests.swift b/Tests/FoundationDBTests/FoundationDBTupleTests.swift new file mode 100644 index 0000000..9aa77da --- /dev/null +++ b/Tests/FoundationDBTests/FoundationDBTupleTests.swift @@ -0,0 +1,340 @@ +/* + * FoundationDBTupleTests.swift + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2016-2025 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Testing +import Foundation + +@testable import FoundationDB + +@Test("TupleNil encoding and decoding") +func testTupleNil() throws { + let tupleNil = TupleNil() + let encoded = tupleNil.encodeTuple() + #expect(encoded == [TupleTypeCode.null.rawValue], "TupleNil should encode to null type code") + + var offset = 1 + let decoded = try TupleNil.decodeTuple(from: encoded, at: &offset) + #expect(decoded is TupleNil, "Should decode back to TupleNil") + #expect(offset == 1, "Offset should not advance for TupleNil") +} + +@Test("TupleString encoding and decoding") +func testTupleString() throws { + let testString = "Hello, World!" + let encoded = testString.encodeTuple() + + #expect(encoded.first == TupleTypeCode.string.rawValue, "TupleString should start with string type code") + #expect(encoded.last == 0x00, "TupleString should end with null terminator") + + var offset = 1 + let decoded = try String.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testString, "Should decode back to original string") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleString with null bytes") +func testTupleStringWithNulls() throws { + let testString = "Hello\u{0}World" + let encoded = testString.encodeTuple() + + #expect(encoded.contains(0x00), "Encoded string should contain null bytes") + #expect(encoded.contains(0xFF), "Null bytes should be escaped with 0xFF") + + var offset = 1 + let decoded = try String.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testString, "Should decode back to original string with nulls") +} + +@Test("TupleBool encoding and decoding") +func testTupleBool() throws { + let testTrue = true + let testFalse = false + + let encodedTrue = testTrue.encodeTuple() + let encodedFalse = testFalse.encodeTuple() + + #expect(encodedTrue == [TupleTypeCode.boolTrue.rawValue], "true should encode to boolTrue type code") + #expect(encodedFalse == [TupleTypeCode.boolFalse.rawValue], "false should encode to boolFalse type code") + + var offsetTrue = 1 + var offsetFalse = 1 + + let decodedTrue = try Bool.decodeTuple(from: encodedTrue, at: &offsetTrue) + let decodedFalse = try Bool.decodeTuple(from: encodedFalse, at: &offsetFalse) + + #expect(decodedTrue == true, "Should decode back to true") + #expect(decodedFalse == false, "Should decode back to false") +} + +@Test("TupleFloat encoding and decoding") +func testTupleFloat() throws { + let testFloat: Float = 3.14159 + let encoded = testFloat.encodeTuple() + + #expect(encoded.first == TupleTypeCode.float.rawValue, "Float should start with float type code") + #expect(encoded.count == 5, "Float should be 5 bytes (1 type code + 4 data)") + + var offset = 1 + let decoded = try Float.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testFloat, "Should decode back to original float") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleDouble encoding and decoding") +func testTupleDouble() throws { + let testDouble: Double = 3.141592653589793 + let encoded = testDouble.encodeTuple() + + #expect(encoded.first == TupleTypeCode.double.rawValue, "Double should start with double type code") + #expect(encoded.count == 9, "Double should be 9 bytes (1 type code + 8 data)") + + var offset = 1 + let decoded = try Double.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testDouble, "Should decode back to original double") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleUUID encoding and decoding") +func testTupleUUID() throws { + let testUUID = UUID() + let encoded = testUUID.encodeTuple() + + #expect(encoded.first == TupleTypeCode.uuid.rawValue, "UUID should start with uuid type code") + #expect(encoded.count == 17, "UUID should be 17 bytes (1 type code + 16 data)") + + var offset = 1 + let decoded = try UUID.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testUUID, "Should decode back to original UUID") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Zero") +func testTupleInt64Zero() throws { + let testInt: Int64 = 0 + let encoded = testInt.encodeTuple() + + #expect(encoded == [TupleTypeCode.intZero.rawValue], "Zero should encode to intZero type code") + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original zero") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Small positive") +func testTupleInt64SmallPositive() throws { + let testInt: Int64 = 42 + let encoded = testInt.encodeTuple() + + #expect(encoded.first == 0x15, "Small positive should use 0x15 type code (positiveInt1)") + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original positive integer") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Very small negative") +func testTupleInt64VerySmallNegative() throws { + let testInt: Int64 = -42 + let encoded = testInt.encodeTuple() + + #expect(encoded.first == 0x13) + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original positive integer") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Large negative") +func testTupleInt64LargeNegative() throws { + let testInt: Int64 = -89_034_333_444 + let encoded = testInt.encodeTuple() + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original negative integer") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Very Large negative") +func testTupleInt64VeryLargeNegative() throws { + let testInt: Int64 = -(1 << 55) - 34897432 + let encoded = testInt.encodeTuple() + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original negative integer") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - VeryVery Large negative") +func testTupleInt64VeryLargeNegative2() throws { + let testInt: Int64 = -(1 << 60) - 34897432 + let encoded = testInt.encodeTuple() + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original negative integer") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") +} + +@Test("TupleInt64 encoding and decoding - Large values") +func testTupleInt64LargeValues() throws { + let largePositive: Int64 = Int64.max + let largeNegative: Int64 = Int64.min + 1 + + let encodedPos = largePositive.encodeTuple() + let encodedNeg = largeNegative.encodeTuple() + + var offsetPos = 1 + var offsetNeg = 1 + + let decodedPos = try Int64.decodeTuple(from: encodedPos, at: &offsetPos) + let decodedNeg = try Int64.decodeTuple(from: encodedNeg, at: &offsetNeg) + + #expect(decodedPos == largePositive, "Should decode back to Int64.max") + #expect(decodedNeg == largeNegative, "Should decode back to Int64.min") +} + +@Test("TupleInt32 encoding and decoding") +func testTupleInt32() throws { + let testInt: Int32 = -2_034_333_444 + let encoded = testInt.encodeTuple() + + var offset = 1 + let decoded = try Int32.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original Int32") +} + +@Test("TupleInt encoding and decoding") +func testTupleInt() throws { + let testInt: Int = 123456 + let encoded = testInt.encodeTuple() + + var offset = 1 + let decoded = try Int.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Should decode back to original Int") +} + +@Test("TupleUInt64 encoding and decoding") +func testTupleUInt64() throws { + let testUInt: UInt64 = 999999 + let encoded = testUInt.encodeTuple() + + var offset = 1 + let decoded = try UInt64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testUInt, "Should decode back to original UInt64") +} + +@Test("TupleNested encoding and decoding") +func testTupleNested() throws { + let innerTuple = Tuple("hello", 42, true) + let outerTuple = Tuple("outer", innerTuple, "end") + + let encoded = outerTuple.encode() + let decoded = try Tuple.decode(from: encoded) + + #expect(decoded.count == 3, "Should have 3 elements") + + let decodedString1 = decoded[0] as? String + #expect(decodedString1 == "outer", "First element should be 'outer'") + + let decodedNested = decoded[1] as? Tuple + #expect(decodedNested != nil, "Second element should be a Tuple") + #expect(decodedNested?.count == 3, "Nested tuple should have 3 elements") + + let decodedString2 = decoded[2] as? String + #expect(decodedString2 == "end", "Third element should be 'end'") +} + +@Test("Tuple with a zero integer") +func testTupleWithZero() throws { + let tuple = Tuple("hello", 0, "foo") + + let encoded = tuple.encode() + let decoded = try Tuple.decode(from: encoded) + + #expect(decoded.count == 3, "Should have 3 elements") + let decodedString1 = decoded[0] as? String + #expect(decodedString1 == "hello") + + let decodedInt = decoded[1] as? Int + #expect(decodedInt == 0) + + let decodedString2 = decoded[2] as? String + #expect(decodedString2 == "foo") +} + + +@Test("TupleNested deep nesting") +func testTupleNestedDeep() throws { + let level3 = Tuple("deep", 123) + let level2 = Tuple("middle", level3) + let level1 = Tuple("top", level2, "bottom") + + let encoded = level1.encode() + let decoded = try Tuple.decode(from: encoded) + + #expect(decoded.count == 3, "Top level should have 3 elements") + + let topString = decoded[0] as? String + #expect(topString == "top", "First element should be 'top'") + + let middleTuple = decoded[1] as? Tuple + #expect(middleTuple != nil, "Second element should be a Tuple") + #expect(middleTuple?.count == 2, "Middle tuple should have 2 elements") + + let bottomString = decoded[2] as? String + #expect(bottomString == "bottom", "Third element should be 'bottom'") +} + +@Test("TupleInt64 encoding and decoding - 1 million distributed integers") +func testTupleInt64DistributedIntegers() throws { + // Deterministic random number generator using LCG algorithm + var seed: UInt64 = 12345 + func nextRandom() -> Int64 { + // Generate full 64-bit value + seed = seed &* 6364136223846793005 &+ 1442695040888963407 + return Int64(bitPattern: seed) + } + + // Test 10000 integers + var positive: Int = 0 + var negative: Int = 0 + for _ in 0..<1000000 { + let testInt = nextRandom() + let encoded = testInt.encodeTuple() + + if testInt > 0 { + positive += 1 + } else if testInt < 0 { + negative += 1 + } + + var offset = 1 + let decoded = try Int64.decodeTuple(from: encoded, at: &offset) + #expect(decoded == testInt, "Integer \(testInt) should encode and decode correctly") + #expect(offset == encoded.count, "Offset should advance to end of encoded data") + } + + print("tested with n_positives = \(positive), n_negatives = \(negative)") +}