// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation import Sodium import Quick import Nimble @testable import SessionMessagingKit class LibSessionTypeConversionUtilitiesSpec: QuickSpec { override class func spec() { // MARK: - a String describe("a String") { // MARK: -- can convert to a cArray it("can convert to a cArray") { expect("Test123".cArray).to(equal([84, 101, 115, 116, 49, 50, 51])) } // MARK: -- can contain emoji it("can contain emoji") { let original: String = "Hi 👋" let libSessionVal: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) = original.toLibSession() let result: String? = String(libSessionVal: libSessionVal) expect(result).to(equal(original)) } // MARK: -- when initialised with a pointer and length context("when initialised with a pointer and length") { // MARK: ---- returns null when given a null pointer it("returns null when given a null pointer") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in String(pointer: nil, length: 5) } expect(result).to(beNil()) } // MARK: ---- returns a truncated string when given an incorrect length it("returns a truncated string when given an incorrect length") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 2) } expect(result).to(equal("Te")) } // MARK: ---- returns a string when valid it("returns a string when valid") { let test: [CChar] = [84, 101, 115, 116] let result = test.withUnsafeBufferPointer { ptr in String(pointer: UnsafeRawPointer(ptr.baseAddress), length: 4) } expect(result).to(equal("Test")) } } // MARK: -- when initialised with a libSession value context("when initialised with a libSession value") { // MARK: ---- returns a string when valid and has no fixed length it("returns a string when valid and has no fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 0) let result = String(libSessionVal: value, fixedLength: .none) expect(result).to(equal("Test")) } // MARK: ---- returns a string when valid and has a fixed length it("returns a string when valid and has a fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) let result = String(libSessionVal: value, fixedLength: 5) expect(result).to(equal("Te\0st")) } // MARK: ---- truncates at the first null termination character when fixed length is none it("truncates at the first null termination character when fixed length is none") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 115, 116) let result = String(libSessionVal: value, fixedLength: .none) expect(result).to(equal("Te")) } // MARK: ---- parses successfully if there is no null termination character and there is no fixed length it("parses successfully if there is no null termination character and there is no fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 115, 116, 84) let result = String(libSessionVal: value, fixedLength: .none) expect(result).to(equal("TestT")) } // MARK: ---- returns an empty string when given a value only containing null termination characters with a fixed length it("returns an empty string when given a value only containing null termination characters with a fixed length") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, fixedLength: 5) expect(result).to(equal("")) } // MARK: ---- defaults the fixed length value to none it("defaults the fixed length value to none") { let value: (CChar, CChar, CChar, CChar, CChar) = (84, 101, 0, 0, 0) let result = String(libSessionVal: value) expect(result).to(equal("Te")) } // MARK: ---- returns an empty string when null and not set to return null it("returns an empty string when null and not set to return null") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, nullIfEmpty: false) expect(result).to(equal("")) } // MARK: ---- returns null when specified and empty it("returns null when specified and empty") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value, nullIfEmpty: true) expect(result).to(beNil()) } // MARK: ---- defaults the null if empty flag to false it("defaults the null if empty flag to false") { let value: (CChar, CChar, CChar, CChar, CChar) = (0, 0, 0, 0, 0) let result = String(libSessionVal: value) expect(result).to(equal("")) } } // MARK: -- when converting to a libSession value context("when converting to a libSession value") { // MARK: ---- succeeeds with a valid value it("succeeeds with a valid value") { let result: (CChar, CChar, CChar, CChar, CChar) = "Test".toLibSession() expect(result.0).to(equal(84)) expect(result.1).to(equal(101)) expect(result.2).to(equal(115)) expect(result.3).to(equal(116)) expect(result.4).to(equal(0)) } // MARK: ---- truncates when too long it("truncates when too long") { let result: (CChar, CChar, CChar, CChar, CChar) = "TestTest".toLibSession() expect(result.0).to(equal(84)) expect(result.1).to(equal(101)) expect(result.2).to(equal(115)) expect(result.3).to(equal(116)) expect(result.4).to(equal(84)) } // MARK: ---- when optional context("when optional") { // MARK: ------ returns empty when null context("returns empty when null") { let value: String? = nil let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() expect(result.0).to(equal(0)) expect(result.1).to(equal(0)) expect(result.2).to(equal(0)) expect(result.3).to(equal(0)) expect(result.4).to(equal(0)) } // MARK: ------ returns a libSession value when not null context("returns a libSession value when not null") { let value: String? = "Test" let result: (CChar, CChar, CChar, CChar, CChar) = value.toLibSession() expect(result.0).to(equal(84)) expect(result.1).to(equal(101)) expect(result.2).to(equal(115)) expect(result.3).to(equal(116)) expect(result.4).to(equal(0)) } } } } // MARK: - Data describe("Data") { // MARK: -- can convert to a cArray it("can convert to a cArray") { expect(Data([1, 2, 3]).cArray).to(equal([1, 2, 3])) } // MARK: -- when initialised with a libSession value context("when initialised with a libSession value") { // MARK: ---- returns truncated data when given the wrong length it("returns truncated data when given the wrong length") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) let result = Data(libSessionVal: value, count: 2) expect(result).to(equal(Data([1, 2]))) } // MARK: ---- returns data when valid it("returns data when valid") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (1, 2, 3, 4, 5) let result = Data(libSessionVal: value, count: 5) expect(result).to(equal(Data([1, 2, 3, 4, 5]))) } // MARK: ---- returns data when all bytes are zero and nullIfEmpty is false it("returns data when all bytes are zero and nullIfEmpty is false") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) let result = Data(libSessionVal: value, count: 5, nullIfEmpty: false) expect(result).to(equal(Data([0, 0, 0, 0, 0]))) } // MARK: ---- returns null when all bytes are zero and nullIfEmpty is true it("returns null when all bytes are zero and nullIfEmpty is true") { let value: (UInt8, UInt8, UInt8, UInt8, UInt8) = (0, 0, 0, 0, 0) let result = Data(libSessionVal: value, count: 5, nullIfEmpty: true) expect(result).to(beNil()) } } // MARK: -- when converting to a libSession value context("when converting to a libSession value") { // MARK: ---- succeeeds with a valid value it("succeeeds with a valid value") { let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 5]).toLibSession() expect(result.0).to(equal(1)) expect(result.1).to(equal(2)) expect(result.2).to(equal(3)) expect(result.3).to(equal(4)) expect(result.4).to(equal(5)) } // MARK: ---- truncates when too long it("truncates when too long") { let result: (Int8, Int8, Int8, Int8, Int8) = Data([1, 2, 3, 4, 1, 2, 3, 4]).toLibSession() expect(result.0).to(equal(1)) expect(result.1).to(equal(2)) expect(result.2).to(equal(3)) expect(result.3).to(equal(4)) expect(result.4).to(equal(1)) } // MARK: ---- fills with empty data when too short context("fills with empty data when too short") { let value: Data? = Data([1, 2, 3]) let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() expect(result.0).to(equal(1)) expect(result.1).to(equal(2)) expect(result.2).to(equal(3)) expect(result.3).to(equal(0)) expect(result.4).to(equal(0)) } // MARK: ---- when optional context("when optional") { // MARK: ------ returns null when null context("returns null when null") { let value: Data? = nil let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() expect(result.0).to(equal(0)) expect(result.1).to(equal(0)) expect(result.2).to(equal(0)) expect(result.3).to(equal(0)) expect(result.4).to(equal(0)) } // MARK: ------ returns a libSession value when not null context("returns a libSession value when not null") { let value: Data? = Data([1, 2, 3, 4, 5]) let result: (Int8, Int8, Int8, Int8, Int8) = value.toLibSession() expect(result.0).to(equal(1)) expect(result.1).to(equal(2)) expect(result.2).to(equal(3)) expect(result.3).to(equal(4)) expect(result.4).to(equal(5)) } } } } // MARK: - an Array describe("an Array") { // MARK: -- when initialised with a 2D C array context("when initialised with a 2D C array") { // MARK: ---- returns the correct array it("returns the correct array") { var test: [CChar] = ( "Test1".cArray.nullTerminated() + "Test2".cArray.nullTerminated() + "Test3AndExtra".cArray.nullTerminated() ) let result = test.withUnsafeMutableBufferPointer { ptr in var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) return [String](pointer: &mutablePtr, count: 3) } expect(result).to(equal(["Test1", "Test2", "Test3AndExtra"])) } // MARK: ---- returns an empty array if given one it("returns an empty array if given one") { var test = [CChar]() let result = test.withUnsafeMutableBufferPointer { ptr in var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) return [String](pointer: &mutablePtr, count: 0) } expect(result).to(equal([])) } // MARK: ---- handles empty strings without issues it("handles empty strings without issues") { var test: [CChar] = ( "Test1".cArray.nullTerminated() + "".cArray.nullTerminated() + "Test2".cArray.nullTerminated() ) let result = test.withUnsafeMutableBufferPointer { ptr in var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) return [String](pointer: &mutablePtr, count: 3) } expect(result).to(equal(["Test1", "", "Test2"])) } // MARK: ---- returns null when given a null pointer it("returns null when given a null pointer") { expect([String](pointer: nil, count: 5)).to(beNil()) } // MARK: ---- returns null when given a null count it("returns null when given a null count") { var test: [CChar] = "Test1".cArray.nullTerminated() let result = test.withUnsafeMutableBufferPointer { ptr in var mutablePtr = UnsafeMutablePointer(ptr.baseAddress) return [String](pointer: &mutablePtr, count: nil) } expect(result).to(beNil()) } // MARK: ---- returns the default value if given null values it("returns the default value if given null values") { expect([String](pointer: nil, count: 5, defaultValue: ["Test"])) .to(equal(["Test"])) } } // MARK: -- when adding a null terminated character context("when adding a null terminated character") { // MARK: ---- adds a null termination character when not present it("adds a null termination character when not present") { let value: [CChar] = [1, 2, 3, 4, 5] expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 5, 0])) } // MARK: ---- adds nothing when already present it("adds nothing when already present") { let value: [CChar] = [1, 2, 3, 4, 0] expect(value.nullTerminated()).to(equal([1, 2, 3, 4, 0])) } } } } }