Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bitchat/Features/voice/VoiceRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,20 @@ final class VoiceRecorder: NSObject, AVAudioRecorderDelegate {
guard session.recordPermission == .granted else {
throw RecorderError.microphoneAccessDenied
}
#if targetEnvironment(simulator)
// allowBluetoothHFP is not available on iOS Simulator
try session.setCategory(
.playAndRecord,
mode: .default,
options: [.defaultToSpeaker, .allowBluetoothA2DP]
)
#else
try session.setCategory(
.playAndRecord,
mode: .default,
options: [.defaultToSpeaker, .allowBluetoothA2DP, .allowBluetoothHFP]
)
#endif
try session.setActive(true, options: .notifyOthersOnDeactivation)
#endif
#if os(macOS)
Expand Down
34 changes: 28 additions & 6 deletions bitchat/Protocols/BinaryEncodingUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,42 @@ extension Data {
return digest.map { String(format: "%02x", $0) }.joined()
}

/// Initialize Data from a hex string.
/// - Parameter hexString: A hex string, optionally prefixed with "0x" or "0X".
/// Whitespace is trimmed. Must have even length after prefix removal.
/// - Returns: nil if the string has odd length or contains invalid hex characters.
init?(hexString: String) {
let len = hexString.count / 2
var hex = hexString.trimmingCharacters(in: .whitespaces)

// Remove optional 0x prefix
if hex.hasPrefix("0x") || hex.hasPrefix("0X") {
hex = String(hex.dropFirst(2))
}

// Reject odd-length strings
guard hex.count % 2 == 0 else {
return nil
}

// Reject empty strings
guard !hex.isEmpty else {
self = Data()
return
}

let len = hex.count / 2
var data = Data(capacity: len)
var index = hexString.startIndex
var index = hex.startIndex

for _ in 0..<len {
let nextIndex = hexString.index(index, offsetBy: 2)
guard let byte = UInt8(String(hexString[index..<nextIndex]), radix: 16) else {
let nextIndex = hex.index(index, offsetBy: 2)
guard let byte = UInt8(String(hex[index..<nextIndex]), radix: 16) else {
return nil
}
data.append(byte)
index = nextIndex
}

self = data
}
}
Expand Down
108 changes: 108 additions & 0 deletions bitchatTests/Utils/HexStringTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//
// HexStringTests.swift
// bitchatTests
//
// Tests for Data(hexString:) hex parsing
//

import Testing
import Foundation
@testable import bitchat

struct HexStringTests {

// MARK: - Valid Hex Strings

@Test func validHexString() {
let data = Data(hexString: "0102030405")
#expect(data == Data([0x01, 0x02, 0x03, 0x04, 0x05]))
}

@Test func validHexStringUppercase() {
let data = Data(hexString: "AABBCCDD")
#expect(data == Data([0xAA, 0xBB, 0xCC, 0xDD]))
}

@Test func validHexStringMixedCase() {
let data = Data(hexString: "aAbBcCdD")
#expect(data == Data([0xAA, 0xBB, 0xCC, 0xDD]))
}

@Test func validHexStringWith0xPrefix() {
let data = Data(hexString: "0x0102030405")
#expect(data == Data([0x01, 0x02, 0x03, 0x04, 0x05]))
}

@Test func validHexStringWith0XPrefix() {
let data = Data(hexString: "0XAABBCCDD")
#expect(data == Data([0xAA, 0xBB, 0xCC, 0xDD]))
}

@Test func validHexStringWithWhitespace() {
let data = Data(hexString: " 0102030405 ")
#expect(data == Data([0x01, 0x02, 0x03, 0x04, 0x05]))
}

@Test func validHexStringWith0xPrefixAndWhitespace() {
let data = Data(hexString: " 0x0102030405 ")
#expect(data == Data([0x01, 0x02, 0x03, 0x04, 0x05]))
}

@Test func emptyHexString() {
let data = Data(hexString: "")
#expect(data == Data())
}

@Test func emptyHexStringWithWhitespace() {
let data = Data(hexString: " ")
#expect(data == Data())
}

@Test func emptyHexStringWith0xPrefix() {
let data = Data(hexString: "0x")
#expect(data == Data())
}

// MARK: - Invalid Hex Strings

@Test func oddLengthHexStringReturnsNil() {
let data = Data(hexString: "012")
#expect(data == nil)
}

@Test func oddLengthHexStringWith0xPrefixReturnsNil() {
let data = Data(hexString: "0x012")
#expect(data == nil)
}

@Test func invalidCharactersReturnNil() {
let data = Data(hexString: "GHIJ")
#expect(data == nil)
}

@Test func mixedValidAndInvalidCharactersReturnNil() {
let data = Data(hexString: "01GH")
#expect(data == nil)
}

@Test func specialCharactersReturnNil() {
let data = Data(hexString: "01-02")
#expect(data == nil)
}

// MARK: - Round Trip Tests

@Test func roundTripConversion() {
let original = Data([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF])
let hexString = original.hexEncodedString()
let roundTripped = Data(hexString: hexString)
#expect(roundTripped == original)
}

@Test func roundTripConversionWith0xPrefix() {
let original = Data([0xDE, 0xAD, 0xBE, 0xEF])
let hexString = "0x" + original.hexEncodedString()
let roundTripped = Data(hexString: hexString)
#expect(roundTripped == original)
}
}