// Plain Swift value types — what survives past the native struct/free-list lifetime // (Marshaling.swift converts the C structs into these and immediately frees the native // list). Nothing here holds a raw pointer. This is the Swift analog of the C# client's // Models.cs. Field naming follows Swift camelCase (the C structs use snake_case). import VoiceCatC /// Channel snapshot — mirrors `vc_channel` (the pull-based view; re-call `listChannels()` /// after `.channelList` / `.userJoined` / `.userLeft` / `.userUpdated` events). public struct Channel: Sendable, Equatable, Identifiable { public let id: UInt32 /// 0 = root. public let parentId: UInt32 public let name: String public let topic: String public let passwordProtected: Bool /// 0 = unlimited. public let maxUsers: UInt32 public init(id: UInt32, parentId: UInt32, name: String, topic: String, passwordProtected: Bool, maxUsers: UInt32) { self.id = id; self.parentId = parentId; self.name = name; self.topic = topic self.passwordProtected = passwordProtected; self.maxUsers = maxUsers } } /// Channel creation/edition descriptor — mirrors `vc_channel_info`. Used by /// `createChannel(_:)` and `editChannel(_:)`. `id == 0` means new channel (for create). public struct ChannelEdit: Sendable, Equatable { public let id: UInt32 // 0 = new channel for create public let parentId: UInt32 // 0 = root public let name: String public let topic: String public let passwordProtected: Bool public let password: String? // nil/empty ignored if passwordProtected == false public let maxUsers: UInt32 // 0 = unlimited public let sortOrder: UInt32 /// 0/nil fields use server defaults. public let audio: AudioConfig public init(id: UInt32, parentId: UInt32, name: String, topic: String, passwordProtected: Bool, password: String?, maxUsers: UInt32, sortOrder: UInt32, audio: AudioConfig) { self.id = id; self.parentId = parentId; self.name = name; self.topic = topic self.passwordProtected = passwordProtected; self.password = password self.maxUsers = maxUsers; self.sortOrder = sortOrder; self.audio = audio } } /// User snapshot — mirrors `vc_user`. public struct User: Sendable, Equatable, Identifiable { public let id: UInt32 public let nickname: String public let isGuest: Bool public let channelId: UInt32 public let selfMicMuted: Bool public let selfDeafened: Bool public let serverMuted: Bool public let serverDeafened: Bool public init(id: UInt32, nickname: String, isGuest: Bool, channelId: UInt32, selfMicMuted: Bool, selfDeafened: Bool, serverMuted: Bool, serverDeafened: Bool) { self.id = id; self.nickname = nickname; self.isGuest = isGuest; self.channelId = channelId self.selfMicMuted = selfMicMuted; self.selfDeafened = selfDeafened self.serverMuted = serverMuted; self.serverDeafened = serverDeafened } } /// Permission bitset — mirrors `vc_permissions` (M5). public struct Permissions: Sendable, Equatable { public let canCreateTempChannel: Bool public let canKick: Bool public let canBan: Bool public let canMoveUsers: Bool public let canAdminAccounts: Bool public let isAdmin: Bool public init(canCreateTempChannel: Bool, canKick: Bool, canBan: Bool, canMoveUsers: Bool, canAdminAccounts: Bool, isAdmin: Bool) { self.canCreateTempChannel = canCreateTempChannel; self.canKick = canKick; self.canBan = canBan self.canMoveUsers = canMoveUsers; self.canAdminAccounts = canAdminAccounts; self.isAdmin = isAdmin } } /// Account entry — mirrors `vc_account` (M5, reply to `listAccounts()`). public struct Account: Sendable, Equatable { public let username: String public let isAdmin: Bool public let createdAtUnixMs: UInt64 public let lastLoginUnixMs: UInt64 public init(username: String, isAdmin: Bool, createdAtUnixMs: UInt64, lastLoginUnixMs: UInt64) { self.username = username; self.isAdmin = isAdmin self.createdAtUnixMs = createdAtUnixMs; self.lastLoginUnixMs = lastLoginUnixMs } } /// Per-user stream summary — mirrors `vc_stream_summary`. For the full effective Opus /// config of a specific (user_id, stream_id), use `VoiceCatClient.getStreamAudioConfig`. public struct StreamSummary: Sendable, Equatable, Identifiable { public let id: UInt32 // stream_id public let kind: VoiceCatStreamKind public let label: String public init(streamId: UInt32, kind: VoiceCatStreamKind, label: String) { self.id = streamId; self.kind = kind; self.label = label } } /// Receive-side state the local listener chose for a specific remote stream — mirrors /// `vc_remote_stream_state`. All LOCAL (no protocol traffic) — docs/voice.md §10. /// Defaults (if `setRemoteStream` was never called): gain 1.0, unmuted, NR off. public struct RemoteStreamState: Sendable, Equatable { public let gain: Float // 0.0–… ; default 1.0 public let muted: Bool public let noiseReduction: Bool public init(gain: Float, muted: Bool, noiseReduction: Bool) { self.gain = gain; self.muted = muted; self.noiseReduction = noiseReduction } } /// Audio device — mirrors `vc_device`. `id` is an opaque, internally-encoded handle /// (currently hex-encoded `ma_device_id`) — always round-trip an id from `listDevices`; /// never construct one by hand (docs/architecture.md §4). public struct Device: Sendable, Equatable, Identifiable { public let id: String public let name: String public let isDefault: Bool public init(id: String, name: String, isDefault: Bool) { self.id = id; self.name = name; self.isDefault = isDefault } } /// iOS audio input port — derived from `AVAudioSession.availableInputs`. Unlike the /// miniaudio-based `Device` (which returns ~2 entries on iOS), this exposes the real /// AVAudioSession input ports (builtInMic, bluetoothHFP, headsetMic, usbAudio, airPlay) /// with their data sources (orientation: front/back/top/bottom) and polar patterns /// (omni/cardioid/subcardioid/bidirectional). Used by `IOSAudioRouter` + `SettingsView`. public struct IOSAudioInputPort: Identifiable, Hashable { public let id: String // port UID (stable across route changes) public let name: String // human-readable port name public let portType: String // AVAudioSession.Port raw value as string public let dataSources: [IOSAudioDataSource]? public let isSelected: Bool // true if this is the current preferredInput public init(id: String, name: String, portType: String, dataSources: [IOSAudioDataSource]?, isSelected: Bool) { self.id = id; self.name = name; self.portType = portType self.dataSources = dataSources; self.isSelected = isSelected } } /// iOS audio data source — a sub-selection of an input port (e.g. built-in mic /// orientation: front/back/top/bottom). May have polar pattern options. public struct IOSAudioDataSource: Identifiable, Hashable { public let id: String // dataSource UID public let name: String // "Front", "Back", "Top", "Bottom" public let polarPatterns: [String]? // AVAudioSession.PolarPattern raw values public let isSelected: Bool // true if this is the current preferredDataSource public let selectedPolarPattern: String? public init(id: String, name: String, polarPatterns: [String]?, isSelected: Bool, selectedPolarPattern: String?) { self.id = id; self.name = name; self.polarPatterns = polarPatterns self.isSelected = isSelected; self.selectedPolarPattern = selectedPolarPattern } } /// iOS audio output route — read-only display of `AVAudioSession.currentRoute.outputs`. public struct IOSAudioOutputRoute: Identifiable, Hashable { public let id: String // port UID public let name: String // human-readable route name public let portType: String // AVAudioSession.Port raw value as string public init(id: String, name: String, portType: String) { self.id = id; self.name = name; self.portType = portType } } /// Effective Opus configuration — mirrors `vc_audio_config`. public struct AudioConfig: Sendable, Equatable { public let codec: UInt32 // 0 = OPUS public let stereo: Bool // mode: 0 = mono, 1 = stereo public let sampleRate: UInt32 public let bitrateBps: UInt32 public let frameMs: UInt32 public let application: UInt32 // 0 = VOIP, 1 = AUDIO, 2 = LOWDELAY public let fec: Bool public let expectedPacketLoss: UInt32 // % 0..100 public let dtx: Bool public let complexity: UInt32 // 0..10 public let dred: Bool // Deep REDundancy (Opus 1.6), off by default public init(codec: UInt32 = 0, stereo: Bool = false, sampleRate: UInt32 = 48000, bitrateBps: UInt32 = 64000, frameMs: UInt32 = 20, application: UInt32 = 0, fec: Bool = true, expectedPacketLoss: UInt32 = 5, dtx: Bool = false, complexity: UInt32 = 10, dred: Bool = false) { self.codec = codec; self.stereo = stereo; self.sampleRate = sampleRate self.bitrateBps = bitrateBps; self.frameMs = frameMs; self.application = application self.fec = fec; self.expectedPacketLoss = expectedPacketLoss; self.dtx = dtx self.complexity = complexity; self.dred = dred } } /// Stream descriptor — mirrors `vc_stream_desc`. Used by `startStream(kind:deviceId:label:)`. public struct StreamDescriptor: Sendable, Equatable { public let kind: VoiceCatStreamKind /// nil = default device for this kind. public let deviceId: String? public let label: String public init(kind: VoiceCatStreamKind, deviceId: String? = nil, label: String) { self.kind = kind; self.deviceId = deviceId; self.label = label } } /// Server identity info — parsed from a `.serverIdentity` event + `getServerIdentityDisplay()`. /// The `tlsCertFingerprint` (SHA-256 hex of the TLS leaf cert) is the value the TOFU gate /// actually pins on; `ed25519Fingerprint` is display-only (docs/security.md §1.1). public struct ServerIdentity: Sendable, Equatable { public let tofuStatus: VoiceCatTofuStatus /// SHA-256 hex of the TLS leaf certificate — the pinned value. No separators (64 chars). public let tlsCertFingerprint: String /// Ed25519 identity fingerprint from ServerHello, hex-formatted — display only. /// Empty if not yet available. public let ed25519Fingerprint: String public init(tofuStatus: VoiceCatTofuStatus, tlsCertFingerprint: String, ed25519Fingerprint: String) { self.tofuStatus = tofuStatus; self.tlsCertFingerprint = tlsCertFingerprint self.ed25519Fingerprint = ed25519Fingerprint } }