iOS File and Image Transfer SDK 1.0.0 Integration Guide
SDK Version: 1.0.0
XTalkFileTransferSdk is an independently selectable SwiftPM package/product for directed file-byte transfer, progress, cancellation, inbound ownership, radio restoration, and bounded JPEG/HEIC transcoding. XTalkImageTranscoder remains inside this package; it is not a sixth package. It requires iOS 17 and Swift 5.9 or newer.
The SDK does not scan or connect BLE, authenticate a device, store contacts or keys, persist files, request Photos permission, render UI, or update a message database. The host must complete BLE connection and device authentication first, then inject a transport, an exclusive radio lease, the local short ID, and a 16-byte control key lookup by source short ID.
1. SwiftPM integration
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "CustomerFileFeature",
platforms: [.iOS(.v17)],
dependencies: [
.package(
name: "XTalkFileTransferSdk",
path: "../../Packages/XTalkFileTransferSdk"
),
],
targets: [
.target(
name: "CustomerFileFeature",
dependencies: [
.product(name: "XTalkFileTransferSdk", package: "XTalkFileTransferSdk"),
]
),
]
)
Then import only:
import XTalkFileTransferSdk
2. Transport and radio lease contracts
Implement FileTransferTransport. runAtCommand must throw on module ERROR, timeout, link failure, or cancellation. sendRfPayload must not return until a successful SEND_FINISH; a negative terminal, timeout, link failure, and cancellation must throw. Both operations must cooperate with Swift task cancellation.
FileTransferRadioLeaseAcquisition must acquire exclusive ownership of the radio and remain cancellation-cooperative while waiting. The SDK ends the lease with .restored or .quarantined. A quarantined radio must not be handed immediately to Mesh, PTT, or realtime voice.
let manager = XTalkFileTransferManager(
transport: appTransport,
acquireRadioLease: {
try await radioArbiter.acquireFileTransferLease()
}
)
3. Events and lifecycle
Set manager.onEvent to a @MainActor @Sendable callback and handle:
.sendProgress(progress).receiveStarted(sourceShortID:totalBytes:).receiveProgress(progress).receiveFinished(sourceShortID:data:result:)
Events are serialized on MainActor. An accepted receive lifecycle is always started → zero or more progress → exactly one finished; receiveFinished is the only receive terminal. Persist bytes only when the outcome is .success.
4. Sending bytes
let request = try XTalkFileTransferSendRequest(
data: fileData,
sourceShortID: localShortID,
targetShortID: peerShortID,
controlKey: peerControlKey
)
let result = await manager.send(request)
Data must contain 1...524287 bytes and the control key must contain exactly 16 bytes. Source and target are UInt8. One manager permits only one send or receive session at a time; a concurrent operation returns .failure(.busy). Invalid request construction throws; transfer terminal state and restoration state are returned in XTalkFileTransferResult.
5. Routing inbound RF exactly once
let ownership = manager.handleInbound(
payload,
metadata: .init(snr: snr, rssiDbm: rssi),
localShortID: localShortID,
controlKeyForSource: { keyStore.controlKey(for: $0) }
)
.notOwned: another protocol owner may inspect the payload..consumed: file transfer owns it; do not forward it elsewhere..rejected(error): the shape belongs to file transfer but was rejected; do not double-consume it.
Never install a fixed/default key fallback. Wrong target, wrong key, CRC failure, and malformed input fail closed. ACK generation is owned by the module/PHY layer; the application must not emit an extra 10-byte ACK.
6. Transcoding images
let options = XTalkImageTranscodeOptions(
maximumBytes: 300_000,
maximumPixelDimension: 2_048,
minimumPixelDimension: 320,
minimumQuality: 0.45,
preferredFormat: .jpeg
)
let image = try XTalkImageTranscoder.transcode(sourceData, options: options)
let request = try XTalkFileTransferSendRequest(
data: image.data,
sourceShortID: localShortID,
targetShortID: peerShortID,
controlKey: peerControlKey
)
let result = await manager.send(request)
The transcoder applies EXIF orientation, flattens alpha onto white, strips source metadata, and searches quality and dimensions without exceeding the hard byte budget. maximumBytes must be within 1...524287; dimensions must be positive with minimum no larger than maximum; minimum quality must be finite and within 0...1.
JPEG and HEIC are strict requested formats, not a fallback list. If HEIC encoding is unavailable, the SDK throws .imageEncodingUnavailable(.heic) instead of silently returning JPEG. It throws .imageExceedsBudget when the budget cannot be met. Transcoding never sends implicitly; Photos access, names, storage, and UI remain host responsibilities.
7. Cancellation and restoration
cancelSend() -> Boolrequests local cancellation of an active send.cancelReceive() async -> Boolwaits for receive cleanup and lease termination.- Local and remote cancellation have distinct outcomes.
- Restoration is
.notRequired,.restored, or.failed(error).
Transfer temporarily changes WORKMODE, ADDTL, RATE, and ANYRATELEN. FREQ is queried for the snapshot and its captured value is rewritten during restoration; transfer setup does not set FREQ. Captured values are restored on tested exit paths. The module has no reliable AT+ANYRATELEN? query contract, so an unknown previous value cannot be restored. Keep transfer under an exclusive lease and reconnect, re-authenticate, and reinitialize after quarantine.
8. Errors
Stable errors distinguish invalid input, connection/auth readiness, busy, malformed protocol, CRC/crypto failure, timeout/retry exhaustion, transport failure, invalid image options, decode failure, unavailable encoder, byte-budget failure, and internal failure. Do not blindly retry .busy, malformed input, or invalid arguments. For a transport failure, preserve the diagnostic string and normally rebuild the BLE/auth session before retrying.
9. Memory, concurrency, and security
The SDK assembles a complete file in Data, capped at roughly 512 KiB. Avoid synchronous file I/O and extra copies on MainActor. Image transcoding is synchronous CPU work and should normally run off MainActor; event delivery returns to MainActor. Transport and key-provider implementations are Sendable and must be thread-safe.
The SDK 1.0.0 compatibility transport does not provide end-to-end confidentiality or integrity guarantees. Treat it as transport compatibility, not an end-to-end secure channel; AEAD-encrypt sensitive content before passing it to the SDK. A future wire upgrade requires explicit version negotiation and a stated legacy policy.