Files
orca/mobile/modules/orca-gzip-utf8-codec/tests/GzipUtf8CodecChecks.swift
T
Jinwoo Hong 5a097e7739 feat(mobile): decode page-bundle download windows natively (#31817)
* feat(mobile): decode page-bundle download windows natively

Each 384 KiB window of the page-bundle download cost about 200-300 ms of
Hermes JS time (tweetnacl secretbox open, fflate gunzip, a base64 re-encode
check and the TextDecoder polyfill), which kept the JS thread busy for the
whole download.

The phone now opens v2 frames with react-native-libsodium (secretbox open and
strict base64) and decodes ranges with a local Expo module,
orca-bundle-window-codec (bounded gunzip via system zlib on iOS and
java.util.zip.Inflater on Android, plus strict UTF-8). The shared v2 session
takes these as optional primitives; the desktop keeps tweetnacl.

If any native piece is missing or fails a known-answer probe, the phone keeps
today's JS path, with the base64 re-encode replaced by an O(1) canonical check.

* refactor(mobile): name the native decode for every inbound frame, not just bundle windows

The native secretbox, base64 and UTF-8 primitives decode every inbound v2
frame on the phone, so the files and the Expo module are renamed for what
they cover (paired-e2ee-native-*, orca-gzip-utf8-codec). A throw from the
native UTF-8 decode now falls back to TextDecoder, since the frame is
already open and its counter advanced.

* test(mobile): pin the TextDecoder fallback when native UTF-8 throws

* fix(mobile): hand the native decode to the transport from the app entry

The transport modules are reached by shared, desktop and e2e code. Importing
the native bindings there pulled expo-modules-core into the e2e typecheck,
whose global types make NODE_ENV required and broke ~20 e2e files. The app
entry now provides the bindings; without them the transport runs the JS path.

* fix(mobile): a native-codec lookup before the app provides bindings does not pin the JS path
2026-10-22 20:55:22 -04:00

55 lines
1.9 KiB
Swift

import Foundation
// The codec's decisions, compiled and run without a device, from the module directory:
//
// swiftc -O -o /tmp/gzip-utf8-codec-checks ios/BoundedGunzip.swift \
// ios/StrictUtf8.swift tests/GzipUtf8CodecChecks.swift && /tmp/gzip-utf8-codec-checks
@main struct GzipUtf8CodecChecks {
static var failures = 0
static func check(_ condition: Bool, _ name: String) {
if !condition {
failures += 1
print("FAIL: \(name)")
}
}
static func bytes(hex: String) -> Data {
var data = Data(capacity: hex.count / 2)
var index = hex.startIndex
while index < hex.endIndex {
let next = hex.index(index, offsetBy: 2)
data.append(UInt8(hex[index..<next], radix: 16)!)
index = next
}
return data
}
static func main() throws {
// Shared with the Kotlin tests and the JS tests, which pin fflate's answer to the same inputs.
let url = URL(fileURLWithPath: "tests/gunzip-vectors.json")
let vectors = try JSONSerialization.jsonObject(with: Data(contentsOf: url)) as! [[String: Any]]
for vector in vectors {
let name = vector["name"] as! String
let expected = (vector["native"] as? String).map { bytes(hex: $0) }
let actual = try? gunzipBounded(
bytes(hex: vector["gzipHex"] as! String), outputLimit: vector["outputLimit"] as! Int)
check(actual == expected, "gunzip: \(name)")
}
let text = "{\"a\":\"x\u{e9}\u{2713}\u{1F642}\"}"
check(decodeStrictUtf8(Data(text.utf8)) == text, "utf8: every sequence length")
check(decodeStrictUtf8(Data()) == "", "utf8: empty")
let refused: [[UInt8]] = [
[0xef, 0xbb, 0xbf, 0x41], [0xc0, 0x80], [0xed, 0xa0, 0x80], [0x41, 0xe2, 0x82], [0x80],
[0xf5, 0x80, 0x80, 0x80],
]
for input in refused {
check(decodeStrictUtf8(Data(input)) == nil, "utf8: refuses \(input)")
}
print(failures == 0 ? "all checks passed" : "\(failures) check(s) failed")
exit(failures == 0 ? 0 : 1)
}
}