Refactor QR scan and share and add QRS support
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import Compression
|
||||
import Foundation
|
||||
import zlib
|
||||
|
||||
final class LubyTransformEncoder {
|
||||
let k: Int
|
||||
@@ -8,7 +8,7 @@ final class LubyTransformEncoder {
|
||||
let bytes: Int
|
||||
private let sourceBlocks: [Data]
|
||||
|
||||
init(data: Data, sliceSize: Int = 500, compress: Bool = true) {
|
||||
init(data: Data, sliceSize: Int = 512, compress: Bool = true) {
|
||||
self.sliceSize = sliceSize
|
||||
|
||||
let compressed: Data
|
||||
@@ -25,26 +25,39 @@ final class LubyTransformEncoder {
|
||||
}
|
||||
|
||||
private static func deflateCompress(_ data: Data) -> Data? {
|
||||
let sourceSize = data.count
|
||||
let destinationSize = sourceSize + 1024
|
||||
var stream = z_stream()
|
||||
|
||||
let destinationBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: destinationSize)
|
||||
defer { destinationBuffer.deallocate() }
|
||||
// Use 15 for zlib format (with header/trailer) to match pako's default
|
||||
guard deflateInit2_(
|
||||
&stream,
|
||||
Z_DEFAULT_COMPRESSION,
|
||||
Z_DEFLATED,
|
||||
15,
|
||||
8,
|
||||
Z_DEFAULT_STRATEGY,
|
||||
ZLIB_VERSION,
|
||||
Int32(MemoryLayout<z_stream>.size)
|
||||
) == Z_OK else {
|
||||
return nil
|
||||
}
|
||||
defer { deflateEnd(&stream) }
|
||||
|
||||
let compressedSize = data.withUnsafeBytes { sourcePtr -> Int in
|
||||
guard let baseAddress = sourcePtr.baseAddress else { return 0 }
|
||||
return compression_encode_buffer(
|
||||
destinationBuffer,
|
||||
destinationSize,
|
||||
baseAddress.assumingMemoryBound(to: UInt8.self),
|
||||
sourceSize,
|
||||
nil,
|
||||
COMPRESSION_ZLIB
|
||||
)
|
||||
let destSize = Int(deflateBound(&stream, UInt(data.count)))
|
||||
var dest = Data(count: destSize)
|
||||
|
||||
let result = data.withUnsafeBytes { srcPtr -> Int32 in
|
||||
dest.withUnsafeMutableBytes { destPtr -> Int32 in
|
||||
stream.next_in = UnsafeMutablePointer(mutating: srcPtr.bindMemory(to: Bytef.self).baseAddress)
|
||||
stream.avail_in = uInt(data.count)
|
||||
stream.next_out = destPtr.bindMemory(to: Bytef.self).baseAddress
|
||||
stream.avail_out = uInt(destSize)
|
||||
return deflate(&stream, Z_FINISH)
|
||||
}
|
||||
}
|
||||
|
||||
guard compressedSize > 0 else { return nil }
|
||||
return Data(bytes: destinationBuffer, count: compressedSize)
|
||||
guard result == Z_STREAM_END else { return nil }
|
||||
dest.count = Int(stream.total_out)
|
||||
return dest
|
||||
}
|
||||
|
||||
private static func sliceData(_ data: Data, sliceSize: Int) -> [Data] {
|
||||
@@ -94,10 +107,8 @@ final class LubyTransformEncoder {
|
||||
}
|
||||
|
||||
let random = Double.random(in: 0 ... 1)
|
||||
for i in 0 ..< k {
|
||||
if random < cumulative[i] {
|
||||
return i + 1
|
||||
}
|
||||
if let i = cumulative.firstIndex(where: { random < $0 }) {
|
||||
return i + 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
@@ -117,4 +128,78 @@ final class LubyTransformEncoder {
|
||||
return self.createBlock(indices: indices)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
static func runSelfTest() -> Bool {
|
||||
// Test 1: Round-trip tests with various sizes
|
||||
let testCases: [(size: Int, sliceSize: Int)] = [
|
||||
(1, 100),
|
||||
(100, 100),
|
||||
(1000, 100),
|
||||
(1031, 100),
|
||||
]
|
||||
|
||||
for (size, sliceSize) in testCases {
|
||||
let data = Data((0 ..< size).map { UInt8($0 % 256) })
|
||||
let encoder = LubyTransformEncoder(data: data, sliceSize: sliceSize, compress: true)
|
||||
let decoder = LubyTransformDecoder()
|
||||
|
||||
var blockCount = 0
|
||||
for block in encoder.fountain() {
|
||||
blockCount += 1
|
||||
if blockCount > encoder.k * 3 {
|
||||
print("LubyTransform self-test FAILED: too many blocks for size=\(size)")
|
||||
return false
|
||||
}
|
||||
do {
|
||||
if try decoder.addBlock(block) { break }
|
||||
} catch {
|
||||
print("LubyTransform self-test FAILED: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
let decoded = try decoder.getDecoded()
|
||||
if decoded != data {
|
||||
print("LubyTransform self-test FAILED: data mismatch for size=\(size)")
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
print("LubyTransform self-test FAILED: \(error)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: TypeScript compatibility (decode blocks generated by TypeScript)
|
||||
// Test vector: 5 bytes [0xAB, 0xCD, 0xEF, 0x12, 0x34], uncompressed, sliceSize=10
|
||||
let tsBlockBase64 = "AQAAAAAAAAABAAAABQAAALt8DL6rze8SNAAAAAAA"
|
||||
let expectedData = Data([0xAB, 0xCD, 0xEF, 0x12, 0x34])
|
||||
|
||||
guard let blockData = Data(base64Encoded: tsBlockBase64),
|
||||
let block = EncodedBlock.fromBinary(blockData)
|
||||
else {
|
||||
print("LubyTransform self-test FAILED: cannot parse TypeScript block")
|
||||
return false
|
||||
}
|
||||
|
||||
let tsDecoder = LubyTransformDecoder()
|
||||
do {
|
||||
_ = try tsDecoder.addBlock(block)
|
||||
let decoded = try tsDecoder.getDecoded()
|
||||
if decoded != expectedData {
|
||||
print("LubyTransform self-test FAILED: TypeScript compatibility mismatch")
|
||||
print("Expected: \(expectedData.map { String(format: "%02X", $0) }.joined())")
|
||||
print("Got: \(decoded.map { String(format: "%02X", $0) }.joined())")
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
print("LubyTransform self-test FAILED: TypeScript decode error: \(error)")
|
||||
return false
|
||||
}
|
||||
|
||||
print("LubyTransform self-test PASSED (including TypeScript compatibility)")
|
||||
return true
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user