demo app + SDK updates:\n- Add direct ANI transfer (prepare -> transfer)\n- Add firmware update flow (fetch latest w/ x-api-key, OTA transfer)\n- Extract x-api-key to NetworkConstants\n- Rename example app to demo; move folder and entry to DemoApp\n- Switch from SPM package dependency to local framework target\n- Enable automatic Info.plist generation for framework target\n- Add placeholder test to satisfy SwiftPM test target\n- Regenerate XcodeGen project (demo.xcodeproj)

This commit is contained in:
km2023
2026-04-09 18:41:56 +08:00
parent c33e7d3fa1
commit 7d9ff3081d
30 changed files with 2594 additions and 516 deletions

View File

@@ -0,0 +1,165 @@
import Foundation
public enum ProtocolManager {
// MARK: - Checksum
/// 8
/// checksum = (~sum + 1) & 0xFF
public static func calculateChecksum(_ data: Data) -> UInt8 {
var sum: UInt32 = 0
for byte in data {
sum += UInt32(byte)
}
return UInt8((~sum &+ 1) & 0xFF)
}
///
/// - Parameters:
/// - data:
/// - expected:
/// - Returns:
public static func verifyChecksum(_ data: Data, expected: UInt8) -> Bool {
calculateChecksum(data) == expected
}
// MARK: - Frame Creation
///
/// - Throws: payload UInt16 > 65535 invalidFrame
public static func createFrames(
type: UInt8,
data: Data,
head: FrameHead = .appToDevice,
maxDataSize: Int = FrameConstants.maxDataSize
) -> [Data] {
// subpageTotal/curPage are encoded as UInt16, so the absolute ceiling is 65535 pages.
// Returning [] keeps the call non-throwing for backwards compatibility; callers (the
// protocol service / file transfer) detect the empty array as a payload-too-large signal.
let maxChunk = max(1, maxDataSize)
let totalPages = (data.count + maxChunk - 1) / maxChunk
if totalPages > Int(UInt16.max) {
return []
}
if data.count <= maxDataSize {
return [createSingleFrame(type: type, data: data, head: head)]
} else {
return createFragmentedFrames(type: type, data: data, head: head, maxDataSize: maxChunk)
}
}
private static func createSingleFrame(type: UInt8, data: Data, head: FrameHead) -> Data {
let totalSize = FrameConstants.headerSize + data.count + FrameConstants.footerSize
var buffer = Data(capacity: totalSize)
// Header: head(1) + type(1) + subpageTotal(2) + curPage(2) + dataLen(2)
buffer.append(head.rawValue)
buffer.append(type)
buffer.append(0) // subpageTotal high
buffer.append(0) // subpageTotal low
buffer.append(0) // curPage high
buffer.append(0) // curPage low
buffer.append(UInt8((data.count >> 8) & 0xFF)) // dataLen high
buffer.append(UInt8(data.count & 0xFF)) // dataLen low
// Data
buffer.append(data)
// Checksum (calculated on everything before checksum)
let checksum = calculateChecksum(buffer)
buffer.append(checksum)
return buffer
}
private static func createFragmentedFrames(
type: UInt8,
data: Data,
head: FrameHead,
maxDataSize: Int
) -> [Data] {
let totalPages = (data.count + maxDataSize - 1) / maxDataSize
var frames: [Data] = []
frames.reserveCapacity(totalPages)
for i in 0..<totalPages {
let start = i * maxDataSize
let end = min(start + maxDataSize, data.count)
let chunk = data[start..<end]
var buffer = Data(capacity: FrameConstants.headerSize + chunk.count + FrameConstants.footerSize)
buffer.append(head.rawValue)
buffer.append(type)
// subpageTotal (big-endian UInt16)
buffer.append(UInt8((totalPages >> 8) & 0xFF))
buffer.append(UInt8(totalPages & 0xFF))
// curPage: (total-1) 0
let curPageVal = totalPages - 1 - i
buffer.append(UInt8((curPageVal >> 8) & 0xFF))
buffer.append(UInt8(curPageVal & 0xFF))
// dataLen
buffer.append(UInt8((chunk.count >> 8) & 0xFF))
buffer.append(UInt8(chunk.count & 0xFF))
// data
buffer.append(contentsOf: chunk)
// checksum
let checksum = calculateChecksum(buffer)
buffer.append(checksum)
frames.append(buffer)
}
return frames
}
// MARK: - Frame Parsing
///
/// - Parameter data:
/// - Returns: `ProtocolFrame` `nil`
public static func parseFrame(_ data: Data) -> ProtocolFrame? {
let minSize = FrameConstants.headerSize + FrameConstants.footerSize
guard data.count >= minSize else { return nil }
let head = data[data.startIndex]
guard head == FrameHead.deviceToApp.rawValue || head == FrameHead.appToDevice.rawValue else {
return nil
}
let type = data[data.startIndex + 1]
let subpageTotal = UInt16(data[data.startIndex + 2]) << 8 | UInt16(data[data.startIndex + 3])
let curPage = UInt16(data[data.startIndex + 4]) << 8 | UInt16(data[data.startIndex + 5])
let dataLen = UInt16(data[data.startIndex + 6]) << 8 | UInt16(data[data.startIndex + 7])
let expectedSize = FrameConstants.headerSize + Int(dataLen) + FrameConstants.footerSize
guard data.count >= expectedSize else { return nil }
let dataStart = data.startIndex + FrameConstants.headerSize
let dataEnd = dataStart + Int(dataLen)
let frameData = data[dataStart..<dataEnd]
let checksum = data[dataEnd]
// Verify checksum on everything before checksum byte
let dataToCheck = data[data.startIndex..<dataEnd]
guard verifyChecksum(dataToCheck, expected: checksum) else {
return nil
}
return ProtocolFrame(
head: head,
type: type,
subpageTotal: subpageTotal,
curPage: curPage,
dataLen: dataLen,
data: Data(frameData),
checksum: checksum
)
}
}