Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
42
examples/app-clip/Package.swift
Normal file
42
examples/app-clip/Package.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
// swift-tools-version: 5.9
|
||||
// Package.swift — SPM manifest for the RVF App Clip skeleton.
|
||||
//
|
||||
// This package links the pre-built RVF static library (librvf_runtime.a)
|
||||
// produced by:
|
||||
// cargo build --release --target aarch64-apple-ios --lib
|
||||
//
|
||||
// Place the compiled .a file under lib/ before building with Xcode.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "RVFAppClip",
|
||||
platforms: [
|
||||
.iOS(.v16),
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "AppClip",
|
||||
targets: ["AppClip"]
|
||||
),
|
||||
],
|
||||
targets: [
|
||||
// C bridge module that exposes the RVF FFI header to Swift.
|
||||
.target(
|
||||
name: "RVFBridge",
|
||||
path: "Sources/RVFBridge",
|
||||
publicHeadersPath: ".",
|
||||
linkerSettings: [
|
||||
// Link the pre-built Rust static library.
|
||||
.unsafeFlags(["-L../../target/aarch64-apple-ios/release"]),
|
||||
.linkedLibrary("rvf_runtime"),
|
||||
]
|
||||
),
|
||||
// Swift App Clip target that consumes the C bridge.
|
||||
.target(
|
||||
name: "AppClip",
|
||||
dependencies: ["RVFBridge"],
|
||||
path: "Sources/AppClip"
|
||||
),
|
||||
]
|
||||
)
|
||||
73
examples/app-clip/Sources/AppClip/AppClipApp.swift
Normal file
73
examples/app-clip/Sources/AppClip/AppClipApp.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
// AppClipApp.swift — Entry point for the RVF App Clip.
|
||||
//
|
||||
// This is a minimal SwiftUI App Clip that scans QR cognitive seeds
|
||||
// and decodes them using the RVF C FFI. Designed to stay under the
|
||||
// 15 MB App Clip size limit per Apple guidelines.
|
||||
//
|
||||
// App Clip invocation URL scheme:
|
||||
// https://rvf.example.com/seed?id=<file_id>
|
||||
//
|
||||
// The App Clip can be invoked by:
|
||||
// 1. Scanning an RVQS QR code directly (camera flow)
|
||||
// 2. Tapping an App Clip Code / NFC tag
|
||||
// 3. Opening a Smart App Banner link
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct AppClipApp: App {
|
||||
@StateObject private var appState = AppClipState()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
AppClipView()
|
||||
.onContinueUserActivity(
|
||||
NSUserActivityTypeBrowsingWeb,
|
||||
perform: handleUserActivity
|
||||
)
|
||||
.environmentObject(appState)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle App Clip invocation via URL.
|
||||
///
|
||||
/// When the App Clip is launched from a Smart App Banner or App Clip Code,
|
||||
/// iOS delivers the invocation URL as a user activity. We extract the
|
||||
/// seed identifier and trigger a download + decode flow.
|
||||
private func handleUserActivity(_ activity: NSUserActivity) {
|
||||
guard let url = activity.webpageURL else { return }
|
||||
appState.handleInvocationURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppClipState
|
||||
|
||||
/// Shared state for App Clip lifecycle and invocation handling.
|
||||
@MainActor
|
||||
final class AppClipState: ObservableObject {
|
||||
|
||||
/// The invocation URL that launched this App Clip (if any).
|
||||
@Published var invocationURL: URL?
|
||||
|
||||
/// Handle an App Clip invocation URL.
|
||||
///
|
||||
/// Extracts the seed ID from the URL query parameters and could
|
||||
/// trigger a network fetch for the seed payload.
|
||||
func handleInvocationURL(_ url: URL) {
|
||||
invocationURL = url
|
||||
|
||||
// Extract seed ID from query parameters.
|
||||
// Example: https://rvf.example.com/seed?id=0102030405060708
|
||||
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
let seedIDParam = components.queryItems?.first(where: { $0.name == "id" }),
|
||||
let _ = seedIDParam.value
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
// In production:
|
||||
// 1. Fetch the seed payload from the CDN using the seed ID.
|
||||
// 2. Pass the raw bytes to SeedDecoder.decode(data:).
|
||||
// 3. Begin progressive download of the full RVF file.
|
||||
}
|
||||
}
|
||||
338
examples/app-clip/Sources/AppClip/AppClipView.swift
Normal file
338
examples/app-clip/Sources/AppClip/AppClipView.swift
Normal file
@@ -0,0 +1,338 @@
|
||||
// AppClipView.swift — SwiftUI view for the RVF App Clip.
|
||||
//
|
||||
// Presents a QR scanner interface and displays decoded seed information.
|
||||
// Uses AVFoundation for camera access and the SeedDecoder for parsing.
|
||||
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
// MARK: - AppClipView
|
||||
|
||||
/// Root view for the App Clip experience.
|
||||
///
|
||||
/// Flow: Scan QR -> Decode RVQS seed -> Display cognitive seed info.
|
||||
struct AppClipView: View {
|
||||
@StateObject private var viewModel = AppClipViewModel()
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack(spacing: 0) {
|
||||
switch viewModel.state {
|
||||
case .scanning:
|
||||
scannerSection
|
||||
case .decoding:
|
||||
decodingSection
|
||||
case .decoded(let info):
|
||||
decodedSection(info)
|
||||
case .error(let message):
|
||||
errorSection(message)
|
||||
}
|
||||
}
|
||||
.navigationTitle("RVF Seed Scanner")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Scanner
|
||||
|
||||
private var scannerSection: some View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
// Camera preview placeholder.
|
||||
// In production, this would be a UIViewRepresentable wrapping
|
||||
// an AVCaptureVideoPreviewLayer for real-time QR scanning.
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.black.opacity(0.8))
|
||||
.frame(width: 280, height: 280)
|
||||
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.strokeBorder(Color.white.opacity(0.6), lineWidth: 2)
|
||||
.frame(width: 240, height: 240)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
Image(systemName: "qrcode.viewfinder")
|
||||
.font(.system(size: 48))
|
||||
.foregroundStyle(.white)
|
||||
Text("Point camera at QR seed")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.white.opacity(0.8))
|
||||
}
|
||||
}
|
||||
|
||||
Text("Scan a cognitive seed QR code to bootstrap intelligence.")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
// Demo button for testing without a camera.
|
||||
Button {
|
||||
viewModel.decodeDemoSeed()
|
||||
} label: {
|
||||
Label("Use Demo Seed", systemImage: "doc.viewfinder")
|
||||
.font(.body.weight(.medium))
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.blue)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decoding
|
||||
|
||||
private var decodingSection: some View {
|
||||
VStack(spacing: 16) {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
Text("Decoding seed...")
|
||||
.font(.headline)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Decoded Result
|
||||
|
||||
private func decodedSection(_ info: SeedInfo) -> some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
// Header card.
|
||||
GroupBox {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Cognitive Seed", systemImage: "brain")
|
||||
.font(.headline)
|
||||
|
||||
Divider()
|
||||
|
||||
infoRow("Version", value: "v\(info.version)")
|
||||
infoRow("Dimension", value: "\(info.dimension)")
|
||||
infoRow("Vectors", value: formatCount(info.totalVectorCount))
|
||||
infoRow("Seed Size", value: formatBytes(info.totalSeedSize))
|
||||
}
|
||||
}
|
||||
|
||||
// Content hash.
|
||||
GroupBox {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Content Hash", systemImage: "number")
|
||||
.font(.headline)
|
||||
|
||||
Divider()
|
||||
|
||||
Text(info.contentHash)
|
||||
.font(.system(.caption, design: .monospaced))
|
||||
.foregroundStyle(.secondary)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
// Manifest info.
|
||||
GroupBox {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Manifest", systemImage: "arrow.down.circle")
|
||||
.font(.headline)
|
||||
|
||||
Divider()
|
||||
|
||||
infoRow("Hosts", value: "\(info.hosts)")
|
||||
infoRow("Layers", value: "\(info.layers)")
|
||||
infoRow("Microkernel", value: info.hasMicrokernel ? "Yes" : "No")
|
||||
infoRow("Signed", value: info.isSigned ? "Yes" : "No")
|
||||
|
||||
if let url = info.primaryHostURL {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Primary Host")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(url)
|
||||
.font(.system(.caption2, design: .monospaced))
|
||||
.foregroundStyle(.blue)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons.
|
||||
Button {
|
||||
viewModel.reset()
|
||||
} label: {
|
||||
Label("Scan Another", systemImage: "qrcode.viewfinder")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error
|
||||
|
||||
private func errorSection(_ message: String) -> some View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 48))
|
||||
.foregroundStyle(.red)
|
||||
|
||||
Text("Decode Failed")
|
||||
.font(.title2.weight(.semibold))
|
||||
|
||||
Text(message)
|
||||
.font(.body)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
Button {
|
||||
viewModel.reset()
|
||||
} label: {
|
||||
Label("Try Again", systemImage: "arrow.counterclockwise")
|
||||
.font(.body.weight(.medium))
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func infoRow(_ label: String, value: String) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.font(.subheadline.weight(.medium))
|
||||
}
|
||||
}
|
||||
|
||||
private func formatCount(_ count: UInt32) -> String {
|
||||
if count >= 1_000_000 {
|
||||
return String(format: "%.1fM", Double(count) / 1_000_000.0)
|
||||
} else if count >= 1_000 {
|
||||
return String(format: "%.1fK", Double(count) / 1_000.0)
|
||||
}
|
||||
return "\(count)"
|
||||
}
|
||||
|
||||
private func formatBytes(_ bytes: UInt32) -> String {
|
||||
if bytes >= 1024 {
|
||||
return String(format: "%.1f KB", Double(bytes) / 1024.0)
|
||||
}
|
||||
return "\(bytes) B"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewModel
|
||||
|
||||
/// View model driving the App Clip scan-decode flow.
|
||||
@MainActor
|
||||
final class AppClipViewModel: ObservableObject {
|
||||
|
||||
enum State: Equatable {
|
||||
case scanning
|
||||
case decoding
|
||||
case decoded(SeedInfo)
|
||||
case error(String)
|
||||
}
|
||||
|
||||
@Published var state: State = .scanning
|
||||
|
||||
private let decoder = SeedDecoder()
|
||||
|
||||
/// Handle raw QR payload bytes from the camera scanner.
|
||||
func handleScannedData(_ data: Data) {
|
||||
state = .decoding
|
||||
Task {
|
||||
do {
|
||||
let info = try decoder.decode(data: data)
|
||||
state = .decoded(info)
|
||||
} catch {
|
||||
state = .error(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode a built-in demo seed for testing without a camera.
|
||||
func decodeDemoSeed() {
|
||||
// Construct a minimal valid RVQS header (64 bytes) for demonstration.
|
||||
// In production, this would come from a real QR scan.
|
||||
var payload = Data(count: 64)
|
||||
payload.withUnsafeMutableBytes { buf in
|
||||
let p = buf.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||
|
||||
// seed_magic = 0x52565153 ("RVQS") in little-endian.
|
||||
p[0] = 0x53; p[1] = 0x51; p[2] = 0x56; p[3] = 0x52
|
||||
// seed_version = 1.
|
||||
p[4] = 0x01; p[5] = 0x00
|
||||
// flags = 0 (minimal).
|
||||
p[6] = 0x00; p[7] = 0x00
|
||||
// file_id = 8 bytes.
|
||||
for i in 8..<16 { p[i] = UInt8(i) }
|
||||
// total_vector_count = 1000 (little-endian).
|
||||
p[0x10] = 0xE8; p[0x11] = 0x03; p[0x12] = 0x00; p[0x13] = 0x00
|
||||
// dimension = 128.
|
||||
p[0x14] = 0x80; p[0x15] = 0x00
|
||||
// base_dtype = 0, profile_id = 0.
|
||||
p[0x16] = 0x00; p[0x17] = 0x00
|
||||
// created_ns = 0 (8 bytes, already zero).
|
||||
// microkernel_offset = 64, microkernel_size = 0.
|
||||
p[0x20] = 0x40; p[0x21] = 0x00; p[0x22] = 0x00; p[0x23] = 0x00
|
||||
// download_manifest_offset = 64, download_manifest_size = 0.
|
||||
p[0x28] = 0x40; p[0x29] = 0x00; p[0x2A] = 0x00; p[0x2B] = 0x00
|
||||
// sig_algo = 0, sig_length = 0.
|
||||
// total_seed_size = 64.
|
||||
p[0x34] = 0x40; p[0x35] = 0x00; p[0x36] = 0x00; p[0x37] = 0x00
|
||||
// content_hash = 8 bytes of 0xAB.
|
||||
for i in 0x38..<0x40 { p[i] = 0xAB }
|
||||
}
|
||||
|
||||
handleScannedData(payload)
|
||||
}
|
||||
|
||||
/// Reset to scanning state.
|
||||
func reset() {
|
||||
state = .scanning
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - QR Scanner Coordinator (AVFoundation placeholder)
|
||||
|
||||
/// Coordinator for camera-based QR code scanning.
|
||||
///
|
||||
/// In a full implementation, this would wrap AVCaptureSession with a
|
||||
/// metadata output delegate to detect QR codes in real-time.
|
||||
/// Kept as a placeholder to show the integration pattern.
|
||||
#if canImport(AVFoundation)
|
||||
final class QRScannerCoordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate {
|
||||
|
||||
var onSeedScanned: ((Data) -> Void)?
|
||||
|
||||
func metadataOutput(
|
||||
_ output: AVCaptureMetadataOutput,
|
||||
didOutput metadataObjects: [AVMetadataObject],
|
||||
from connection: AVCaptureConnection
|
||||
) {
|
||||
guard let readable = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
|
||||
readable.type == .qr,
|
||||
let stringValue = readable.stringValue,
|
||||
let data = stringValue.data(using: .utf8)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
// In production, the QR code would contain raw binary data.
|
||||
// For App Clips invoked via URL, the seed bytes would be
|
||||
// fetched from the URL's associated payload.
|
||||
onSeedScanned?(data)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
166
examples/app-clip/Sources/AppClip/SeedDecoder.swift
Normal file
166
examples/app-clip/Sources/AppClip/SeedDecoder.swift
Normal file
@@ -0,0 +1,166 @@
|
||||
// SeedDecoder.swift — Swift wrapper for decoding RVQS QR Cognitive Seeds.
|
||||
//
|
||||
// Calls into the RVF C FFI (librvf_runtime.a) via the RVFBridge module
|
||||
// to parse raw seed bytes scanned from a QR code.
|
||||
|
||||
import Foundation
|
||||
import RVFBridge
|
||||
|
||||
// MARK: - SeedInfo
|
||||
|
||||
/// Decoded information from an RVQS QR Cognitive Seed.
|
||||
struct SeedInfo: Codable, Equatable, Sendable {
|
||||
/// Seed format version.
|
||||
let version: UInt16
|
||||
/// Number of download hosts in the manifest.
|
||||
let hosts: UInt32
|
||||
/// Number of progressive download layers.
|
||||
let layers: UInt32
|
||||
/// SHAKE-256-64 content hash as a hex string.
|
||||
let contentHash: String
|
||||
/// Total vector count the seed references.
|
||||
let totalVectorCount: UInt32
|
||||
/// Vector dimensionality.
|
||||
let dimension: UInt16
|
||||
/// Total seed payload size in bytes.
|
||||
let totalSeedSize: UInt32
|
||||
/// Whether the seed has an embedded WASM microkernel.
|
||||
let hasMicrokernel: Bool
|
||||
/// Whether the seed is cryptographically signed.
|
||||
let isSigned: Bool
|
||||
/// Primary download URL (if available).
|
||||
let primaryHostURL: String?
|
||||
}
|
||||
|
||||
// MARK: - SeedDecoderError
|
||||
|
||||
/// Errors that can occur during seed decoding.
|
||||
enum SeedDecoderError: LocalizedError {
|
||||
case emptyData
|
||||
case parseFailed(code: Int32)
|
||||
case urlExtractionFailed(code: Int32)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .emptyData:
|
||||
return "Seed data is empty."
|
||||
case .parseFailed(let code):
|
||||
return "Seed parse failed with error code \(code)."
|
||||
case .urlExtractionFailed(let code):
|
||||
return "Host URL extraction failed with error code \(code)."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SeedDecoder
|
||||
|
||||
/// Decodes RVQS QR Cognitive Seeds by calling the RVF C FFI.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```swift
|
||||
/// let decoder = SeedDecoder()
|
||||
/// let info = try decoder.decode(data: qrPayload)
|
||||
/// print(info.contentHash)
|
||||
/// ```
|
||||
final class SeedDecoder: Sendable {
|
||||
|
||||
init() {}
|
||||
|
||||
/// Decode raw QR seed bytes into a `SeedInfo`.
|
||||
///
|
||||
/// - Parameter data: The raw RVQS seed payload from a QR code.
|
||||
/// - Returns: Parsed seed information.
|
||||
/// - Throws: `SeedDecoderError` if parsing fails.
|
||||
func decode(data: Data) throws -> SeedInfo {
|
||||
guard !data.isEmpty else {
|
||||
throw SeedDecoderError.emptyData
|
||||
}
|
||||
|
||||
// Parse the 64-byte header via the C FFI.
|
||||
var header = RvqsHeaderC()
|
||||
let parseResult: Int32 = data.withUnsafeBytes { rawBuffer in
|
||||
guard let baseAddress = rawBuffer.baseAddress else {
|
||||
return RVQS_ERR_NULL_PTR
|
||||
}
|
||||
let ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
|
||||
return rvqs_parse_header(ptr, rawBuffer.count, &header)
|
||||
}
|
||||
|
||||
guard parseResult == RVQS_OK else {
|
||||
throw SeedDecoderError.parseFailed(code: parseResult)
|
||||
}
|
||||
|
||||
// Extract primary host URL (best-effort; nil if not available).
|
||||
let primaryURL = extractPrimaryHostURL(from: data)
|
||||
|
||||
// Derive host_count and layer_count from the seed result.
|
||||
// The C FFI provides the header; we infer counts from manifest presence.
|
||||
// For a full implementation, rvf_seed_parse would walk the TLV manifest.
|
||||
// In this skeleton, we report manifest presence via flags.
|
||||
let hasManifest = (header.flags & 0x0002) != 0
|
||||
let hostCount: UInt32 = primaryURL != nil ? 1 : 0
|
||||
let layerCount: UInt32 = hasManifest ? 1 : 0
|
||||
|
||||
// Build the hex string for content_hash.
|
||||
let hashBytes = withUnsafeBytes(of: header.content_hash) { Array($0) }
|
||||
let contentHash = hashBytes.map { String(format: "%02x", $0) }.joined()
|
||||
|
||||
// Check flags.
|
||||
let hasMicrokernel = (header.flags & 0x0001) != 0
|
||||
let isSigned = (header.flags & 0x0004) != 0
|
||||
|
||||
return SeedInfo(
|
||||
version: header.seed_version,
|
||||
hosts: hostCount,
|
||||
layers: layerCount,
|
||||
contentHash: contentHash,
|
||||
totalVectorCount: header.total_vector_count,
|
||||
dimension: header.dimension,
|
||||
totalSeedSize: header.total_seed_size,
|
||||
hasMicrokernel: hasMicrokernel,
|
||||
isSigned: isSigned,
|
||||
primaryHostURL: primaryURL
|
||||
)
|
||||
}
|
||||
|
||||
/// Verify the content hash of a seed payload.
|
||||
///
|
||||
/// - Parameter data: The raw RVQS seed payload.
|
||||
/// - Returns: `true` if the content hash is valid.
|
||||
func verifyContentHash(data: Data) -> Bool {
|
||||
let result: Int32 = data.withUnsafeBytes { rawBuffer in
|
||||
guard let baseAddress = rawBuffer.baseAddress else {
|
||||
return RVQS_ERR_NULL_PTR
|
||||
}
|
||||
let ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
|
||||
return rvqs_verify_content_hash(ptr, rawBuffer.count)
|
||||
}
|
||||
return result == RVQS_OK
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Extract the primary download host URL from the seed's TLV manifest.
|
||||
private func extractPrimaryHostURL(from data: Data) -> String? {
|
||||
var urlBuffer = [UInt8](repeating: 0, count: 256)
|
||||
var urlLength: Int = 0
|
||||
|
||||
let result: Int32 = data.withUnsafeBytes { rawBuffer in
|
||||
guard let baseAddress = rawBuffer.baseAddress else {
|
||||
return RVQS_ERR_NULL_PTR
|
||||
}
|
||||
let ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
|
||||
return rvqs_get_primary_host_url(
|
||||
ptr, rawBuffer.count,
|
||||
&urlBuffer, urlBuffer.count,
|
||||
&urlLength
|
||||
)
|
||||
}
|
||||
|
||||
guard result == RVQS_OK, urlLength > 0 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(bytes: urlBuffer[..<urlLength], encoding: .utf8)
|
||||
}
|
||||
}
|
||||
5
examples/app-clip/Sources/RVFBridge/module.modulemap
Normal file
5
examples/app-clip/Sources/RVFBridge/module.modulemap
Normal file
@@ -0,0 +1,5 @@
|
||||
module RVFBridge [system] {
|
||||
header "rvf_bridge.h"
|
||||
link "rvf_runtime"
|
||||
export *
|
||||
}
|
||||
172
examples/app-clip/Sources/RVFBridge/rvf_bridge.h
Normal file
172
examples/app-clip/Sources/RVFBridge/rvf_bridge.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* rvf_bridge.h — C header declaring the RVF FFI functions for the App Clip.
|
||||
*
|
||||
* These declarations mirror the extern "C" functions exported by
|
||||
* crates/rvf/rvf-runtime/src/ffi.rs. The App Clip calls these through
|
||||
* the pre-built librvf_runtime.a static library.
|
||||
*/
|
||||
|
||||
#ifndef RVF_BRIDGE_H
|
||||
#define RVF_BRIDGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- Result codes ---- */
|
||||
|
||||
#define RVQS_OK 0
|
||||
#define RVQS_ERR_NULL_PTR -1
|
||||
#define RVQS_ERR_TOO_SHORT -2
|
||||
#define RVQS_ERR_BAD_MAGIC -3
|
||||
#define RVQS_ERR_SIGNATURE_INVALID -4
|
||||
#define RVQS_ERR_HASH_MISMATCH -5
|
||||
#define RVQS_ERR_DECOMPRESS_FAIL -6
|
||||
#define RVQS_ERR_BUFFER_TOO_SMALL -7
|
||||
#define RVQS_ERR_PARSE_FAIL -8
|
||||
|
||||
/* ---- Structs ---- */
|
||||
|
||||
/**
|
||||
* Mirrors the RvqsHeaderC struct from ffi.rs.
|
||||
* 64-byte fixed-size header of an RVQS QR Cognitive Seed.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t seed_magic;
|
||||
uint16_t seed_version;
|
||||
uint16_t flags;
|
||||
uint8_t file_id[8];
|
||||
uint32_t total_vector_count;
|
||||
uint16_t dimension;
|
||||
uint8_t base_dtype;
|
||||
uint8_t profile_id;
|
||||
uint64_t created_ns;
|
||||
uint32_t microkernel_offset;
|
||||
uint32_t microkernel_size;
|
||||
uint32_t download_manifest_offset;
|
||||
uint32_t download_manifest_size;
|
||||
uint16_t sig_algo;
|
||||
uint16_t sig_length;
|
||||
uint32_t total_seed_size;
|
||||
uint8_t content_hash[8];
|
||||
} RvqsHeaderC;
|
||||
|
||||
/**
|
||||
* High-level seed parse result returned to Swift.
|
||||
* Populated by rvf_seed_parse and freed by rvf_seed_free.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Seed format version. */
|
||||
uint16_t version;
|
||||
/** Number of download hosts in the manifest. */
|
||||
uint32_t host_count;
|
||||
/** Number of progressive layers in the manifest. */
|
||||
uint32_t layer_count;
|
||||
/** SHAKE-256-64 content hash (8 bytes). */
|
||||
uint8_t content_hash[8];
|
||||
/** Total vector count from the header. */
|
||||
uint32_t total_vector_count;
|
||||
/** Vector dimensionality. */
|
||||
uint16_t dimension;
|
||||
/** Total seed payload size. */
|
||||
uint32_t total_seed_size;
|
||||
/** Seed flags bitfield. */
|
||||
uint16_t flags;
|
||||
} RvfSeedResult;
|
||||
|
||||
/* ---- FFI Functions (from librvf_runtime.a) ---- */
|
||||
|
||||
/**
|
||||
* Parse a raw RVQS seed payload and extract header information.
|
||||
*
|
||||
* @param data Pointer to the raw QR seed bytes.
|
||||
* @param len Length of the data buffer.
|
||||
* @param out Pointer to an RvqsHeaderC struct to receive the parsed header.
|
||||
* @return RVQS_OK on success, or a negative error code.
|
||||
*/
|
||||
int32_t rvqs_parse_header(const uint8_t *data, size_t len, RvqsHeaderC *out);
|
||||
|
||||
/**
|
||||
* Verify the HMAC-SHA256 signature of a QR seed.
|
||||
*
|
||||
* @param data Pointer to the full seed payload.
|
||||
* @param data_len Length of the seed payload.
|
||||
* @param key Pointer to the signing key.
|
||||
* @param key_len Length of the signing key.
|
||||
* @return RVQS_OK if signature is valid, or a negative error code.
|
||||
*/
|
||||
int32_t rvqs_verify_signature(const uint8_t *data, size_t data_len,
|
||||
const uint8_t *key, size_t key_len);
|
||||
|
||||
/**
|
||||
* Verify the content hash of a QR seed payload.
|
||||
*
|
||||
* @param data Pointer to the full seed payload.
|
||||
* @param data_len Length of the seed payload.
|
||||
* @return RVQS_OK if hash matches, or a negative error code.
|
||||
*/
|
||||
int32_t rvqs_verify_content_hash(const uint8_t *data, size_t data_len);
|
||||
|
||||
/**
|
||||
* Decompress the WASM microkernel from a QR seed.
|
||||
*
|
||||
* @param data Pointer to the full seed payload.
|
||||
* @param data_len Length of the seed payload.
|
||||
* @param out Buffer to receive decompressed microkernel.
|
||||
* @param out_cap Capacity of the output buffer.
|
||||
* @param out_len Receives the actual decompressed size.
|
||||
* @return RVQS_OK on success, or a negative error code.
|
||||
*/
|
||||
int32_t rvqs_decompress_microkernel(const uint8_t *data, size_t data_len,
|
||||
uint8_t *out, size_t out_cap,
|
||||
size_t *out_len);
|
||||
|
||||
/**
|
||||
* Extract the primary host URL from the download manifest.
|
||||
*
|
||||
* @param data Pointer to the full seed payload.
|
||||
* @param data_len Length of the seed payload.
|
||||
* @param url_buf Buffer to receive the URL string (not null-terminated).
|
||||
* @param url_cap Capacity of the URL buffer.
|
||||
* @param url_len Receives the actual URL length.
|
||||
* @return RVQS_OK on success, or a negative error code.
|
||||
*/
|
||||
int32_t rvqs_get_primary_host_url(const uint8_t *data, size_t data_len,
|
||||
uint8_t *url_buf, size_t url_cap,
|
||||
size_t *url_len);
|
||||
|
||||
/* ---- Convenience wrappers (implemented in Swift, declared here for reference) ---- */
|
||||
|
||||
/**
|
||||
* Parse a QR seed payload into a high-level RvfSeedResult.
|
||||
*
|
||||
* This is a convenience wrapper that calls rvqs_parse_header internally
|
||||
* and populates the simplified result struct. Implemented on the Swift side
|
||||
* using the lower-level FFI functions above.
|
||||
*
|
||||
* @param data Pointer to the raw QR seed bytes.
|
||||
* @param len Length of the data buffer.
|
||||
* @param out Pointer to an RvfSeedResult struct to populate.
|
||||
* @return RVQS_OK on success, or a negative error code.
|
||||
*/
|
||||
int32_t rvf_seed_parse(const uint8_t *data, size_t len, RvfSeedResult *out);
|
||||
|
||||
/**
|
||||
* Free any resources associated with an RvfSeedResult.
|
||||
*
|
||||
* Currently a no-op since RvfSeedResult is a plain value type,
|
||||
* but provided for forward-compatibility if the struct gains
|
||||
* heap-allocated fields.
|
||||
*
|
||||
* @param result Pointer to the result to free.
|
||||
*/
|
||||
void rvf_seed_free(RvfSeedResult *result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RVF_BRIDGE_H */
|
||||
Reference in New Issue
Block a user