From 8891435cbad6d1047479c187f279f695709bc874 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 14 Oct 2026 19:49:59 +0200 Subject: [PATCH 01/29] feat(stream): a capture tool that streams one simulator's screen over a pipe Co-Authored-By: Apex --- .gitignore | 1 + tools/screen-capture/.gitignore | 1 + tools/screen-capture/Package.swift | 29 ++ .../DeviceDisplaySizes.swift | 48 ++ .../DeviceHubKeyboardRouting.swift | 147 ++++++ .../simlock-screen-capture/DevicePose.swift | 49 ++ .../simlock-screen-capture/FrameCapture.swift | 455 ++++++++++++++++++ .../FramebufferSurfaceSelector.swift | 77 +++ .../simlock-screen-capture/H264Encoder.swift | 229 +++++++++ .../PixelBufferUtils.swift | 62 +++ .../SimFrameworks.swift | 25 + .../simlock-screen-capture/StreamFormat.swift | 46 ++ .../simlock-screen-capture/VideoEncoder.swift | 60 +++ .../simlock-screen-capture/Xcode.swift | 25 + .../Sources/simlock-screen-capture/main.swift | 385 +++++++++++++++ tools/screen-capture/scripts/unframe.mjs | 99 ++++ 16 files changed, 1738 insertions(+) create mode 100644 tools/screen-capture/.gitignore create mode 100644 tools/screen-capture/Package.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/SimFrameworks.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/StreamFormat.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift create mode 100644 tools/screen-capture/Sources/simlock-screen-capture/main.swift create mode 100644 tools/screen-capture/scripts/unframe.mjs diff --git a/.gitignore b/.gitignore index 917ef5a2..a02e21dd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test-results/ playwright-report/ .agent-device .slow-lane.env +tools/screen-capture/.build/ diff --git a/tools/screen-capture/.gitignore b/tools/screen-capture/.gitignore new file mode 100644 index 00000000..30bcfa4e --- /dev/null +++ b/tools/screen-capture/.gitignore @@ -0,0 +1 @@ +.build/ diff --git a/tools/screen-capture/Package.swift b/tools/screen-capture/Package.swift new file mode 100644 index 00000000..5ca3d183 --- /dev/null +++ b/tools/screen-capture/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:6.0 +// +// simlock-screen-capture — a standalone Swift CLI that streams the framebuffer of a booted +// iOS Simulator to stdout. The capture and encode pipeline is serve-sim's (Apache-2.0), copied +// from Sources/SimNative and Sources/SimNativeSupport with two changes: +// +// 1. `FrameCapture.findSimDevice` also searches the device set named by +// SIMLOCK_IOS_DEVICE_SET. serve-sim searches only CoreSimulator's default set, and simlock +// boots every simulator in its own `simctl --set` root (ADR 0001), so without this patch it +// cannot see a simlock device at all. +// 2. There is no node-swift binding. serve-sim's binding is `sim-module.swift`, the only file in +// that package that needs NodeAPI; a process that writes frames to stdout instead does not +// need it, and the daemon can then supervise the capture the same way it supervises every +// other tool it runs. +// +// One target, so the copied types keep their internal visibility and need no `public` edits. +import PackageDescription + +let package = Package( + name: "simlock-screen-capture", + platforms: [.macOS(.v14)], + targets: [ + .executableTarget( + name: "simlock-screen-capture", + path: "Sources/simlock-screen-capture", + swiftSettings: [.swiftLanguageMode(.v5)] + ), + ] +) diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift b/tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift new file mode 100644 index 00000000..a6cf483f --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift @@ -0,0 +1,48 @@ +import Foundation + +/// Reads native framebuffer sizes from a CoreSimulator device-type +/// `capabilities.plist`. Foldables list more than one integrated digitizer +/// display (iPhone Duo: 1398×2034 cover + 2007×2853 inner); presentation +/// surfaces such as the 7680×4320 resizable scene have no chrome identifier +/// and are ignored. +public enum DeviceDisplaySizes { + public static func nativeSizes(fromCapabilitiesPlist data: Data) -> [FramebufferSurfaceSize] { + guard let plist = try? PropertyListSerialization.propertyList( + from: data, + options: [], + format: nil + ) else { + return [] + } + let root = plist as? [String: Any] ?? [:] + let capabilities = (root["capabilities"] as? [String: Any]) ?? root + let displays = capabilities["displays"] as? [[String: Any]] ?? [] + + var sizes: [FramebufferSurfaceSize] = [] + for display in displays { + guard (display["displayType"] as? String) == "integrated" else { continue } + guard boolValue(display["hasDigitizer"]) else { continue } + let chrome = display["chromeIdentifier"] as? String ?? "" + guard !chrome.isEmpty else { continue } + let width = intValue(display["width"]) + let height = intValue(display["height"]) + guard width > 0, height > 0 else { continue } + sizes.append(FramebufferSurfaceSize(width: width, height: height)) + } + return sizes + } + + private static func boolValue(_ value: Any?) -> Bool { + if let flag = value as? Bool { return flag } + if let number = value as? NSNumber { return number.boolValue } + return false + } + + private static func intValue(_ value: Any?) -> Int { + if let number = value as? Int { return number } + if let number = value as? NSNumber { return number.intValue } + if let number = value as? Double { return Int(number.rounded()) } + if let number = value as? Float { return Int(number.rounded()) } + return 0 + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift b/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift new file mode 100644 index 00000000..ccee8fe1 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift @@ -0,0 +1,147 @@ +import Foundation + +/// Environment policy for the optional Xcode 27 Device Hub keyboard route. +public enum DeviceHubKeyboardConfiguration { + /// Only the documented value `1` opts out. Values such as `0` and `false` + /// leave the route enabled. + public static func isDisabled(environmentValue: String?) -> Bool { + environmentValue == "1" + } +} + +public struct DeviceHubWindow: Equatable, Sendable { + public let processIdentifier: Int32 + public let windowNumber: UInt32 + public let name: String + public let layer: Int + public let isOnScreen: Bool + + public init( + processIdentifier: Int32, + windowNumber: UInt32, + name: String, + layer: Int = 0, + isOnScreen: Bool = true + ) { + self.processIdentifier = processIdentifier + self.windowNumber = windowNumber + self.name = name + self.layer = layer + self.isOnScreen = isOnScreen + } +} + +public enum DeviceHubWindowRoutingFailure: Error, Equatable, Sendable, CustomStringConvertible { + case noEligibleWindows + case targetNotFound + case ambiguousTarget(count: Int) + case targetNotFocused(focusedName: String) + + public var description: String { + switch self { + case .noEligibleWindows: + return "Device Hub has no visible simulator window" + case .targetNotFound: + return "the target simulator window is not visible" + case .ambiguousTarget(let count): + return "the target simulator name matches \(count) windows" + case .targetNotFocused(let focusedName): + return "another Device Hub window has keyboard focus (\(focusedName))" + } + } +} + +/// Chooses a Device Hub simulator window from a key-window-first snapshot. A +/// simulator name is not globally unique, so duplicate matches are deliberately +/// rejected instead of risking keyboard input in the wrong guest. +public enum DeviceHubWindowRouter { + public static func route( + windows: [DeviceHubWindow], + processIdentifier: Int32, + targetWindowTitle: String + ) -> Result { + let eligible = windows.filter { + $0.processIdentifier == processIdentifier + && $0.layer == 0 + && $0.isOnScreen + && !$0.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + } + guard let focused = eligible.first else { return .failure(.noEligibleWindows) } + + // Device Hub's AX title is canonical (` – `). Match + // it exactly: simulator names are user-controlled and prefix matching + // could route input to a different device with a longer name. + let matches = eligible.filter { $0.name == targetWindowTitle } + guard !matches.isEmpty else { return .failure(.targetNotFound) } + guard matches.count == 1 else { return .failure(.ambiguousTarget(count: matches.count)) } + guard matches[0].windowNumber == focused.windowNumber else { + return .failure(.targetNotFocused(focusedName: focused.name)) + } + return .success(matches[0]) + } +} + +public enum HIDModifier: Equatable, Sendable { + case control + case shift + case option + case command +} + +/// USB HID Keyboard/Keypad usage (page 0x07) to macOS virtual key code. +public enum HIDKeyboardMapping { + public static func macVirtualKeyCode(for usage: UInt32) -> UInt16? { + keyCodes[usage] + } + + public static func modifier(for usage: UInt32) -> HIDModifier? { + switch usage { + case 0xE0, 0xE4: return .control + case 0xE1, 0xE5: return .shift + case 0xE2, 0xE6: return .option + case 0xE3, 0xE7: return .command + default: return nil + } + } + + private static let keyCodes: [UInt32: UInt16] = [ + // A-Z + 0x04: 0, 0x05: 11, 0x06: 8, 0x07: 2, 0x08: 14, 0x09: 3, + 0x0A: 5, 0x0B: 4, 0x0C: 34, 0x0D: 38, 0x0E: 40, 0x0F: 37, + 0x10: 46, 0x11: 45, 0x12: 31, 0x13: 35, 0x14: 12, 0x15: 15, + 0x16: 1, 0x17: 17, 0x18: 32, 0x19: 9, 0x1A: 13, 0x1B: 7, + 0x1C: 16, 0x1D: 6, + + // Number row + 0x1E: 18, 0x1F: 19, 0x20: 20, 0x21: 21, 0x22: 23, + 0x23: 22, 0x24: 26, 0x25: 28, 0x26: 25, 0x27: 29, + + // Editing and punctuation + 0x28: 36, 0x29: 53, 0x2A: 51, 0x2B: 48, 0x2C: 49, + 0x2D: 27, 0x2E: 24, 0x2F: 33, 0x30: 30, 0x31: 42, + 0x32: 42, 0x33: 41, 0x34: 39, 0x35: 50, 0x36: 43, + 0x37: 47, 0x38: 44, 0x39: 57, + + // Function keys + 0x3A: 122, 0x3B: 120, 0x3C: 99, 0x3D: 118, 0x3E: 96, 0x3F: 97, + 0x40: 98, 0x41: 100, 0x42: 101, 0x43: 109, 0x44: 103, 0x45: 111, + 0x46: 105, 0x47: 107, 0x48: 113, + + // Navigation + 0x49: 114, 0x4A: 115, 0x4B: 116, 0x4C: 117, 0x4D: 119, + 0x4E: 121, 0x4F: 124, 0x50: 123, 0x51: 125, 0x52: 126, + + // Numeric keypad + 0x53: 71, 0x54: 75, 0x55: 67, 0x56: 78, 0x57: 69, 0x58: 76, + 0x59: 83, 0x5A: 84, 0x5B: 85, 0x5C: 86, 0x5D: 87, 0x5E: 88, + 0x5F: 89, 0x60: 91, 0x61: 92, 0x62: 82, 0x63: 65, + 0x64: 10, 0x67: 81, + + // Media keys commonly emitted by browsers + 0x7F: 74, 0x80: 72, 0x81: 73, + + // Modifiers + 0xE0: 59, 0xE1: 56, 0xE2: 58, 0xE3: 55, + 0xE4: 62, 0xE5: 60, 0xE6: 61, 0xE7: 54, + ] +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift b/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift new file mode 100644 index 00000000..930d0fc4 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift @@ -0,0 +1,49 @@ +/// Named hinge angles for iPhone Duo (V68). +/// +/// Which framebuffer is live is decided in TypeScript from SpringBoard's +/// primary-panel readback, with the 90° rule only before that readback. +/// Touch routing uses the captured pixel size (`touchTarget`), not the pose. +/// Do not toggle SimScreen power — FrontBoard's bookend display monitor +/// asserts if a foldable panel is missing at SpringBoard launch. +public struct DevicePoseSpec: Equatable, Sendable { + public let name: String + public let hingeDegrees: Double + + public init(name: String, hingeDegrees: Double) { + self.name = name + self.hingeDegrees = hingeDegrees + } +} + +public enum DevicePose { + public static let coverWidth = 1398 + public static let coverHeight = 2034 + public static let innerWidth = 2007 + public static let innerHeight = 2853 + + /// Duo's own Integrated screen registrations, including SimulatorHID's mask. + /// Do not apply this mapping to TVOut, CarPlay, or single-panel devices. + public static func touchTarget(width: Int, height: Int) -> UInt32? { + let size = [width, height].sorted() + if size == [coverWidth, coverHeight] { return 0x40000001 } + if size == [innerWidth, innerHeight] { return 0x40000003 } + return nil + } + + public static let presets: [DevicePoseSpec] = [ + DevicePoseSpec(name: "closed", hingeDegrees: 0), + DevicePoseSpec(name: "tent", hingeDegrees: 80), + DevicePoseSpec(name: "tabletop", hingeDegrees: 100), + DevicePoseSpec(name: "book", hingeDegrees: 130), + DevicePoseSpec(name: "open", hingeDegrees: 180), + ] + + public static func preset(named raw: String) -> DevicePoseSpec? { + let name = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + if name == "cover" { return preset(named: "closed") } + if name == "inner" || name == "openflat" || name == "flat" { + return preset(named: "open") + } + return presets.first { $0.name == name } + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift b/tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift new file mode 100644 index 00000000..123b1ef6 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift @@ -0,0 +1,455 @@ +import Foundation +import CoreVideo +import CoreMedia +import CoreGraphics +import IOSurface +import ObjectiveC + +/// Headless simulator frame capture via direct IOSurface access. +/// +/// Uses SimulatorKit frame callbacks (via objc_msgSend on the IO port descriptor) +/// for event-driven capture with zero jitter. Maintains a 5fps idle floor +/// for late-joining clients. +/// +/// Pipeline: IOSurface (shared memory) → CVPixelBuffer (zero-copy) → H.264 encode +actor FrameCapture { + private let queue = DispatchSerialQueue(label: "frame-capture", qos: .userInteractive) + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + private var photocopier = Photocopier() + private var onFrame: ((CVPixelBuffer, CMTime) -> Void)? + private var frameCount: UInt64 = 0 + private(set) var capturedWidth: Int = 0 + private(set) var capturedHeight: Int = 0 + private var idleTimer: Task? + private var lastCaptureTime: ContinuousClock.Instant = .now + private var lastSeeds: [ObjectIdentifier: UInt32] = [:] + private var rewireTickCount: Int = 0 + /// Interval at which the idle timer re-emits the current frame even when + /// the simulator isn't rendering anything new. This is load-bearing for two + /// consumers: + /// 1. Browsers rendering `` only render a multipart + /// chunk once the NEXT boundary arrives, so a single static frame never + /// paints until something changes. + /// 2. Any upstream MJPEG→WebSocket relay only caches a frame when at least + /// one subscriber is due for it — a late-joining relay subscriber on an + /// idle sim never gets a cached frame to show. + /// Re-emitting at ~5 fps fixes both without meaningful CPU cost. + private static let idleInterval: ContinuousClock.Duration = .milliseconds(200) + + private var descriptors: [NSObject] = [] + private var callbackUUIDs: [ObjectIdentifier: UUID] = [:] + private var ioClient: NSObject? + private var expectedScreenSizes: [FramebufferSurfaceSize] = [] + private var preferredScreenSize: FramebufferSurfaceSize? + private var didLogRejectedPresentationSurface = false + private var didLogMissingExpectedSurface = false + + func start(deviceUDID: String, onFrame: @escaping @Sendable (CVPixelBuffer, CMTime) -> Void) throws { + self.onFrame = onFrame + + SimFrameworks.load() + guard let device = Self.findSimDevice(udid: deviceUDID) else { + throw makeError(1, "Device \(deviceUDID) not found") + } + + let state = device.value(forKey: "stateString") as? String ?? "unknown" + guard state == "Booted" else { + throw makeError(2, "Device not booted (state: \(state))") + } + self.expectedScreenSizes = Self.nativeScreenSizes(for: device) + if expectedScreenSizes.count > 1 { + let summary = expectedScreenSizes.map { "\($0.width)x\($0.height)" }.joined(separator: ", ") + print("[capture] Device has \(expectedScreenSizes.count) native displays: \(summary)") + } + + guard let io = device.perform(NSSelectorFromString("io"))?.takeUnretainedValue() as? NSObject else { + throw makeError(3, "Failed to get device IO") + } + self.ioClient = io + + try wireUpFramebuffer() + startIdleTimer() + print("[capture] Frame callbacks registered (event-driven) + 5fps idle floor") + } + + /// Find all framebuffer display descriptors, register callbacks on each, + /// and cache them. Safe to re-call if the cached descriptors become stale. + /// + /// The simulator exposes multiple `com.apple.framebuffer.display` ports + /// (main screen + secondary planes/overlays). We can't reliably tell which + /// one is the primary up-front, so we listen on all of them and let + /// `captureFrame()` pick whichever currently has the largest live surface. + private func wireUpFramebuffer() throws { + guard let io = ioClient else { + throw makeError(3, "No IO client") + } + + // Refresh ports — descriptors are created lazily. + io.perform(NSSelectorFromString("updateIOPorts")) + + let candidates = try findFramebufferDescriptors(io: io) + + // Tear down old callbacks. + let unregSel = NSSelectorFromString("unregisterScreenCallbacksWithUUID:") + for oldDesc in descriptors { + if let uuid = callbackUUIDs[ObjectIdentifier(oldDesc)], + oldDesc.responds(to: unregSel) { + oldDesc.perform(unregSel, with: uuid) + } + } + callbackUUIDs.removeAll() + lastSeeds.removeAll() + descriptors = candidates + + // Registering screen callbacks is what causes SimulatorKit to wire the + // display pipeline to our client and populate `framebufferSurface`. + for desc in candidates { + try registerFrameCallbacks(desc: desc) + } + + if let best = pickBestDescriptor() { + let surfSel = NSSelectorFromString("framebufferSurface") + if let surfObj = best.perform(surfSel)?.takeUnretainedValue() { + let surf = unsafeBitCast(surfObj, to: IOSurface.self) + capturedWidth = IOSurfaceGetWidth(surf) + capturedHeight = IOSurfaceGetHeight(surf) + print("[capture] Framebuffer: \(capturedWidth)x\(capturedHeight) (direct IOSurface, zero-copy)") + } + } + + captureFrame() + } + + private func findFramebufferDescriptors(io: NSObject) throws -> [NSObject] { + guard let ports = io.value(forKey: "deviceIOPorts") as? [NSObject] else { + throw makeError(4, "Failed to get IO ports") + } + + let pidSel = NSSelectorFromString("portIdentifier") + let descSel = NSSelectorFromString("descriptor") + let surfSel = NSSelectorFromString("framebufferSurface") + + var candidates: [NSObject] = [] + for port in ports { + guard port.responds(to: pidSel), + let pid = port.perform(pidSel)?.takeUnretainedValue(), + "\(pid)" == "com.apple.framebuffer.display", + port.responds(to: descSel), + let desc = port.perform(descSel)?.takeUnretainedValue() as? NSObject, + desc.responds(to: surfSel) + else { continue } + candidates.append(desc) + } + + if candidates.isEmpty { + throw makeError(5, "No framebuffer display descriptor found") + } + return candidates + } + + /// Return the descriptor matching the device type's native screen size. + /// Xcode 27 can expose an additional 7680x4320 presentation surface while + /// Device Hub is resizing; selecting the historical largest surface then + /// feeds a non-device frame to VideoToolbox and produces `encodingFailed`. + /// If screen metadata is unavailable, retain the old largest-live fallback. + private func pickBestDescriptor() -> NSObject? { + let surfSel = NSSelectorFromString("framebufferSurface") + let sizes = descriptors.map { desc -> FramebufferSurfaceSize in + guard let surfObj = desc.perform(surfSel)?.takeUnretainedValue() else { + return FramebufferSurfaceSize(width: 0, height: 0) + } + let surf = unsafeBitCast(surfObj, to: IOSurface.self) + return FramebufferSurfaceSize( + width: IOSurfaceGetWidth(surf), + height: IOSurfaceGetHeight(surf) + ) + } + guard let selection = FramebufferSurfaceSelector.select( + from: sizes, + expectedSizes: expectedScreenSizes, + preferredSize: preferredScreenSize + ) else { + return nil + } + + if selection.matchedExpectedSize, !didLogRejectedPresentationSurface { + let selected = sizes[selection.index] + let selectedArea = Int64(selected.width) * Int64(selected.height) + if let larger = sizes.filter(\.isLive).max(by: { + Int64($0.width) * Int64($0.height) < Int64($1.width) * Int64($1.height) + }), Int64(larger.width) * Int64(larger.height) > selectedArea { + print( + "[capture] Ignoring non-device framebuffer \(larger.width)x\(larger.height); " + + "native screen is \(selected.width)x\(selected.height)" + ) + didLogRejectedPresentationSurface = true + } + } else if + !selection.matchedExpectedSize, + !expectedScreenSizes.isEmpty, + !didLogMissingExpectedSurface + { + let selected = sizes[selection.index] + let expected = expectedScreenSizes.map { "\($0.width)x\($0.height)" }.joined(separator: ", ") + print( + "[capture] No framebuffer matches native screen(s) \(expected); " + + "falling back to \(selected.width)x\(selected.height)" + ) + didLogMissingExpectedSurface = true + } + return descriptors[selection.index] + } + + // MARK: - Frame callbacks via objc_msgSend + + private func registerFrameCallbacks(desc: AnyObject) throws { + let regSel = #selector(FramebufferDescriptor.registerScreenCallbacks) + guard desc.responds(to: regSel) else { + throw makeError(8, "Descriptor doesn't support registerScreenCallbacks") + } + + let uuid = UUID() + callbackUUIDs[ObjectIdentifier(desc)] = uuid + + desc.registerScreenCallbacks( + uuid: uuid, + callbackQueue: queue, + frameCallback: { [self] in assumeIsolated { $0.captureFrame() } }, + surfacesChangedCallback: { [self] in assumeIsolated { $0.captureFrame() } }, + propertiesChangedCallback: {} + ) + } + + private func startIdleTimer() { + self.idleTimer = Task { [weak self] in + while !Task.isCancelled { + guard let self else { return } + await self.onIdleTimerTick() + try? await Task.sleep(for: Self.idleInterval) + } + } + } + + private func onIdleTimerTick() { + let now = ContinuousClock.now + guard (now - self.lastCaptureTime) >= Self.idleInterval else { return } + self.captureFrame(force: true) + // Self-heal: if we've never captured a frame, the cached descriptor + // is likely stale. Re-wire the pipeline periodically (every ~1s) + // until frames start flowing. + if self.frameCount == 0 { + self.rewireTickCount += 1 + if self.rewireTickCount % 5 == 0 { + do { + try self.wireUpFramebuffer() + } catch { + // Swallow — we'll try again on the next tick. + } + } + } + } + + // MARK: - Frame capture + + private func captureFrame(force: Bool = false) { + guard let desc = pickBestDescriptor() else { return } + + let surfSel = NSSelectorFromString("framebufferSurface") + guard let surfObj = desc.perform(surfSel)?.takeUnretainedValue() else { return } + let surface = unsafeBitCast(surfObj, to: IOSurface.self) + + // Seed-skip: when the simulator's framebuffer content hasn't changed, + // don't spend cycles re-encoding the same pixels back-to-back from the + // frame-callback path. BUT: we must still re-emit at the idle floor + // (~5 fps) so that downstream consumers keep seeing a live stream — + // see the `idleInterval` doc-comment for why that matters. + let key = ObjectIdentifier(desc) + let seed = IOSurfaceGetSeed(surface) + let seedChanged = lastSeeds[key] != seed + if frameCount > 0, !seedChanged, !force { return } + lastSeeds[key] = seed + + let w = IOSurfaceGetWidth(surface) + let h = IOSurfaceGetHeight(surface) + guard w > 0, h > 0 else { return } + + if capturedWidth != w || capturedHeight != h { + capturedWidth = w + capturedHeight = h + print("[capture] Surface size changed: \(w)x\(h)") + } + + var pixelBuffer: Unmanaged? + let status = CVPixelBufferCreateWithIOSurface( + kCFAllocatorDefault, surface, + [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA] as CFDictionary, + &pixelBuffer + ) + guard status == kCVReturnSuccess, let pb = pixelBuffer?.takeRetainedValue() else { return } + + lastCaptureTime = .now + frameCount += 1 + let timestamp = CMTime(value: CMTimeValue(frameCount), timescale: 60) + guard let copy = photocopier.copy(pb) else { return } + onFrame?(copy, timestamp) + } + + func getScreenSize() -> (width: Int, height: Int)? { + guard capturedWidth > 0, capturedHeight > 0 else { return nil } + return (capturedWidth, capturedHeight) + } + + func stop() { + idleTimer?.cancel() + idleTimer = nil + + let unregSel = NSSelectorFromString("unregisterScreenCallbacksWithUUID:") + for desc in descriptors { + if let uuid = callbackUUIDs[ObjectIdentifier(desc)], + desc.responds(to: unregSel) { + desc.perform(unregSel, with: uuid) + } + } + callbackUUIDs.removeAll() + descriptors.removeAll() + lastSeeds.removeAll() + onFrame = nil + frameCount = 0 + capturedWidth = 0 + capturedHeight = 0 + rewireTickCount = 0 + lastCaptureTime = .now + ioClient = nil + expectedScreenSizes = [] + preferredScreenSize = nil + didLogRejectedPresentationSurface = false + didLogMissingExpectedSurface = false + } + + func setPreferredScreenSize(width: Int, height: Int) { + if width > 0, height > 0 { + preferredScreenSize = FramebufferSurfaceSize(width: width, height: height) + } else { + preferredScreenSize = nil + } + didLogRejectedPresentationSurface = false + didLogMissingExpectedSurface = false + captureFrame(force: true) + } + + // MARK: - Helpers + + private func makeError(_ code: Int, _ msg: String) -> NSError { + NSError(domain: "FrameCapture", code: code, + userInfo: [NSLocalizedDescriptionKey: msg]) + } + + /// Native pixel sizes for every integrated digitizer display on this device + /// type. Foldables list more than one (iPhone Duo cover + inner). Falls back + /// to `SimDeviceType.mainScreenSize` when the capabilities plist is missing. + private static func nativeScreenSizes(for device: NSObject) -> [FramebufferSurfaceSize] { + if let fromCapabilities = sizesFromCapabilities(device), !fromCapabilities.isEmpty { + return fromCapabilities + } + if let main = mainScreenSize(for: device) { + return [main] + } + return [] + } + + private static func sizesFromCapabilities(_ device: NSObject) -> [FramebufferSurfaceSize]? { + guard let deviceType = deviceType(for: device) else { return nil } + let bundlePath: String? + if let path = deviceType.value(forKey: "bundlePath") as? String { + bundlePath = path + } else if let url = deviceType.value(forKey: "bundleURL") as? URL { + bundlePath = url.path + } else { + bundlePath = nil + } + guard let bundlePath else { return nil } + let capPath = (bundlePath as NSString).appendingPathComponent("Contents/Resources/capabilities.plist") + guard let data = try? Data(contentsOf: URL(fileURLWithPath: capPath)) else { return nil } + return DeviceDisplaySizes.nativeSizes(fromCapabilitiesPlist: data) + } + + /// SimDeviceType.mainScreenSize is private but stable across the same + /// SimulatorKit versions already used by this file. Runtime validation on + /// Xcode 27 confirms that it reports native pixels, not logical points, and + /// matches the primary IOSurface exactly. The `@convention(c)` IMP type is + /// intentional: it preserves the platform CGSize return ABI (registers on + /// arm64 and the appropriate struct-return convention on x86_64). + private static func mainScreenSize(for device: NSObject) -> FramebufferSurfaceSize? { + guard let deviceType = deviceType(for: device) else { return nil } + let selector = NSSelectorFromString("mainScreenSize") + guard deviceType.responds(to: selector) else { return nil } + + typealias GetSize = @convention(c) (AnyObject, Selector) -> CGSize + let getSize = unsafeBitCast(deviceType.method(for: selector), to: GetSize.self) + let size = getSize(deviceType, selector) + guard size.width.isFinite, size.height.isFinite else { return nil } + + let width = Int(size.width.rounded()) + let height = Int(size.height.rounded()) + guard width > 0, height > 0 else { return nil } + return FramebufferSurfaceSize(width: width, height: height) + } + + private static func deviceType(for device: NSObject) -> NSObject? { + let deviceTypeSelector = NSSelectorFromString("deviceType") + guard + device.responds(to: deviceTypeSelector), + let deviceType = device.perform(deviceTypeSelector)?.takeUnretainedValue() as? NSObject + else { + return nil + } + return deviceType + } + + /// simlock patch: the device set to search, or nil for CoreSimulator's default. + /// + /// serve-sim looks devices up in the default set only. simlock boots every simulator in a + /// simlock-owned `simctl --set` root (ADR 0001), so its UDIDs are not in the default set and + /// the unpatched lookup answers "not found" for a device that is demonstrably booted. + /// CoreSimulator has no environment variable that moves the default set (`HOME` does not + /// move it either), so the set has to be named here. + static var deviceSetPath: String? { + ProcessInfo.processInfo.environment["SIMLOCK_IOS_DEVICE_SET"] + } + + static func findSimDevice(udid: String) -> NSObject? { + guard let contextClass = NSClassFromString("SimServiceContext") as? NSObject.Type else { return nil } + let developerDir = Xcode.developerDir() + let sharedSel = NSSelectorFromString("sharedServiceContextForDeveloperDir:error:") + guard let context = contextClass.perform(sharedSel, with: developerDir, with: nil)? + .takeUnretainedValue() as? NSObject else { return nil } + let deviceSet: NSObject + if let path = deviceSetPath { + let setSel = NSSelectorFromString("deviceSetWithPath:error:") + guard let set = context.perform(setSel, with: path, with: nil)? + .takeUnretainedValue() as? NSObject else { return nil } + deviceSet = set + } else { + let deviceSetSel = NSSelectorFromString("defaultDeviceSetWithError:") + guard let set = context.perform(deviceSetSel, with: nil)? + .takeUnretainedValue() as? NSObject else { return nil } + deviceSet = set + } + guard let devices = deviceSet.value(forKey: "devices") as? [NSObject] else { return nil } + return devices.first(where: { + ($0.value(forKey: "UDID") as? NSUUID)?.uuidString == udid + }) + } +} + +@objc protocol FramebufferDescriptor { + @objc(registerScreenCallbacksWithUUID:callbackQueue:frameCallback:surfacesChangedCallback:propertiesChangedCallback:) + func registerScreenCallbacks( + uuid: UUID, + callbackQueue: DispatchQueue, + frameCallback: @convention(block) @escaping () -> Void, + surfacesChangedCallback: @convention(block) @escaping () -> Void, + propertiesChangedCallback: @convention(block) @escaping () -> Void + ) +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift b/tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift new file mode 100644 index 00000000..9e71631a --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift @@ -0,0 +1,77 @@ +public struct FramebufferSurfaceSize: Equatable, Sendable { + public let width: Int + public let height: Int + + public init(width: Int, height: Int) { + self.width = width + self.height = height + } + + public var isLive: Bool { width > 0 && height > 0 } + + fileprivate func matches(_ other: FramebufferSurfaceSize) -> Bool { + (width == other.width && height == other.height) + || (width == other.height && height == other.width) + } +} + +public struct FramebufferSurfaceSelection: Equatable, Sendable { + public let index: Int + public let matchedExpectedSize: Bool + + public init(index: Int, matchedExpectedSize: Bool) { + self.index = index + self.matchedExpectedSize = matchedExpectedSize + } +} + +/// Selects the simulator's main display without relying on a maximum-size +/// heuristic. Newer Device Hub versions may publish an additional presentation +/// surface (for example 7680x4320 while resizing); the device type's native +/// screen sizes identify the real display(s). Foldables expose more than one +/// native size (cover + inner). With no preferred size, capture follows Device +/// Hub's rest pose: the smaller cover panel. A preferred size pins one of them. +/// When that private metadata is not available, the historical largest-live +/// surface behavior is preserved. +public enum FramebufferSurfaceSelector { + public static func select( + from candidates: [FramebufferSurfaceSize], + expectedSize: FramebufferSurfaceSize? + ) -> FramebufferSurfaceSelection? { + select( + from: candidates, + expectedSizes: expectedSize.map { [$0] } ?? [], + preferredSize: nil + ) + } + + public static func select( + from candidates: [FramebufferSurfaceSize], + expectedSizes: [FramebufferSurfaceSize], + preferredSize: FramebufferSurfaceSize? = nil + ) -> FramebufferSurfaceSelection? { + let live = candidates.indices.filter { candidates[$0].isLive } + guard !live.isEmpty else { return nil } + + if let preferredSize, preferredSize.isLive, + let preferred = live.first(where: { candidates[$0].matches(preferredSize) }) { + return FramebufferSurfaceSelection(index: preferred, matchedExpectedSize: true) + } + + let expectedLive = live.filter { index in + expectedSizes.contains { $0.isLive && candidates[index].matches($0) } + } + if let bestExpected = expectedLive.min(by: { area(of: candidates[$0]) < area(of: candidates[$1]) }) { + return FramebufferSurfaceSelection(index: bestExpected, matchedExpectedSize: true) + } + + let largest = live.max { lhs, rhs in + area(of: candidates[lhs]) < area(of: candidates[rhs]) + }! + return FramebufferSurfaceSelection(index: largest, matchedExpectedSize: false) + } + + private static func area(of size: FramebufferSurfaceSize) -> Int64 { + Int64(size.width) * Int64(size.height) + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift b/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift new file mode 100644 index 00000000..b76426cd --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift @@ -0,0 +1,229 @@ +import Foundation +import CoreVideo +import CoreMedia +import VideoToolbox + +/// Real-time H.264 encoder backed by `VTCompressionSession`, producing AVCC +/// (length-prefixed NAL) output for the `/stream.avcc` endpoint. +/// +/// Submission is fire-and-forget: the caller hands a `CVPixelBuffer` in and +/// the encoded chunk comes back via `onEncoded` on VideoToolbox's own queue. +/// The incoming buffer wraps SimulatorKit's live framebuffer IOSurface, which +/// SimulatorKit recycles in place — VT encodes asynchronously, so we deep-copy +/// into a private pooled buffer before submitting to avoid a torn frame race. +actor H264Encoder { + let queue = DispatchSerialQueue(label: "h264-encoder", qos: .userInteractive) + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + struct Encoded { + /// avcC parameter-set blob — emitted once on the first IDR per session. + let description: Data? + let kind: Kind + /// Length-prefixed AVCC NAL bytes (not Annex-B start codes). + let avcc: Data + enum Kind { case keyframe, delta } + } + + private var session: VTCompressionSession? + private var width: Int32 = 0 + private var height: Int32 = 0 + private let fps: Int32 + private var bitrate: Int + private var emittedDescription = false + private var frameCount: Int64 = 0 + + init(fps: Int = 60, bitrate: Int = 6_000_000) { + self.fps = Int32(fps) + self.bitrate = bitrate + } + + deinit { + if let session { VTCompressionSessionInvalidate(session) } + } + + /// Encode one frame. Real-time VideoToolbox sessions may deliberately drop + /// a frame under pressure; that is reported as `nil`, not as an error. + func encode(_ source: CVPixelBuffer, forceKeyframe: Bool = false) async throws -> Encoded? { + let w = Int32(CVPixelBufferGetWidth(source)) + let h = Int32(CVPixelBufferGetHeight(source)) + if session == nil || w != width || h != height { + width = w + height = h + rebuildSession() + } + guard let session else { + throw Errors.couldNotCreateSession + } + + frameCount += 1 + let pts = CMTime(value: frameCount, timescale: fps) + let frameProps: NSDictionary? = forceKeyframe + ? [kVTEncodeFrameOptionKey_ForceKeyFrame: kCFBooleanTrue!] as NSDictionary + : nil + + let result: (buffer: CMSampleBuffer?, status: OSStatus, flags: VTEncodeInfoFlags) = await withCheckedContinuation { continuation in + let status = VTCompressionSessionEncodeFrame( + session, + imageBuffer: source, + presentationTimeStamp: pts, + duration: .invalid, + frameProperties: frameProps, + infoFlagsOut: nil + ) { @Sendable status, flags, sampleBuffer in + guard status == noErr, let sampleBuffer else { + continuation.resume(returning: (nil, status, flags)) + return + } + continuation.resume(returning: (sampleBuffer, status, flags)) + } + if status != noErr { + continuation.resume(returning: (nil, status, [])) + } + } + if result.status == noErr, result.flags.contains(.frameDropped) { return nil } + guard result.status == noErr else { throw Errors.encodingFailed(result.status) } + guard let buffer = result.buffer else { throw Errors.missingSampleBuffer } + return try extract(from: buffer) + } + + func stop() { + if let session { + VTCompressionSessionInvalidate(session) + self.session = nil + } + } + + // MARK: - private + + private func rebuildSession() { + if let session { + VTCompressionSessionInvalidate(session) + self.session = nil + } + + // Low-latency rate control puts VideoToolbox in its real-time/low-delay + // pipeline and, crucially, emits a bitstream the *decoder* treats as + // low-latency (small max_dec_frame_buffering). Without it the decoder + // fills a large DPB before emitting, adding ~300ms of latency on the + // client even though the stream carries no B-frames. Falls back to the + // default spec on the rare hardware that rejects it. + let lowLatencySpec: NSDictionary = [ + kVTVideoEncoderSpecification_EnableLowLatencyRateControl: kCFBooleanTrue!, + ] + var sess: VTCompressionSession? + func create(spec: CFDictionary?) -> OSStatus { + VTCompressionSessionCreate( + allocator: kCFAllocatorDefault, + width: width, height: height, + codecType: kCMVideoCodecType_H264, + encoderSpecification: spec, + imageBufferAttributes: nil, + compressedDataAllocator: kCFAllocatorDefault, + outputCallback: nil, + refcon: nil, + compressionSessionOut: &sess + ) + } + var status = create(spec: lowLatencySpec) + if status != noErr || sess == nil { + sess = nil + status = create(spec: nil) + } + guard status == noErr, let sess else { return } + + let props: [(CFString, Any)] = [ + (kVTCompressionPropertyKey_RealTime, kCFBooleanTrue!), + (kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel), + (kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse!), + (kVTCompressionPropertyKey_AverageBitRate, NSNumber(value: bitrate)), + (kVTCompressionPropertyKey_ExpectedFrameRate, NSNumber(value: fps)), + // 5s keyframe interval: IDRs are far larger than P-frames, so + // spacing them out keeps scroll/animation smooth. Late joiners + // don't wait for the natural IDR — we force one on connect. + (kVTCompressionPropertyKey_MaxKeyFrameInterval, NSNumber(value: fps * 5)), + ] + for (key, value) in props { + VTSessionSetProperty(sess, key: key, value: value as CFTypeRef) + } + VTCompressionSessionPrepareToEncodeFrames(sess) + session = sess + emittedDescription = false + } + + private func extract(from sample: CMSampleBuffer) throws -> Encoded { + let isKeyframe = !notSync(sample) + guard let dataBuf = CMSampleBufferGetDataBuffer(sample) else { + throw Errors.invalidSampleBuffer + } + + var totalLength = 0 + var dataPointer: UnsafeMutablePointer? + guard CMBlockBufferGetDataPointer( + dataBuf, atOffset: 0, lengthAtOffsetOut: nil, + totalLengthOut: &totalLength, dataPointerOut: &dataPointer + ) == noErr, let dataPointer else { + throw Errors.invalidSampleBuffer + } + let avcc = Data(bytes: dataPointer, count: totalLength) + + var description: Data? + if isKeyframe, let format = CMSampleBufferGetFormatDescription(sample) { + let nextDescription = avcCBlob(from: format) + if !emittedDescription && nextDescription != nil { + emittedDescription = true + description = nextDescription + } + } + return Encoded(description: description, kind: isKeyframe ? .keyframe : .delta, avcc: avcc) + } + + private func notSync(_ sample: CMSampleBuffer) -> Bool { + guard let attachments = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: false), + CFArrayGetCount(attachments) > 0, + let dict = CFArrayGetValueAtIndex(attachments, 0) else { return false } + let cfDict = unsafeBitCast(dict, to: CFDictionary.self) + return CFDictionaryContainsKey(cfDict, Unmanaged.passUnretained(kCMSampleAttachmentKey_NotSync).toOpaque()) + } + + /// avcC parameter-set blob (ISO/IEC 14496-15 §5.2.4.1) carrying SPS + PPS. + private func avcCBlob(from format: CMFormatDescription) -> Data? { + var spsCount = 0 + var spsPtr: UnsafePointer? + var spsSize = 0 + var nalSize: Int32 = 0 + guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + format, parameterSetIndex: 0, + parameterSetPointerOut: &spsPtr, parameterSetSizeOut: &spsSize, + parameterSetCountOut: &spsCount, nalUnitHeaderLengthOut: &nalSize + ) == noErr, let spsPtr, spsSize >= 4 else { return nil } + + var ppsPtr: UnsafePointer? + var ppsSize = 0 + guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + format, parameterSetIndex: 1, + parameterSetPointerOut: &ppsPtr, parameterSetSizeOut: &ppsSize, + parameterSetCountOut: nil, nalUnitHeaderLengthOut: nil + ) == noErr, let ppsPtr else { return nil } + + let sps = UnsafeBufferPointer(start: spsPtr, count: spsSize) + let pps = UnsafeBufferPointer(start: ppsPtr, count: ppsSize) + var blob = Data() + blob.append(0x01) + blob.append(sps[1]); blob.append(sps[2]); blob.append(sps[3]) + blob.append(0xFF) + blob.append(0xE1) + blob.append(UInt8((spsSize >> 8) & 0xFF)); blob.append(UInt8(spsSize & 0xFF)) + blob.append(contentsOf: sps) + blob.append(0x01) + blob.append(UInt8((ppsSize >> 8) & 0xFF)); blob.append(UInt8(ppsSize & 0xFF)) + blob.append(contentsOf: pps) + return blob + } + + enum Errors: Error { + case couldNotCreateSession + case encodingFailed(OSStatus) + case missingSampleBuffer + case invalidSampleBuffer + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift b/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift new file mode 100644 index 00000000..fa1c6694 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift @@ -0,0 +1,62 @@ +import CoreVideo + +struct Dimensions: Hashable, Sendable { + var width: Int + var height: Int +} + +extension CVPixelBuffer { + var dimensions: Dimensions { + Dimensions(width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self)) + } +} + +struct Photocopier { + private var _pool: CVPixelBufferPool? + private var dimensions: Dimensions? + + init() {} + + private mutating func pool(dimensions: Dimensions) -> CVPixelBufferPool? { + if let _pool, self.dimensions == dimensions { + return _pool + } + let attrs: [String: Any] = [ + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, + kCVPixelBufferWidthKey as String: Int(dimensions.width), + kCVPixelBufferHeightKey as String: Int(dimensions.height), + kCVPixelBufferIOSurfacePropertiesKey as String: [:], + ] + var newPool: CVPixelBufferPool? + CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs as CFDictionary, &newPool) + self._pool = newPool + self.dimensions = dimensions + return newPool + } + + /// Deep-copy `source` (which wraps the recycled framebuffer IOSurface) + /// into a private pooled buffer that sinks can retain. + mutating func copy(_ source: CVPixelBuffer) -> CVPixelBuffer? { + guard let pool = self.pool(dimensions: source.dimensions) else { return nil } + var out: CVPixelBuffer? + guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &out) == kCVReturnSuccess, + let dst = out else { return nil } + + CVPixelBufferLockBaseAddress(source, .readOnly) + CVPixelBufferLockBaseAddress(dst, []) + defer { + CVPixelBufferUnlockBaseAddress(dst, []) + CVPixelBufferUnlockBaseAddress(source, .readOnly) + } + guard let srcAddr = CVPixelBufferGetBaseAddress(source), + let dstAddr = CVPixelBufferGetBaseAddress(dst) else { return nil } + let srcStride = CVPixelBufferGetBytesPerRow(source) + let dstStride = CVPixelBufferGetBytesPerRow(dst) + let rows = CVPixelBufferGetHeight(source) + let copyBytes = min(srcStride, dstStride) + for row in 0..`; high bandwidth. +/// - `avcc`: length-prefixed H.264 NAL chunks (AVCC framing) decoded by the +/// browser's WebCodecs `VideoDecoder`. ~5-10x less bandwidth; needs a +/// canvas + `VideoDecoder`, so the client feature-detects and falls back +/// to `mjpeg`. +enum StreamFormat: String { + case mjpeg + case avcc +} + +/// Bytes that wrap each chunk on the `/stream.avcc` wire. Every chunk is a +/// 4-byte big-endian length (covering the tag byte + payload) followed by a +/// one-byte tag and the payload: +/// +/// - `0x01` description — avcC parameter-set blob (SPS/PPS); configures the +/// decoder. Emitted once per encoder session and replayed to late joiners. +/// - `0x02` keyframe — IDR (decoder can start here). +/// - `0x03` delta — non-IDR P-frame (depends on prior frames). +/// - `0x04` seed — a JPEG painted immediately on connect so the viewer sees +/// the current screen before the first IDR decodes. +/// +enum AVCCEnvelope { + static let descriptionTag: UInt8 = 0x01 + static let keyframeTag: UInt8 = 0x02 + static let deltaTag: UInt8 = 0x03 + static let seedTag: UInt8 = 0x04 + + static func description(avcc: Data) -> Data { wrap(tag: descriptionTag, payload: avcc) } + static func keyframe(avcc: Data) -> Data { wrap(tag: keyframeTag, payload: avcc) } + static func delta(avcc: Data) -> Data { wrap(tag: deltaTag, payload: avcc) } + static func seed(jpeg: Data) -> Data { wrap(tag: seedTag, payload: jpeg) } + + private static func wrap(tag: UInt8, payload: Data) -> Data { + let length = UInt32(payload.count + 1) + var out = Data(capacity: 5 + payload.count) + withUnsafeBytes(of: length.bigEndian) { out.append(contentsOf: $0) } + out.append(tag) + out.append(payload) + return out + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift b/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift new file mode 100644 index 00000000..0e77fa01 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift @@ -0,0 +1,60 @@ +import Foundation +import CoreVideo +import CoreGraphics +import ImageIO +import UniformTypeIdentifiers + +/// Encodes CVPixelBuffer frames as JPEG data for MJPEG streaming. +actor VideoEncoder { + // makes sure we don't block the main thread / cooperative thread pool + let queue = DispatchSerialQueue(label: "video-encoder", qos: .userInteractive) + + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + private let quality: CGFloat + + init(quality: CGFloat = 0.7) { + self.quality = quality + } + + func encode(pixelBuffer: CVPixelBuffer) throws -> Data { + CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) + defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } + + let width = CVPixelBufferGetWidth(pixelBuffer) + let height = CVPixelBufferGetHeight(pixelBuffer) + guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { + throw Errors.invalidPixelBuffer + } + let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) + + let colorSpace = CGColorSpaceCreateDeviceRGB() + guard let context = CGContext( + data: baseAddress, + width: width, + height: height, + bitsPerComponent: 8, + bytesPerRow: bytesPerRow, + space: colorSpace, + bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue + ), let cgImage = context.makeImage() else { + throw Errors.encodingFailed + } + + let data = NSMutableData() + guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, "public.jpeg" as CFString, 1, nil) else { + throw Errors.encodingFailed + } + CGImageDestinationAddImage(dest, cgImage, [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary) + guard CGImageDestinationFinalize(dest) else { + throw Errors.encodingFailed + } + + return data as Data + } + + enum Errors: Error { + case invalidPixelBuffer + case encodingFailed + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift b/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift new file mode 100644 index 00000000..9b209224 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift @@ -0,0 +1,25 @@ +import Foundation + +enum Xcode { + /// Absolute path to the active Xcode's Developer dir (`xcode-select -p`), + /// e.g. `/Applications/Xcode.app/Contents/Developer`. Falls back to the + /// default install path if `xcode-select` can't be run. + static func developerDir() -> String { + let fallback = "/Applications/Xcode.app/Contents/Developer" + let pipe = Pipe() + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/xcode-select") + process.arguments = ["-p"] + process.standardOutput = pipe + // Only read output if the process actually launched; otherwise + // waitUntilExit() on an unstarted process traps. + do { + try process.run() + } catch { + return fallback + } + process.waitUntilExit() + return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? fallback + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/main.swift b/tools/screen-capture/Sources/simlock-screen-capture/main.swift new file mode 100644 index 00000000..7cfa0294 --- /dev/null +++ b/tools/screen-capture/Sources/simlock-screen-capture/main.swift @@ -0,0 +1,385 @@ +// simlock-screen-capture — streams the framebuffer of one booted iOS Simulator on stdout. +// +// Usage: +// simlock-screen-capture [--codec avcc|mjpeg] [--fps N] [--bitrate BPS] +// [--max-dimension PX] [--duration SEC] +// +// Environment: +// SIMLOCK_IOS_DEVICE_SET the `simctl --set` root the device lives in (see FrameCapture) +// +// The wire +// -------- +// The inherited stdout becomes the video channel and is never written to again by anyone else: +// before anything else happens, fd 1 is redirected onto stderr, so every stray `print()` — the +// copied capture code logs that way, and so does anything a framework might add — lands on the +// log stream instead of cutting a hole in the bitstream. Frames go to a private duplicate of the +// original stdout. +// +// Each frame is one write in serve-sim's AVCC envelope: a 4-byte big-endian length (covering the +// tag byte that follows it), then a tag, then the payload. Tags 0x01-0x04 are serve-sim's (avcC +// description, keyframe, delta, JPEG seed); 0x05 is this tool's own JSON meta frame, written first +// so a relay learns the codec and the size before any video arrives. +// +// stderr is UTF-8 text: the log, plus a single `RESULT ` line at exit. A caller that wants +// the video must keep the two streams apart — which simlock's process port already does, and +// which the `device.exec` protocol cannot because its output chunks are strings. +// +// Lifecycle: capture runs until SIGTERM/SIGINT/SIGHUP or until stdin closes. The daemon stops a +// stream by killing the process, so "the viewer pressed stop" and "the viewer went away" are the +// same event here, and no capture can outlive the connection that asked for it. +import Foundation +import CoreMedia +import CoreVideo +import VideoToolbox + +// MARK: - Options + +struct Options { + var udid = "" + var codec = "avcc" + var fps = 30 + var bitrate = 2_000_000 + var maxDimension = 720 + var duration = 0.0 +} + +func parseOptions(_ args: [String]) -> Options? { + var o = Options() + var positionals: [String] = [] + var i = 0 + while i < args.count { + let a = args[i] + func value() -> String? { + i += 1 + return i < args.count ? args[i] : nil + } + switch a { + case "--codec": guard let v = value() else { return nil }; o.codec = v + case "--fps": guard let v = value(), let n = Int(v) else { return nil }; o.fps = max(1, min(60, n)) + case "--bitrate": guard let v = value(), let n = Int(v) else { return nil }; o.bitrate = max(100_000, n) + case "--max-dimension": guard let v = value(), let n = Int(v) else { return nil }; o.maxDimension = max(0, n) + case "--duration": guard let v = value(), let s = Double(v) else { return nil }; o.duration = max(0, s) + default: + if a.hasPrefix("--") { return nil } + positionals.append(a) + } + i += 1 + } + guard positionals.count == 1 else { return nil } + o.udid = positionals[0] + guard o.codec == "avcc" || o.codec == "mjpeg" else { return nil } + return o +} + +// MARK: - Framing + +/// serve-sim's AVCC envelope (`Sources/SimNative/StreamFormat.swift`) plus this tool's meta tag. +enum Tag: UInt8 { + case description = 0x01 + case keyframe = 0x02 + case delta = 0x03 + case seed = 0x04 + case meta = 0x05 +} + +/// What the frame path mutates, behind one lock. A class rather than an actor because the capture +/// callback is synchronous and stdout needs one writer at a time. +final class Session: @unchecked Sendable { + let lock = NSLock() + var scaler: Scaler? + var scaledSize = Dimensions(width: 0, height: 0) + var frames = 0 + var bytes = 0 + /// The scaler is sized from the first frame, so a resize mid-session is not tracked (a + /// rotation changes the framebuffer's long edge and the scale factor with it). The PoC + /// accepts the first frame's geometry; the fix is to rebuild the scaler on size change. + var scalerSize = Dimensions(width: 0, height: 0) +} + +let session = Session() + +/// The video channel: a private duplicate of the stdout we were started with. The inherited fd 1 +/// is redirected onto stderr below, so text that any code in this process writes to stdout — the +/// copied capture code logs with `print`, and frameworks do too — cannot reach the bitstream. +let videoFd: Int32 = { + let fd = dup(STDOUT_FILENO) + guard fd >= 0 else { + fatalError("could not duplicate stdout") + } + dup2(STDERR_FILENO, STDOUT_FILENO) + return fd +}() + +/// Writes one envelope frame to the video channel: length prefix (covering the tag and payload), +/// tag, payload. A partial write would desynchronize every frame after it, so the whole frame goes +/// out under one lock. +func emit(_ tag: Tag, _ data: Data) { + let total = UInt32(data.count + 1) + var out = Data(capacity: data.count + 5) + out.append(UInt8((total >> 24) & 0xff)) + out.append(UInt8((total >> 16) & 0xff)) + out.append(UInt8((total >> 8) & 0xff)) + out.append(UInt8(total & 0xff)) + out.append(tag.rawValue) + out.append(data) + session.lock.lock() + session.frames += 1 + session.bytes += out.count + session.lock.unlock() + out.withUnsafeBytes { pointer in + var written = 0 + let base = pointer.baseAddress! + while written < out.count { + let n = write(videoFd, base + written, out.count - written) + if n <= 0 { + // EPIPE is the viewer going away, which is the normal end of a stream. + _exit(0) + } + written += n + } + } +} + +func log(_ message: String) { + FileHandle.standardError.write("[capture] \(message)\n".data(using: .utf8)!) +} + +/// Resident set of this process, in bytes. This is the number that answers "what does one open +/// stream cost the worker", so it is measured rather than estimated. +func residentBytes() -> UInt64 { + var info = mach_task_basic_info() + var count = mach_msg_type_number_t(MemoryLayout.size) / 4 + let status = withUnsafeMutablePointer(to: &info) { pointer -> kern_return_t in + pointer.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { + task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) + } + } + return status == KERN_SUCCESS ? info.resident_size : 0 +} + +// MARK: - Downscale +// +// serve-sim captures at the simulator's native framebuffer size (1179x2556 on an iPhone 16). A +// viewer several hops away through the gateway does not need every pixel, and each one is paid +// for twice on the uplink. VTPixelTransferSession scales on the GPU, so the saving is bandwidth +// without a CPU cost on the worker. + +/// Not thread-safe by itself: every call happens on the single encode queue. +final class Scaler: @unchecked Sendable { + private var transfer: VTPixelTransferSession? + private let pool: CVPixelBufferPool + + init(targetWidth: Int, targetHeight: Int) { + let attrs: [String: Any] = [ + kCVPixelBufferWidthKey as String: targetWidth, + kCVPixelBufferHeightKey as String: targetHeight, + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, + kCVPixelBufferIOSurfacePropertiesKey as String: [String: Any](), + ] + var poolRef: CVPixelBufferPool? + CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs as CFDictionary, &poolRef) + self.pool = poolRef! + } + + /// Scales into a fresh buffer from the pool, or returns nil when the GPU transfer fails. + func transferFrom(_ source: CVPixelBuffer) -> CVPixelBuffer? { + if transfer == nil { + var created: VTPixelTransferSession? + guard VTPixelTransferSessionCreate( + allocator: kCFAllocatorDefault, + pixelTransferSessionOut: &created + ) == noErr, let created else { return nil } + transfer = created + } + guard let transfer else { return nil } + var target: CVPixelBuffer? + guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &target) == noErr, + let target else { return nil } + guard VTPixelTransferSessionTransferImage(transfer, from: source, to: target) == noErr else { + return nil + } + return target + } +} + +// MARK: - Signal and stdin teardown + +func watchExitSources() { + for sig in [SIGTERM, SIGINT, SIGHUP] { + signal(sig) { _ in _exit(0) } + } + // stdin is a liveness handle: the daemon that closes it is the daemon that asked for stop. + Thread.detachNewThread { + var byte: UInt8 = 0 + while read(0, &byte, 1) > 0 {} + log("stdin closed") + _exit(0) + } +} + +/// Reports the running totals every few seconds, so a caller's log carries what a stream cost even +/// though a stream ended by a viewer going away has no chance to report at exit. +func watchStats() { + Thread.detachNewThread { + var lastFrames = 0 + var lastBytes = 0 + while true { + Thread.sleep(forTimeInterval: 5) + session.lock.lock() + let frames = session.frames + let bytes = session.bytes + session.lock.unlock() + let secs = max(0.001, Date.now.timeIntervalSince(startedAt)) + let window = max(0.001, secs) + log( + "STATS {\"fps\": \((Double(frames - lastFrames) / 5 * 10).rounded() / 10), " + + "\"kbps\": \(Int(Double(bytes - lastBytes) * 8 / 5 / 1000)), " + + "\"totalFrames\": \(frames), \"totalBytes\": \(bytes), " + + "\"avgKbps\": \(Int(Double(bytes) * 8 / window / 1000)), " + + "\"rssMb\": \(Int(residentBytes() / 1_048_576))}" + ) + lastFrames = frames + lastBytes = bytes + } + } +} + +// MARK: - Main + +guard let options = parseOptions(Array(CommandLine.arguments.dropFirst())) else { + log("usage: simlock-screen-capture [--codec avcc|mjpeg] [--fps N] [--bitrate BPS] [--max-dimension PX] [--duration SEC]") + exit(64) +} + +watchExitSources() + +let startedAt = Date.now +watchStats() +let capture = FrameCapture() +let encoder = H264Encoder(fps: options.fps, bitrate: options.bitrate) +let jpegEncoder = VideoEncoder(quality: 0.7) + +/// A captured frame, handed from the capture queue to the encode queue. +struct Pending: @unchecked Sendable { + let buffer: CVPixelBuffer + /// Monotonic nanoseconds (`DispatchTime.uptimeNanoseconds`), so a wall-clock jump cannot stall + /// the fps throttle. + let atNanos: UInt64 +} + +let (frames, sink) = AsyncStream.makeStream(of: Pending.self, bufferingPolicy: .bufferingNewest(1)) + +// Capture fires on SimulatorKit's render callbacks plus a 5 fps idle floor, so the cadence is the +// simulator's. Two things happen on the encode side: the fps throttle, then the encode. Dropped +// frames cost nothing — an H.264 P-frame carries the difference, so a jump from N to N+3 decodes +// as one slightly larger delta. + +/// Runs the encode loop. `newest(1)` means a slow encoder or a relay that stops draining drops +/// old frames instead of queueing them without bound, which is the behaviour a live view needs. +/// +/// The first frame forces an IDR so the viewer that just asked for the stream can start decoding +/// immediately rather than waiting for the encoder's own keyframe interval; after that +/// VideoToolbox spaces IDRs itself (`MaxKeyFrameInterval = fps * 5`, from serve-sim's encoder). +func encodeLoop() async { + let intervalNanos = UInt64(1_000_000_000 / Double(options.fps)) + var lastSent: UInt64 = 0 + var started = false + var metaEmitted = false + for await frame in frames { + if started, frame.atNanos &- lastSent < intervalNanos { continue } + lastSent = frame.atNanos + let forceKeyframe = !started + started = true + + guard let prepared = prepareForEncode(frame.buffer) else { continue } + if !metaEmitted { + metaEmitted = true + let meta: [String: Any] = [ + "udid": options.udid, + "codec": options.codec == "avcc" ? "h264" : "mjpeg", + "width": CVPixelBufferGetWidth(prepared), + "height": CVPixelBufferGetHeight(prepared), + "fps": options.fps, + "deviceSet": ProcessInfo.processInfo.environment["SIMLOCK_IOS_DEVICE_SET"] ?? "default", + ] + if let json = try? JSONSerialization.data(withJSONObject: meta), + let string = String(data: json, encoding: .utf8) { + log("ready \(string)") + emit(.meta, Data(string.utf8)) + } + } + if options.codec == "mjpeg" { + if let data = try? await jpegEncoder.encode(pixelBuffer: prepared) { + emit(.delta, data) + } + } else if let encoded = try? await encoder.encode( + prepared, + forceKeyframe: forceKeyframe + ) { + if let description = encoded.description { + emit(.description, description) + } + emit(encoded.kind == .keyframe ? .keyframe : .delta, encoded.avcc) + } + } +} + +/// Scales to the configured maximum and returns a buffer safe to encode. The encoder deep-copies +/// what it is given internally, so the recycled framebuffer IOSurface is never held. +func prepareForEncode(_ buffer: CVPixelBuffer) -> CVPixelBuffer? { + let width = CVPixelBufferGetWidth(buffer) + let height = CVPixelBufferGetHeight(buffer) + guard options.maxDimension > 0, max(width, height) > options.maxDimension else { + return buffer + } + session.lock.lock() + if session.scaler == nil { + let factor = Double(options.maxDimension) / Double(max(width, height)) + // Even dimensions: H.264 4:2:0 rejects an odd chroma plane. + let targetWidth = max(2, Int(Double(width) * factor) & ~1) + let targetHeight = max(2, Int(Double(height) * factor) & ~1) + session.scaler = Scaler(targetWidth: targetWidth, targetHeight: targetHeight) + session.scaledSize = Dimensions(width: targetWidth, height: targetHeight) + log("scaling \(width)x\(height) -> \(targetWidth)x\(targetHeight)") + } + let scaler = session.scaler + session.lock.unlock() + return scaler?.transferFrom(buffer) +} + +Task { + do { + try await capture.start(deviceUDID: options.udid) { pixelBuffer, _ in + sink.yield(Pending(buffer: pixelBuffer, atNanos: DispatchTime.now().uptimeNanoseconds)) + } + } catch { + log("start failed: \(error)") + _exit(1) + } + // The meta frame is written by the encode loop on the first frame, because that is the first + // moment the encoded geometry (post-scale) is known. + await encodeLoop() +} + +if options.duration > 0 { + try? await Task.sleep(nanoseconds: UInt64(options.duration * 1_000_000_000)) + let secs = max(0.001, Date.now.timeIntervalSince(startedAt)) + session.lock.lock() + let counted = (session.frames, session.bytes) + session.lock.unlock() + let stats: [String: Any] = [ + "seconds": (secs * 100).rounded() / 100, + "frames": counted.0, + "bytes": counted.1, + "fps": (Double(counted.0) / secs * 10).rounded() / 10, + "kbps": ((Double(counted.1) * 8 / secs) / 1000).rounded(), + "avgFrameBytes": counted.0 == 0 ? 0 : Int(Double(counted.1) / Double(counted.0)), + "rssMb": Int(residentBytes() / 1_048_576), + ] + if let json = try? JSONSerialization.data(withJSONObject: stats) { + log("RESULT \(String(data: json, encoding: .utf8)!)") + } + exit(0) +} + +dispatchMain() diff --git a/tools/screen-capture/scripts/unframe.mjs b/tools/screen-capture/scripts/unframe.mjs new file mode 100644 index 00000000..2fcc022d --- /dev/null +++ b/tools/screen-capture/scripts/unframe.mjs @@ -0,0 +1,99 @@ +// why: ffprobe reads Annex-B, not the length-prefixed envelope the capture tool writes, so proving +// a captured stream actually decodes needs something in between. Prints the per-tag frame counts +// while it is at it, because a stream that decodes but is missing its keyframe proves nothing. +// +// node scripts/unframe.mjs capture.bin out.264 +import { readFileSync, writeFileSync } from "node:fs"; + +const [input, output] = process.argv.slice(2); +if (!input || !output) { + console.error("usage: node scripts/unframe.mjs "); + process.exit(64); +} + +const TAGS = { 1: "description", 2: "keyframe", 3: "delta", 4: "seed", 5: "meta" }; +const START = Buffer.from([0, 0, 0, 1]); + +const buf = readFileSync(input); +let offset = 0; +let malformed = 0; +const counts = {}; +const parameterSets = []; +const slices = []; +let meta = null; + +while (offset + 5 <= buf.length) { + const length = buf.readUInt32BE(offset); + const tag = buf[offset + 4]; + const payload = buf.subarray(offset + 5, offset + 5 + length - 1); + if (payload.length !== length - 1) { + malformed++; + break; + } + counts[TAGS[tag] ?? `unknown(${tag})`] = (counts[TAGS[tag] ?? `unknown(${tag})`] ?? 0) + 1; + if (tag === 5) meta = JSON.parse(payload.toString("utf8")); + if (tag === 1) parameterSets.push(...extractParameterSets(payload)); + if (tag === 2 || tag === 3) slices.push(...extractNalUnits(payload)); + offset += 5 + length - 1; +} + +// why: a decoder needs SPS+PPS in-band before the first IDR, so the avcC blob's parameter sets are +// written ahead of the frames rather than left in the description frame a decoder may see too late. +writeFileSync( + output, + Buffer.concat([ + START, + parameterSets[0], + START, + parameterSets[1], + ...slices.flatMap((nal) => [START, nal]), + ]), +); + +// why: the payload is repeated 4-byte length + NAL unit, and the lengths are read rather than +// scanned for start codes -- a NAL's own bytes can look like a start code. +function extractNalUnits(payload) { + const units = []; + let i = 0; + while (i + 4 <= payload.length) { + const size = payload.readUInt32BE(i); + units.push(Buffer.from(payload.subarray(i + 4, i + 4 + size))); + i += 4 + size; + } + return units; +} + +// why: the avcC layout is fixed by ISO/IEC 14496-15 §5.2.4.1 -- ver, profile, compat, level, +// lengthSize, SPS count, SPS[], PPS count, PPS[] -- so the offsets below are the spec's, not ours. +function extractParameterSets(avcc) { + const sets = []; + let i = 5; + const spsCount = avcc[i] & 0x1f; + i += 1; + for (let s = 0; s < spsCount; s++) { + const size = avcc.readUInt16BE(i); + i += 2; + sets.push(Buffer.from(avcc.subarray(i, i + size))); + i += size; + } + const ppsCount = avcc[i]; + i += 1; + for (let s = 0; s < ppsCount; s++) { + const size = avcc.readUInt16BE(i); + i += 2; + sets.push(Buffer.from(avcc.subarray(i, i + size))); + i += size; + } + return sets; +} + +console.log( + JSON.stringify({ + meta, + counts, + malformed, + bytes: buf.length, + parameterSets: parameterSets.length, + slices: slices.length, + }), +); From ec45e94869f6d1c52752de9ee2411accd584e710 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Wed, 14 Oct 2026 22:01:56 +0200 Subject: [PATCH 02/29] feat(stream): a lease's screen, streamed to whoever is looking at it Proof of feasibility plus measured performance, not a finished feature. A leased iOS simulator's screen is captured by a Swift CLI, encoded H.264, and handed to a browser as the body of one GET /v1/stream/leases/{leaseId}. The request is the only on-command and closing it the only off-command, so nothing runs until someone asks to look and nothing runs when they stop; a lease that ends by any route stops the capture too, including under a connected viewer. No contract change and no protocol bump: the stream rides the relay an ADR 0026/0028 exchange already uses, as the second named relay target, and the envelope crosses the link unreframed. A gateway forwards a stream without parsing one byte of it. Measured on an Apple silicon Mac, 330x720 at 1500 kbps: - an untouched screen sits at the capture's 5 fps idle floor, 9-76 kbit/s - real swipes reach 18.6-23.2 fps and 880-1097 kbit/s - a relayed read costs nothing measurable over reading the tool directly - the capture process itself is 66-78 MB The capture program is serve-sim's pipeline, vendored and patched: its FrameCapture finds a device in the device set named by SIMLOCK_IOS_DEVICE_SET, where serve-sim searches only CoreSimulator's default set and simlock boots every simulator in its own `simctl --set` root. Known gaps, all deliberate at this stage: the tool is not shipped or installed, simulators only (a physical device is refused STREAM_NOT_SUPPORTED), and one unit test on the silence bound fails in roughly one run in five -- it drains the body to observe a timeout whose step the stream's own prefetch decides. --- .fallowrc.json | 5 +- docs/CONFIGURATION.md | 48 +- docs/CONSOLE.md | 18 + docs/HTTP-API.md | 51 +- docs/internal/agent-rules/toolchain.md | 23 + e2e/console/screen-capture.ts | 72 +++ e2e/console/screen.spec.ts | 245 ++++++++ src/cli/index.test.ts | 7 + src/contract/schemas.ts | 11 + src/core/cleanup/idle-destroy.test.ts | 7 + src/core/cleanup/idle-shutdown.test.ts | 7 + src/core/config.ts | 44 ++ src/core/doctor.test.ts | 7 + src/core/nuke.test.ts | 7 + src/core/reaper.test.ts | 7 + src/daemon/dispatcher.test.ts | 7 + src/daemon/main.ts | 239 ++++++- src/daemon/server.test.ts | 7 + src/http/errors.ts | 33 +- src/http/test-fakes.ts | 7 + src/leasing/acquisition-planner.test.ts | 7 + src/leasing/create-leasing.test.ts | 7 + .../lease-acquisition-coordinator.test.ts | 7 + src/leasing/lease-end-participants.test.ts | 7 + src/leasing/lease-health-monitor.test.ts | 7 + src/leasing/physical-device-watcher.test.ts | 7 + src/leasing/physical-leasing.test.ts | 7 + src/stream/caller.ts | 7 + src/stream/capture.test.ts | 269 ++++++++ src/stream/capture.ts | 211 +++++++ src/stream/captures.test.ts | 107 ++++ src/stream/captures.ts | 61 ++ src/stream/framing.test.ts | 75 +++ src/stream/framing.ts | 72 +++ src/stream/gateway-link.ts | 91 +++ src/stream/index.ts | 13 + src/stream/mounts.ts | 48 ++ src/stream/refusal.ts | 78 +++ src/stream/service.test.ts | 145 +++++ src/stream/service.ts | 101 +++ src/stream/worker-link.test.ts | 117 ++++ src/stream/worker-link.ts | 100 +++ ui/src/screen/stream.test.ts | 581 ++++++++++++++++++ ui/src/screen/stream.ts | 359 +++++++++++ ui/src/screen/viewer.test.tsx | 92 +++ ui/src/screen/viewer.tsx | 222 +++++++ ui/src/styles.css | 15 + ui/src/views/leases.tsx | 2 + 48 files changed, 3643 insertions(+), 24 deletions(-) create mode 100644 e2e/console/screen-capture.ts create mode 100644 e2e/console/screen.spec.ts create mode 100644 src/stream/caller.ts create mode 100644 src/stream/capture.test.ts create mode 100644 src/stream/capture.ts create mode 100644 src/stream/captures.test.ts create mode 100644 src/stream/captures.ts create mode 100644 src/stream/framing.test.ts create mode 100644 src/stream/framing.ts create mode 100644 src/stream/gateway-link.ts create mode 100644 src/stream/index.ts create mode 100644 src/stream/mounts.ts create mode 100644 src/stream/refusal.ts create mode 100644 src/stream/service.test.ts create mode 100644 src/stream/service.ts create mode 100644 src/stream/worker-link.test.ts create mode 100644 src/stream/worker-link.ts create mode 100644 ui/src/screen/stream.test.ts create mode 100644 ui/src/screen/stream.ts create mode 100644 ui/src/screen/viewer.test.tsx create mode 100644 ui/src/screen/viewer.tsx diff --git a/.fallowrc.json b/.fallowrc.json index 0baed307..3df4274d 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -12,6 +12,8 @@ // them (budget-guard, comment-lint) import from their neighbours (usage, comments). // .dependency-cruiser.cycles.mjs is named only by src/lint-rules.test.ts, which runs depcruise // with it (ADR 0027). + // tools/screen-capture/scripts/unframe.mjs is a diagnostic a person runs by hand against a + // captured file, the way the .agents scripts are. "entry": [ "src/cli/main.ts", "src/daemon/main.ts", @@ -20,7 +22,8 @@ "e2e/console/*.spec.ts", "vitest.mutate.config.ts", ".agents/scripts/*.mjs", - ".dependency-cruiser.cycles.mjs" + ".dependency-cruiser.cycles.mjs", + "tools/screen-capture/scripts/*.mjs" ], "dynamicallyLoaded": ["e2e/fake-driver/index.ts"], // The web console's libraries are bundled into dist/ui at build time and ship as files, so diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 5c5ec67b..db0e18c7 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -38,6 +38,11 @@ edit yourself keeps the mode you give it until the next `config set`; run | `agentDevice.enabled` | **Both sides.** On a worker: run one agent-device daemon on this worker, started and watched by Simlock. It sees Simlock's devices and no others, refuses `boot` and `shutdown`, and keeps its files in `~/.simlock/agent-device`. If agent-device is missing, older than 0.21.25, speaks another RPC protocol or needs a newer Node than Simlock runs on, it is not started and Simlock runs on without it. A daemon a killed Simlock left behind is stopped at the next start. With `http.enabled` also `true`, the agent-device CLI reaches it at `/v1/agent-device` (see [HTTP-API.md](HTTP-API.md#v1agent-device)). On a gateway: serve `/v1/agent-device` and send each agent to a worker whose agent-device is available; the gateway runs no daemon of its own. | `false` | | `agentDevice.leaseTtlCapMs` | **Both sides.** Caps the TTL of the leases the agent-device CLI makes and renews through Simlock: a longer `ttlMs` is lowered to it, and a lease that sends none gets it. A whole number of milliseconds from `5000` to `lease.maxTtlMs`; a value outside that range stops the daemon at start, on a gateway too. Used only when `agentDevice.enabled` is `true`. | unset (no cap) | | `agentDevice.command` | **Worker side.** The agent-device executable: a name looked up on `PATH`, or a path. A gateway ignores it and logs a warning. Used only when `agentDevice.enabled` is `true`. | `agent-device` | +| `stream.enabled` | **Both sides.** Offer a live view of a leased iOS simulator's screen. On a worker: answer `GET /v1/stream/leases/{leaseId}` (see [HTTP-API.md](HTTP-API.md#get-v1streamleasesleaseid)) by running the capture program for the device that lease holds. On a gateway: serve the same URL and relay each request to the worker that holds the lease. Off by default because a stream is a capture process pinned to a leased device — with it off, nothing is running until someone asks to look, and nothing is running when they stop. Read at start; changing it needs a restart. | `false` | +| `stream.command` | **Worker side.** The capture program: a name looked up on `PATH`, or a path. A gateway ignores it and logs a warning. Used only when `stream.enabled` is `true`. A worker whose program is not there reports the capability as unavailable in `simlock status` and `simlock worker list`, and refuses a request with `STREAM_UNAVAILABLE` rather than failing one silently. | `simlock-screen-capture` | +| `stream.fps` | **Worker side.** The frame rate the encoder is asked for. A capture only produces frames when the device's screen changes, so this is a ceiling and not a promise: an untouched home screen stays far below it, which is what keeps an idle stream cheap. A whole number from `1` to `60`. | `30` | +| `stream.maxDimension` | **Worker side.** Cap on the encoded picture's longest side, in pixels; the other side follows the device's aspect ratio. This is the lever that costs the most bandwidth — a device that encodes at about 6 Mbit/s native is nearer 1.4 Mbit/s at `720`. A whole number from `96` to `4096`. | `720` | +| `stream.bitrateKbps` | **Worker side.** Target bitrate in kilobits per second, handed to the encoder as an average rather than a ceiling; a frame that needs more takes it. A whole number from `64` to `40000`. | `1500` | | `exec.timeoutMs` | **Worker side.** How long one `device.exec` command (`simlock simctl` / `simlock adb` against a gateway or over HTTP) may run before the worker kills it and the operation fails with `EXEC_TIMEOUT`. Authoritative: it bounds the process that actually runs. | `10 minutes` | | `gateway.routing` | **Gateway side.** Which routing policy places a queued request on a worker. `warm-then-free` is the only policy in v1. Among the workers that can serve the request, in order: a worker whose health is not `running`, or that has requests of its own waiting, is left out; a worker holding a ready device that fits goes first (the device's mode must fit the request, a request with no mode fits a device that serves the worker's default mode, and its runtime must be the one requested or, with none requested, the one the worker would pick: the newest runtime the model pairs with); otherwise a worker with no free running slot for the platform or overall is left out (a running device that is not leased counts as free: the worker shuts it down to make room), a worker under its RAM budget for the platform is preferred over one at it (a worker at its budget is still asked when it is the only one left), and the one with the most free running capacity for the platform wins. A worker that is left out is busy, not unable: the request waits for it. Ties go to the lowest worker id. | `warm-then-free` | | `gateway.disconnectedRetentionMs` | **Gateway side.** How long a disconnected worker is kept (greyed, never dispatched to) before the gateway forgets it. The clock is held while the gateway still knows of gateway-issued leases on that worker, and that hold ends when the last of those leases passes its deadline. | `24 hours` | @@ -160,13 +165,16 @@ local agents whether or not its gateway is reachable, and `http.enabled` stays off by default. **A gateway reads a deliberately small slice of this file**: `mode`, `http.*`, -`log.*`, `lease.*`, `eventBuffer.*`, `eventLog.*`, `gateway.*` and -`agentDevice.*` (`enabled` and `leaseTtlCapMs` only). Every other key — -capacity, drivers, downloads, idle, warmPool, health, ios, android, stalledTransition — +`log.*`, `lease.*`, `eventBuffer.*`, `eventLog.*`, `gateway.*`, `agentDevice.*` +(`enabled` and `leaseTtlCapMs` only) and `stream.*` (`enabled` only). Every +other key — capacity, drivers, downloads, idle, warmPool, health, ios, android, +stalledTransition — configures devices, which a gateway does not have; each one present in a gateway's config is reported with a warning and ignored, the same treatment an -unknown key gets. The worker-side `gateway.url`/`token`/`label` and -`agentDevice.command` are warned about there too. Nothing is stripped: the same file can be copied between a +unknown key gets. The worker-side `gateway.url`/`token`/`label`, +`agentDevice.command` and the `stream.command`/`fps`/`maxDimension`/`bitrateKbps` +that configure a capture a gateway never run are warned about there too. +Nothing is stripped: the same file can be copied between a worker and a gateway with only `mode` differing, and the warning is what makes the difference visible rather than silent. @@ -567,6 +575,36 @@ Only these four keys exist. There is no way to pass other emulator arguments from config, so nothing here can move an emulator's port or AVD home. +## Screen streaming + +`stream.enabled` turns a leased device's screen into something you can look at: +a worker runs a capture program for the device a lease holds, encodes its +screen as H.264, and the bytes reach a browser as the body of one ordinary +`GET /v1/stream/leases/{leaseId}` (see +[HTTP-API.md](HTTP-API.md#get-v1streamleasesleaseid)). On a gateway the same URL +relays the request to the worker that holds the lease, and the gateway forwards +the bytes without reading one of them. + +Nothing is captured until someone asks to look, and asking is the only +on-command there is: the request itself keeps the capture running, and closing +it — pressing Stop, navigating away, closing the tab — is the off-command. A +lease that ends by any other route stops the capture too, so a device never +goes on being filmed after it has been handed back, and no stream outlives a +viewer or a lease. + +What it costs is mostly `stream.maxDimension` and `stream.bitrateKbps`. Against +a device whose screen is being actively redrawn on an Apple silicon Mac, the +encoder's native settings produce about 6 Mbit/s; at `maxDimension` `720` and +`bitrateKbps` `1500` the same motion is about 1.4 Mbit/s, and a device nobody is +touching sits between 25 and 90 kbit/s because a screen that has not changed +sends almost nothing. `stream.fps` is a ceiling rather than a rate for the same +reason. The capture process itself costs about 50-80 MB of memory per device +being watched. + +Only a simulator's screen can be streamed. A lease on an Android emulator or a +physical iOS device is refused with `STREAM_NOT_SUPPORTED`, naming that as the +reason rather than failing for one the reader has to guess. + ## Capacity strategies How many devices Simlock lets exist and run at once is decided by a capacity diff --git a/docs/CONSOLE.md b/docs/CONSOLE.md index e3cf328c..9e1001f7 100644 --- a/docs/CONSOLE.md +++ b/docs/CONSOLE.md @@ -212,6 +212,24 @@ and the id of the request that was granted the lease, while the daemon still keeps it. A lease that has been released or has expired says "Lease not found". +### Watching a lease's screen + +A lease's page ends with a **Screen** panel: one button, **Show screen**, and +while it is on, the device's picture with the facts beside it — its size and +codec, the frames painted, the rate they arrive at, the bandwidth they cost, and +how many were dropped rather than drawn behind an earlier one. + +Nothing runs while it is off, and the button is the only control. Pressing +**Stop**, navigating away, closing the tab and signing out all stop the capture +on the worker the same way, and a lease that ends — released, expired, or lost +its device — ends the picture with it rather than leaving a frame that looks +live on the page. What you are looking at is either moving or gone. + +The panel is there wherever the worker it runs on has screen streaming turned on +(see [`stream.enabled`](CONFIGURATION.md)). Where it is off, or where the lease is +on a device a screen cannot be captured from — an Android emulator or a physical +iPhone — the panel says so, in the worker's own words, and starts nothing. + ### Who holds a lease A lease taken over the HTTP API belongs to the token that took it. The console diff --git a/docs/HTTP-API.md b/docs/HTTP-API.md index d3e07a56..8ca5a804 100644 --- a/docs/HTTP-API.md +++ b/docs/HTTP-API.md @@ -903,6 +903,55 @@ Paths in `args` resolve on the daemon's filesystem that path to exist *there*). Getting a file onto that machine is not part of this API in this version — see [Not implemented](#not-implemented). +### `GET /v1/stream/leases/{leaseId}` + +Streams the screen of the device one lease holds, for as long as the +connection stays open. Offered only where `stream.enabled` is `true` (see +[CONFIGURATION.md](CONFIGURATION.md)); a daemon without it never mounts this +route and answers it like any path it has not heard of. + +The response has no declared length and does not end on its own. Its headers +describe the picture before a byte of it is read: + +| Header | | +|---|---| +| `content-type` | `application/vnd.simlock.stream+h264`, or `+mjpeg` | +| `simlock-stream-codec` | `h264` or `mjpeg` | +| `simlock-stream-width` / `-height` | The encoded picture's size in pixels | +| `simlock-stream-fps` | The frame rate the encoder was asked for | +| `x-simlock-lease` | The lease id this stream is | + +The body is a sequence of frames, each one `4-byte big-endian length`, then a +1-byte tag, then the payload. The length covers the tag byte. Tag `0x05` is a +JSON frame naming the device's `codec`, `width`, `height` and `fps`, and it is +always the first frame; `0x01` is the codec's parameter sets, `0x02` a key +frame, `0x03` a delta. A key frame's and a delta's payload is length-prefixed +AVCC NAL data, which is what a browser's `VideoDecoder` consumes directly, and +the `0x01` payload is an `avcC` blob for its `configure` call — the bytes a +capture wrote are the bytes a reader gets, re-framed by nothing on the way. + +**Closing the request is the only stop-command.** There is no stop endpoint and +no stop frame: holding the connection open is what keeps the capture running, +and a client that goes away — aborting the fetch, closing the tab, losing the +network — ends it. A lease ending by any route stops it too, including while a +viewer is still connected, so a stream cannot outlive the lease that authorises +it. Nothing is captured before a request arrives, which is why this route is +worth asking for twice rather than holding open in advance. + +Authorization is the same as every other lease route: a token sees its own +leases, an operator sees any, and anything else is a `404`. `STREAM_NOT_SUPPORTED` +(415) means the device behind the lease can never produce a picture here — a +physical iOS device or an Android emulator. `STREAM_UNAVAILABLE` (503) means it +could but cannot now, usually a capture program that is not installed; asking +again after its operator installs it is the right move. `WORKER_UNREACHABLE` on +a gateway means the worker that holds the lease is gone. + +The body is the same bytes on a gateway as on the worker: a gateway relays a +stream rather than reading one, so a client pointed at a gateway needs no second +code path. A refusal, by contrast, keeps the shape every other route uses and +the worker's own code — a viewer told `STREAM_NOT_SUPPORTED` by a gateway +learned that from the machine that looked at the device. + ### `POST /v1/components/install` Role: `operator`. Installs one iOS simulator runtime or Android system image @@ -1692,7 +1741,7 @@ Every failure is the same shape the daemon protocol uses: | 400 | `BAD_REQUEST` (malformed body, bad query param, validation) | | 401 | `UNAUTHENTICATED` (missing or unrecognized token) | | 403 | `FORBIDDEN` (role doesn't permit the route — including a `worker` token on any `/v1` route other than `/v1/uplink`, and an `agent`/`operator` token at `/v1/uplink`; a `/v1/lease-requests/*` route whose request another token sent; or `POST /v1/leases/{id}/renew`/`DELETE /v1/leases/{id}`/`POST /v1/leases/{id}/exec` naming another requester's still-live lease), `DOWNLOADS_DISABLED` (`POST /v1/components/install` under `downloads.policy: "never"`) | -| 404 | `UNKNOWN_WORKER` (`POST`/`DELETE /v1/workers/{id}/drain` naming a worker the gateway does not know), `UNKNOWN_LEASE_REQUEST` (unknown request id), `UNKNOWN_LEASE` (unknown lease id, expired/released, **or `GET /v1/leases/{id}`/`GET /v1/leases/{id}/events` naming another requester's lease** — see [`GET /v1/leases/{id}`](#get-v1leasesid)) , `UNKNOWN_DEVICE` (`DELETE /v1/devices/{id}` naming a device that is not enrolled) | +| 404 | `UNKNOWN_WORKER` (`POST`/`DELETE /v1/workers/{id}/drain` naming a worker the gateway does not know), `UNKNOWN_LEASE_REQUEST` (unknown request id), `UNKNOWN_LEASE` (unknown lease id, expired/released, **or `GET /v1/leases/{id}`/`GET /v1/leases/{id}/events` naming another requester's lease** — see [`GET /v1/leases/{id}`](#get-v1leasesid); also `GET /v1/stream/leases/{leaseId}` naming a lease unknown, released, or another requester's — a stream reveals no lease that asking could not already list), `UNKNOWN_DEVICE` (`DELETE /v1/devices/{id}` naming a device that is not enrolled) | | 409 | `REQUESTER_ALREADY_LEASED` (body names the existing lease id; fleet-wide on a gateway), `LEASE_ID_TAKEN` (a `leaseId` an active lease or a waiting request holds; body carries `leaseId`; on a gateway, one of the gateway's own leases or requests, or a worker that refused it), `IDEMPOTENCY_CONFLICT` (an `Idempotency-Key` repeated with a different device or `leaseId`), `REQUEST_NOT_CANCELLABLE` (body names the lease id if the request had already been granted), `WORKER_CONNECTED` (`DELETE /v1/workers/{id}` while its uplink is open), `COMPONENT_NOT_OWNED`, `COMPONENT_IN_USE` (body carries `devices` and `foreignDevices`), `COMPONENT_BUSY` (the three refusals of `DELETE /v1/components/{platform}/{version}`), `DEVICE_BUSY` (`POST /v1/devices`, `DELETE /v1/devices/{id}` of a leased, reclaiming or held device; body carries `id` and `reason`) | | 422 | `UNKNOWN_MODEL`, `RUNTIME_MISSING`, `NO_DRIVER`, `CAPABILITY_MISSING` (a request required capabilities no worker able to serve it has available; body carries `capabilities`), `DEVICE_NOT_ENROLLABLE` (`POST /v1/devices`; body carries `id`, `platform`, `reason`, and `canonicalId` when known), `PASSTHROUGH_REFUSED` (a refused `exec` verb or `devicectl` command, a caller-supplied `--set`/`-P`, a bare `adb shell`), `UNKNOWN_PASSTHROUGH_TOOL` | | 501 | `UNSUPPORTED_IN_GATEWAY_MODE` (an operation that acts on one machine, asked of a gateway: `POST /v1/components/install`, `GET /v1/components`, `DELETE /v1/components/{platform}/{version}`, `POST /v1/devices`, `DELETE /v1/devices/{id}`), `UNSUPPORTED_IN_WORKER_MODE` (an operation on a gateway's workers, asked of a single host: `POST`/`DELETE /v1/workers/{id}/drain`, `DELETE /v1/workers/{id}`, `POST /v1/components/install` with `workers`) | diff --git a/docs/internal/agent-rules/toolchain.md b/docs/internal/agent-rules/toolchain.md index 2634db0d..d5b563c1 100644 --- a/docs/internal/agent-rules/toolchain.md +++ b/docs/internal/agent-rules/toolchain.md @@ -60,6 +60,29 @@ that goes through the reviews its diff's kind gets and the merge gate: | Comment gardening | `garden-comments` | weekly | | Review calibration | `calibrate-reviews` | weekly | +## The screen capture program + +`stream.command` names a program this repo does not ship. The one it is written +against lives in `tools/screen-capture/` — a Swift package, so nothing in +`pnpm build` or `pnpm test` builds or runs it: + +```sh +cd tools/screen-capture && swift build -c release --scratch-path .build/out +# the binary: .build/out/Products/Release/simlock-screen-capture +``` + +Point `stream.command` at that path (an absolute path is checked for existence +per request, so installing it needs no daemon restart). + +It cannot run in the unit, e2e or console lanes: it reads a booted simulator's +framebuffer, which means real devices and the slow lane. What the lanes do cover +is everything this repo writes — the envelope reader, the refusal paths, the +lifecycle — against a program that writes the same envelope from stills +(`e2e/console/screen-capture.ts`). A change to the Swift program is checked the +way the slow lane checks anything else: by hand, on a machine with a simulator, +where `node tools/screen-capture/scripts/unframe.mjs out.264` says +whether the bytes it wrote decode. + ## Slow lane Tests that start real simulators or emulators. Run them only through diff --git a/e2e/console/screen-capture.ts b/e2e/console/screen-capture.ts new file mode 100644 index 00000000..c8b44388 --- /dev/null +++ b/e2e/console/screen-capture.ts @@ -0,0 +1,72 @@ +import { chmod, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +// why: the browser lane needs a capture program to point a daemon's `stream.command` at, and the real +// one is Swift, needs a booted simulator, and cannot run on a CI runner. This writes the same envelope +// that tool writes -- a meta frame naming `mjpeg`, then one JPEG per delta frame -- from a process that +// owes its life to nothing but the daemon that started it. Stills rather than H.264 so the lane decodes +// in every browser, WebCodecs being Chromium's for now; the H.264 path is pinned by unit tests. + +// why: three solid-colour stills -- red, lime, blue, made by +// `ffmpeg -f lavfi -i color=red:s=120x240 -frames:v 1 -q:v 30 f.jpg` -- held as base64 so the program +// below turns them back into bytes with no fixture file to ship. They differ because the lane asserts +// the pane shows a picture that *changes*, and one colour painted twice is not a change. +const STILLS = [ + "/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYxLjE5LjEwMAD/2wBDAAg8PEc8R1JSUlJSUmFaYWVlZWFhYWFlZWVsbGx/f39sbGxlZWxseHh/f4qOioODf4OOjpaWlrS0rKzS0tn/////xABNAAEBAAAAAAAAAAAAAAAAAAAABgEBAQEAAAAAAAAAAAAAAAAAAAYHEAEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/8AAEQgA8AB4AwEiAAIRAAMRAP/aAAwDAQACEQMRAD8AiwEo38AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/9k=", + "/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYxLjE5LjEwMAD/2wBDAAg8PEc8R1JSUlJSUmFaYWVlZWFhYWFlZWVsbGx/f39sbGxlZWxseHh/f4qOioODf4OOjpaWlrS0rKzS0tn/////xABMAAEBAAAAAAAAAAAAAAAAAAAABgEBAQAAAAAAAAAAAAAAAAAAAAYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/wAARCADwAHgDASIAAhEAAxEA/9oADAMBAAIRAxEAPwCLASqJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/2Q==", + "/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYxLjE5LjEwMAD/2wBDAAg8PEc8R1JSUlJSUmFaYWVlZWFhYWFlZWVsbGx/f39sbGxlZWxseHh/f4qOioODf4OOjpaWlrS0rKzS0tn/////xABNAAEBAAAAAAAAAAAAAAAAAAAABwEBAQEAAAAAAAAAAAAAAAAAAAUHEAEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/8AAEQgA8AB4AwEiAAIRAAMRAP/aAAwDAQACEQMRAD8AjgDf0sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/9k=", +]; + +export async function writeCaptureProgram(dir: string): Promise { + const path = join(dir, "mjpeg-capture.mjs"); + await writeFile( + path, + `#!/usr/bin/env node +// why: the lane's stand-in for simlock's screen capture program. It writes the same envelope, exits +// when the daemon closes its stdin, and logs when it was started and when it went away -- which is how +// the lane proves a viewer leaving or a lease ending stops a capture rather than only a picture. +import { appendFileSync } from "node:fs"; + +const LOG = ${JSON.stringify(join(dir, "capture.log"))}; +const args = process.argv.slice(2); +const address = args.at(-1); +const stills = ${JSON.stringify(STILLS)}.map((text) => Buffer.from(text, "base64")); + +function envelope(tag, payload) { + const head = Buffer.alloc(5); + head.writeUInt32BE(payload.length + 1, 0); + head[4] = tag; + return Buffer.concat([head, payload]); +} + +const meta = Buffer.from( + JSON.stringify({ udid: address, codec: "mjpeg", width: 120, height: 240, fps: 5, deviceSet: "lane" }), +); + +appendFileSync(LOG, \`opened \${JSON.stringify(args)}\\n\`); +process.stdout.write(envelope(0x05, meta)); + +let next = 0; +const timer = setInterval(() => { + process.stdout.write(envelope(0x03, stills[next++ % stills.length])); +}, 100); + +// why: stdin is the liveness handle the real tool documents, so an "end" on it is the one stop a +// daemon gives a capture, and the line below is the lane's evidence that the request was what held it. +process.stdin.on("end", () => { + appendFileSync(LOG, "closed\\n"); + clearInterval(timer); + process.exit(0); +}); +process.stdin.resume(); +process.stdout.on("error", () => process.exit(0)); +`, + ); + await chmod(path, 0o755); + return path; +} + +export async function captureLog(dir: string): Promise { + const { readFile } = await import("node:fs/promises"); + return readFile(join(dir, "capture.log"), "utf8").catch(() => ""); +} diff --git a/e2e/console/screen.spec.ts b/e2e/console/screen.spec.ts new file mode 100644 index 00000000..56a063c3 --- /dev/null +++ b/e2e/console/screen.spec.ts @@ -0,0 +1,245 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import type { Page } from "@playwright/test"; + +import { expect, type RunningDaemon, signIn, startDaemon, test } from "./fixtures.js"; +import { captureLog, writeCaptureProgram } from "./screen-capture.js"; + +// why: the capture program this lane writes is its witness as well as its picture source -- it records +// when the daemon started it and when it went away. That is what lets these tests say a viewer opening +// a pane *started a capture on the worker* and that the same viewer leaving stopped one, which is the +// one promise this surface makes and a screenshot cannot prove. + +const IOS = { availableOsVersions: ["18.4"], knownModels: ["iPhone 16"] }; +const LEASE = ["lease", "--platform", "ios", "--device", "iPhone 16", "--os", "18.4"] as const; + +interface StreamingHost { + readonly command: string; + readonly dir: string; + readonly host: RunningDaemon; + readonly lease: string; +} + +async function streamingHost(): Promise { + const dir = await mkdtemp(join(tmpdir(), "simlock-screen-")); + const command = await writeCaptureProgram(dir); + let host: RunningDaemon | undefined; + try { + host = await startDaemon("worker", { + config: { + stream: { command, enabled: true, fps: 5 }, + }, + driverScript: { ios: IOS }, + }); + const granted = await host.cli([...LEASE, "--agent-id", "console-screen", "--detach"]); + expect(granted.code, granted.stderr).toBe(0); + return { + command, + dir, + host, + lease: (granted.json as { lease: { id: string } }).lease.id, + }; + } catch (error) { + await rm(dir, { force: true, recursive: true }); + await host?.dispose(); + throw error; + } +} + +async function cleanup(started: StreamingHost): Promise { + await started.host.dispose(); + await rm(started.dir, { force: true, recursive: true }); +} + +async function openLeasePage(page: Page, host: RunningDaemon, lease: string): Promise { + await page.goto(new URL(`/leases/${encodeURIComponent(lease)}`, host.url).toString()); + await signIn(page, host.tokens.operator); + await expect(page.getByRole("heading", { level: 2, name: "Screen" })).toBeVisible(); +} + +function showButton(page: Page) { + return page.getByRole("button", { name: /^(Show screen|Stop)$/ }); +} + +function screen(page: Page) { + return page.getByLabel("The leased device's screen"); +} + +// why: the mean colour of what the canvas holds, so "the picture changed" is a number rather than a +// hope -- three solid colours that alternate mean three distinct readings, and two equal ones mean a +// canvas painted once and left alone. +async function averageColour(page: Page): Promise { + const rgb = await screen(page).evaluate((element) => { + const canvas = element as HTMLCanvasElement; + const image = canvas.getContext("2d")?.getImageData(0, 0, canvas.width, canvas.height); + if (image === undefined) throw new Error("the screen canvas has no 2d context"); + const data = image.data; + const totals = [0, 0, 0]; + for (let i = 0; i < data.length; i += 4) { + totals[0] += data[i]; + totals[1] += data[i + 1]; + totals[2] += data[i + 2]; + } + const count = data.length / 4; + return totals.map((total) => Math.round(total / count)); + }); + return rgb.join(","); +} + +async function fact(page: Page, label: string): Promise { + return page + .locator(".facts > div") + .filter({ has: page.getByText(label, { exact: true }) }) + .first() + .getByRole("definition") + .innerText(); +} + +async function frames(page: Page): Promise { + const shown = await fact(page, "Frames"); + const parsed = Number.parseInt(shown, 10); + return Number.isNaN(parsed) ? 0 : parsed; +} + +async function captureLogLines(dir: string, word: string): Promise { + const log = await captureLog(dir); + return log.split("\n").filter((line) => line.startsWith(word)).length; +} + +test.describe("the Screen pane", () => { + test("shows nothing and starts no capture until asked", async ({ page }) => { + const started = await streamingHost(); + try { + await openLeasePage(page, started.host, started.lease); + await expect(screen(page)).toHaveCount(0); + await expect(page.getByText("No picture while this is off.")).toBeVisible(); + await expect.poll(() => fact(page, "Picture")).toBe("—"); + await expect.poll(() => fact(page, "State")).toBe("off"); + expect(await captureLogLines(started.dir, "opened")).toBe(0); + } finally { + await cleanup(started); + } + }); + + test("Show screen starts the capture, paints it, and reports what it is showing", async ({ + page, + }) => { + const started = await streamingHost(); + try { + await openLeasePage(page, started.host, started.lease); + await showButton(page).click(); + await expect(screen(page)).toBeVisible(); + await logReached(started.dir, "opened", 1); + + // why: these numbers are the capture program's own, so the pane sizing itself from them proves it + // read the meta frame rather than guessing a size from whatever arrived first. + await expect.poll(() => fact(page, "Picture"), { timeout: 10_000 }).toBe("120 × 240 mjpeg"); + await expect + .poll(async () => (await frames(page)) > 0, { timeout: 10_000 }) + .toBe(true); + await expect.poll(() => fact(page, "State"), { timeout: 10_000 }).toBe("live"); + + // why: three different colours arrive in turn, so readings that keep differing prove frames are + // still arriving rather than one picture painted once and left on the canvas. + const seen = new Set(); + await expect + .poll( + async () => { + seen.add(await averageColour(page)); + return seen.size; + }, + { timeout: 10_000 }, + ) + .toBeGreaterThanOrEqual(2); + } finally { + await cleanup(started); + } + }); + + test("Stop stops the capture, and so does leaving the page", async ({ page }) => { + const started = await streamingHost(); + try { + await openLeasePage(page, started.host, started.lease); + await showButton(page).click(); + await logReached(started.dir, "opened", 1); + await expect + .poll(async () => (await frames(page)) > 0, { timeout: 10_000 }) + .toBe(true); + + await showButton(page).click(); + await logReached(started.dir, "closed", 1); + await expect(screen(page)).toHaveCount(0); + await expect(showButton(page)).toHaveText("Show screen"); + + await showButton(page).click(); + await logReached(started.dir, "opened", 2); + await page.goto(new URL("/leases", started.host.url).toString()); + await logReached(started.dir, "closed", 2); + + // why: arriving back at the lease page must not resume a picture nobody asked for -- a third + // "opened" here is a capture running for a viewer that only opened a page. + await page.goto( + new URL(`/leases/${encodeURIComponent(started.lease)}`, started.host.url).toString(), + ); + await expect(showButton(page)).toHaveText("Show screen"); + expect(await captureLogLines(started.dir, "opened")).toBe(2); + } finally { + await cleanup(started); + } + }); + + test("a worker with streaming off refuses the picture and says what to do", async ({ page }) => { + const dir = await mkdtemp(join(tmpdir(), "simlock-screen-")); + let host: RunningDaemon | undefined; + try { + host = await startDaemon("worker", { driverScript: { ios: IOS } }); + const granted = await host.cli([...LEASE, "--agent-id", "console-screen", "--detach"]); + expect(granted.code, granted.stderr).toBe(0); + const lease = (granted.json as { lease: { id: string } }).lease.id; + await openLeasePage(page, host, lease); + await showButton(page).click(); + await expect(page.getByRole("alert")).toContainText("stream.enabled is on"); + await expect(screen(page)).toHaveCount(0); + expect(await captureLog(dir)).toBe(""); + } finally { + await host?.dispose(); + await rm(dir, { force: true, recursive: true }); + } + }); + + test("a release takes the pane away and leaves no capture running", async ({ page }) => { + const started = await streamingHost(); + try { + await openLeasePage(page, started.host, started.lease); + await showButton(page).click(); + await logReached(started.dir, "opened", 1); + await expect + .poll(async () => (await frames(page)) > 0, { timeout: 10_000 }) + .toBe(true); + + const released = await started.host.cli(["release", started.lease]); + expect(released.code, released.stderr).toBe(0); + + // why: a lease that is gone has no page to watch, so the pane goes with it and its abort stops the + // capture. What this proves is the promise an operator can see: leaving a lease behind never + // leaves a device being filmed. The guarantee that a lease ending stops a capture whose viewer is + // still connected is not observable from a browser -- it is pinned in + // `src/stream/captures.test.ts`, where a capture can be held open past the lease. + await logReached(started.dir, "closed", 1); + await expect(page.getByText("Lease not found")).toBeVisible(); + expect(await captureLogLines(started.dir, "opened")).toBe(1); + } finally { + await cleanup(started); + } + }); +}); + +async function logReached(dir: string, word: string, count: number): Promise { + // why: a capture stopping is the daemon's act, not the browser's, and it is the fact under test -- so + // a lane that did not wait for it would pass with a capture that never started or never stopped. + await expect + .poll(async () => (await captureLogLines(dir, word)) >= count, { timeout: 10_000 }) + .toBe(true); +} diff --git a/src/cli/index.test.ts b/src/cli/index.test.ts index f153d65e..94cc2b89 100644 --- a/src/cli/index.test.ts +++ b/src/cli/index.test.ts @@ -6188,6 +6188,13 @@ function testConfig(): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/contract/schemas.ts b/src/contract/schemas.ts index 965906a3..6e560b8f 100644 --- a/src/contract/schemas.ts +++ b/src/contract/schemas.ts @@ -904,6 +904,17 @@ export const configSchema = z.object({ command: z.string(), leaseTtlCapMs: z.number().optional(), }), + // why: optional because a worker older than the stream route answers `config.get` with no `stream` + // at all, and a gateway reading one must not fail to read its config over a feature it does not have. + stream: z + .object({ + enabled: z.boolean(), + command: z.string(), + fps: z.number(), + maxDimension: z.number(), + bitrateKbps: z.number(), + }) + .optional(), health: z.object({ enabled: z.boolean(), probeIntervalMs: z.number(), diff --git a/src/core/cleanup/idle-destroy.test.ts b/src/core/cleanup/idle-destroy.test.ts index 260b3071..49d318d4 100644 --- a/src/core/cleanup/idle-destroy.test.ts +++ b/src/core/cleanup/idle-destroy.test.ts @@ -8,6 +8,13 @@ const gibibyte = 1024 ** 3; const config: Config = { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 0 }, gateway: { diff --git a/src/core/cleanup/idle-shutdown.test.ts b/src/core/cleanup/idle-shutdown.test.ts index 911c9138..d17198a4 100644 --- a/src/core/cleanup/idle-shutdown.test.ts +++ b/src/core/cleanup/idle-shutdown.test.ts @@ -6,6 +6,13 @@ import { idleShutdownRule } from "./idle-shutdown.js"; const config: Config = { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 0 }, gateway: { diff --git a/src/core/config.ts b/src/core/config.ts index b3a6a3be..183b08b4 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -255,6 +255,17 @@ export interface Config { readonly command: string; readonly leaseTtlCapMs?: number; }; + readonly stream: { + // why: off by default, because a stream is a capture process pinned to a leased simulator and + // nothing should start one for a machine nobody is looking at. A gateway reads `enabled` alone: + // it answers a stream by relaying it, so the program and its picture settings belong to the + // worker that runs the capture. + readonly enabled: boolean; + readonly command: string; + readonly fps: number; + readonly maxDimension: number; + readonly bitrateKbps: number; + }; /** * ADR 0005 §3/§6. Both sides of the fleet live under one key, and which half is read depends * on `mode`: @@ -483,6 +494,7 @@ const GATEWAY_CONFIG_KEYS: readonly string[] = [ "eventLog", "gateway", "agentDevice", + "stream", ]; /** The `gateway.*` sub-keys a *worker* reads. A gateway ignores these three (it dials nobody); @@ -493,6 +505,15 @@ const WORKER_ONLY_GATEWAY_KEYS: readonly string[] = ["url", "token", "label"]; // why: `command` names the program a worker runs, and a gateway runs none. const WORKER_ONLY_AGENT_DEVICE_KEYS: readonly string[] = ["command"]; +// why: the capture program and its picture settings are what a worker runs and encodes with; a +// gateway relays a stream and never reads a video byte, so it has nothing to do with them. +const WORKER_ONLY_STREAM_KEYS: readonly string[] = [ + "bitrateKbps", + "command", + "fps", + "maxDimension", +]; + /** * ADR 0005 §2: "worker-only keys in a gateway's config are ignored with a warning, as unknown * keys are today". Warned per key the operator actually wrote (the file and override layers), @@ -528,6 +549,15 @@ function warnWorkerOnlyKeys(layers: readonly unknown[], warn: Warn): void { `Ignoring "agentDevice.${key}": it names the program a worker runs, ` + `and this daemon runs in gateway mode.`, ); + warnWorkerOnlySubKeys( + object, + "stream", + WORKER_ONLY_STREAM_KEYS, + warn, + (key) => + `Ignoring "stream.${key}": it configures the capture a worker runs, ` + + `and a gateway only relays a stream.`, + ); } } @@ -761,6 +791,13 @@ function defaultConfig( }, }, agentDevice: { enabled: false, command: "agent-device" }, + stream: { + enabled: false, + command: "simlock-screen-capture", + fps: 30, + maxDimension: 720, + bitrateKbps: 1_500, + }, gateway: { disconnectedRetentionMs: DEFAULT_DISCONNECTED_RETENTION_MS, execTimeoutMs: DEFAULT_GATEWAY_EXEC_TIMEOUT_MS, @@ -918,6 +955,13 @@ function configValidators(strategy: CapacityStrategyName): Record return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 1 }, gateway: { diff --git a/src/core/nuke.test.ts b/src/core/nuke.test.ts index b13333d4..fa4b0729 100644 --- a/src/core/nuke.test.ts +++ b/src/core/nuke.test.ts @@ -158,6 +158,13 @@ function config(): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 1 }, gateway: { diff --git a/src/core/reaper.test.ts b/src/core/reaper.test.ts index 665184ac..7a4a06cb 100644 --- a/src/core/reaper.test.ts +++ b/src/core/reaper.test.ts @@ -54,6 +54,13 @@ function config(): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/daemon/dispatcher.test.ts b/src/daemon/dispatcher.test.ts index f61ef665..0b42ea53 100644 --- a/src/daemon/dispatcher.test.ts +++ b/src/daemon/dispatcher.test.ts @@ -3581,6 +3581,13 @@ function testConfig( return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, gateway: { disconnectedRetentionMs: 24 * 60 * 60_000, execTimeoutMs: 11 * 60_000, diff --git a/src/daemon/main.ts b/src/daemon/main.ts index daaba518..b43eda8b 100644 --- a/src/daemon/main.ts +++ b/src/daemon/main.ts @@ -46,7 +46,18 @@ import { Nuke, SerializedDecision, } from "../core/index.js"; -import { createLeasing } from "../leasing/index.js"; +import { createLeasing, type LeaseEndParticipant } from "../leasing/index.js"; +import { + createCaptureRegistry, + createGatewayStreamLink, + createStreamLeaseEnd, + createStreamMount, + createStreamRelayTarget, + createWorkerStreamLink, + STREAM_TARGET, + type StreamLink, + type StreamRegistry, +} from "../stream/index.js"; import { AdbServerUnavailableError, AndroidDriver, @@ -63,6 +74,7 @@ import { iosPrerequisites, } from "../drivers/ios/index.js"; import { createHttpApp } from "../http/app.js"; +import type { HttpMount } from "../http/app.js"; import type { HttpDispatch } from "../http/dispatcher-session.js"; import { HttpGateway } from "../http/server.js"; import { TokenStore } from "../http/token-store.js"; @@ -359,6 +371,20 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise driver.platform), ) : undefined; + const stream = config.stream.enabled + ? createStreamParts({ + args: streamCaptureArgs(config), + command: config.stream.command, + drivers, + filesystem, + leaseEnvironment: Object.assign( + {}, + ...drivers.map((driver) => driver.virtualLeaseEnvironment()), + ), + logger: logger.child("stream"), + registry, + }) + : undefined; // Builds leasing on top of core (ADR 0018 §2). const leasing = createLeasing({ clock, @@ -369,7 +395,10 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise ({ state: "off" }) } : agentDeviceCapabilitySource(agentDeviceParts), + ...(stream === undefined ? [] : [stream.capabilitySource]), ], capacity: core.capacityReader, warmPool: core.warmPoolReader, @@ -523,6 +553,7 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise leasing.corePorts.notifyAvailability()), @@ -628,8 +659,8 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise Promise) | undefined; let resolveHttpStarted: (() => void) | undefined; @@ -946,8 +986,8 @@ async function startGatewayDaemon(options: GatewayDaemonOptions): Promise>; -// why: the agent-device edge imports nothing from the gateway, so this is the one place the gateway's parts become its port. +// why: all four sit on one link, so a stream answered over a worker's own HTTP surface and a stream +// answered for a gateway go through the same lease check and the same capture -- one place enforces a +// rule -- and the lease that ends has one registry to stop, whichever door the picture came in by. +interface StreamParts { + readonly capabilitySource: { + readonly name: string; + readonly read: () => Promise<{ + readonly reason?: string; + readonly state: "available" | "off" | "unavailable"; + }>; + }; + readonly leaseEnd: LeaseEndParticipant; + readonly link: StreamLink; + readonly mount: HttpMount; + readonly relayTarget: ( + request: Request, + tenant: string, + leaseId: string | undefined, + ) => Promise; +} + +function createStreamParts(options: { + readonly args: readonly string[]; + readonly command: string; + readonly drivers: readonly Driver[]; + readonly filesystem: Filesystem; + readonly leaseEnvironment: Readonly>; + readonly logger: Logger; + readonly registry: { + readonly snapshot: { + readonly devices: readonly { + readonly address?: string; + readonly id: string; + readonly physical: boolean; + readonly spec: { readonly platform: string }; + }[]; + readonly leases: readonly { + readonly deviceId: string; + readonly id: string; + readonly ownerId: string; + readonly requesterId: string; + }[]; + }; + }; +}): StreamParts { + // why: only a command that spells a path can be checked; a bare program name is resolved by the + // OS at spawn, and a failure there is reported with the spawn's own words rather than guessed at. + const namesAPath = options.command.includes("/"); + const available = namesAPath ? () => options.filesystem.exists(options.command) : undefined; + const captures = createCaptureRegistry(); + const link = createWorkerStreamLink({ + ...(available === undefined ? {} : { available }), + args: options.args, + captures, + command: options.command, + leaseEnvironment: options.leaseEnvironment, + onLog: (line) => options.logger.info("Capture", { line }), + registry: createStreamRegistry(options.registry), + }); + const service = { + link, + logger: { + info: (message: string, fields?: Record) => + options.logger.info(message, fields), + warn: (message: string, fields?: Record) => + options.logger.warn(message, fields), + }, + }; + return { + capabilitySource: { + name: SCREEN_STREAM_CAPABILITY, + read: async () => + available === undefined || (await available()) + ? { state: "available" } + : { reason: `No screen capture program at ${options.command}`, state: "unavailable" }, + }, + link, + leaseEnd: createStreamLeaseEnd(captures), + mount: createStreamMount(service), + relayTarget: createStreamRelayTarget(service), + }; +} + +// why: the snapshot is read at the moment of the read rather than held, because a stream is aimed at +// the device a lease holds right now -- a lease that ended a moment ago is not a stream, and a stale +// read would aim a capture at a simulator this worker has already begun to reclaim. +function createStreamRegistry(registry: { + readonly snapshot: { + readonly devices: readonly { + readonly address?: string; + readonly id: string; + readonly physical: boolean; + readonly spec: { readonly platform: string }; + }[]; + readonly leases: readonly { + readonly deviceId: string; + readonly id: string; + readonly ownerId: string; + readonly requesterId: string; + }[]; + }; +}): StreamRegistry { + return { + deviceOfLease: (leaseId) => { + const snapshot = registry.snapshot; + const lease = snapshot.leases.find((candidate) => candidate.id === leaseId); + if (lease === undefined) return undefined; + const device = snapshot.devices.find((candidate) => candidate.id === lease.deviceId); + if (device === undefined || device.address === undefined) return undefined; + return { + address: device.address, + physical: device.physical, + platform: device.spec.platform, + }; + }, + leaseOwner: (leaseId) => { + const lease = registry.snapshot.leases.find((candidate) => candidate.id === leaseId); + return lease === undefined + ? undefined + : { ownerId: lease.ownerId, requesterId: lease.requesterId }; + }, + }; +} + +function streamCaptureArgs(config: Config): string[] { + return [ + "--codec", + "avcc", + "--fps", + String(config.stream.fps), + "--bitrate", + String(config.stream.bitrateKbps * 1_000), + "--max-dimension", + String(config.stream.maxDimension), + ]; +} + +function createGatewayStreamParts(options: { + readonly leaseIndex: FleetLeaseIndex; + readonly logger: Logger; + readonly relay: GatewayRelay; +}): { readonly mount: HttpMount } { + const link = createGatewayStreamLink({ + fleet: { + holder: (leaseId) => { + const entry = options.leaseIndex.resolve(leaseId); + return entry === undefined + ? undefined + : { + ownerId: entry.ownerId, + requesterId: entry.requesterId, + workerLeaseId: entry.workerLeaseId, + workerId: entry.workerId, + }; + }, + requesterPrefix: options.leaseIndex.requesterPrefix, + relay: (workerId, request, tenant, leaseId) => + options.relay.relay(workerId, { + request, + target: STREAM_TARGET, + tenant, + ...(leaseId === undefined ? {} : { leaseId }), + }), + }, + }); + return { + mount: createStreamMount({ + link, + logger: { + info: (message: string, fields?: Record) => + options.logger.info(message, fields), + warn: (message: string, fields?: Record) => + options.logger.warn(message, fields), + }, + }), + }; +} + function agentDeviceFleetPort({ instanceId, leaseIndex, diff --git a/src/daemon/server.test.ts b/src/daemon/server.test.ts index b5a3b812..4d9a135c 100644 --- a/src/daemon/server.test.ts +++ b/src/daemon/server.test.ts @@ -3571,6 +3571,13 @@ function testConfig( return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/http/errors.ts b/src/http/errors.ts index 2a23175e..ab0353d2 100644 --- a/src/http/errors.ts +++ b/src/http/errors.ts @@ -9,14 +9,31 @@ import { type SimlockErrorCode, } from "../contract/index.js"; -/** Every status this frontend ever answers with; keeps `mapError` exhaustive by construction. - * 501 is `UNSUPPORTED_IN_GATEWAY_MODE`'s (ADR 0005 §34) and `UNSUPPORTED_IN_WORKER_MODE`'s (ADR - * 0012 §2): the request was well-formed and the daemon is healthy, this daemon's mode simply - * does not implement that operation. 504 is - * `EXEC_TIMEOUT`'s (ADR 0005 §19e): a `device.exec` command the daemon killed for outrunning - * `exec.timeoutMs` is the one case where what this frontend was waiting on never finished, - * which is exactly what a gateway timeout says. */ -export type HttpStatus = 400 | 401 | 403 | 404 | 409 | 422 | 500 | 501 | 503 | 504; +// why: every status this frontend ever answers with, which keeps `mapError` exhaustive by +// construction. 501 is `UNSUPPORTED_IN_GATEWAY_MODE`'s (ADR 0005 §34) and +// `UNSUPPORTED_IN_WORKER_MODE`'s (ADR 0012 §2): the request was well-formed and the daemon is +// healthy, this daemon's mode simply does not implement that operation. 504 is `EXEC_TIMEOUT`'s +// (ADR 0005 §19e): a `device.exec` command the daemon killed for outrunning `exec.timeoutMs` is the +// one case where what this frontend was waiting on never finished, which is exactly what a gateway +// timeout says. 405, 415 and 502 are answered only by the screen stream mount, which returns its own +// response rather than raising an error for `errorResponse` -- a stream cannot refuse one once it has +// begun, so it refuses with a status while the answer is still a status: 405 for a stream asked to be +// written, 415 for a device that can never produce a picture, 502 for a worker that answered "200" to +// its gateway with no picture at all. +export type HttpStatus = + | 400 + | 401 + | 403 + | 404 + | 405 + | 409 + | 415 + | 422 + | 500 + | 501 + | 502 + | 503 + | 504; /** * Uniform `{status, code}` pair a route or middleware raises directly (auth, ownership, diff --git a/src/http/test-fakes.ts b/src/http/test-fakes.ts index c7e59543..ad9076ec 100644 --- a/src/http/test-fakes.ts +++ b/src/http/test-fakes.ts @@ -33,6 +33,13 @@ export function testConfig( return { mode, agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, gateway: { disconnectedRetentionMs: 24 * 60 * 60_000, execTimeoutMs: 11 * 60_000, diff --git a/src/leasing/acquisition-planner.test.ts b/src/leasing/acquisition-planner.test.ts index 01239b85..32254121 100644 --- a/src/leasing/acquisition-planner.test.ts +++ b/src/leasing/acquisition-planner.test.ts @@ -17,6 +17,13 @@ const spec = { physical: false, model: "iPhone 16", osVersion: "26.5", platform: const config: Config = { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/create-leasing.test.ts b/src/leasing/create-leasing.test.ts index 952fdcdb..9e66c80b 100644 --- a/src/leasing/create-leasing.test.ts +++ b/src/leasing/create-leasing.test.ts @@ -46,6 +46,13 @@ function config( return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/lease-acquisition-coordinator.test.ts b/src/leasing/lease-acquisition-coordinator.test.ts index da4522eb..dd7b4bcc 100644 --- a/src/leasing/lease-acquisition-coordinator.test.ts +++ b/src/leasing/lease-acquisition-coordinator.test.ts @@ -64,6 +64,13 @@ function config(maxDevices = 1, maxRunning = 1): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/lease-end-participants.test.ts b/src/leasing/lease-end-participants.test.ts index 0d1b6930..2e8933c1 100644 --- a/src/leasing/lease-end-participants.test.ts +++ b/src/leasing/lease-end-participants.test.ts @@ -113,6 +113,13 @@ function config(options: EngineOptions): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/lease-health-monitor.test.ts b/src/leasing/lease-health-monitor.test.ts index 3ea64933..7be60239 100644 --- a/src/leasing/lease-health-monitor.test.ts +++ b/src/leasing/lease-health-monitor.test.ts @@ -25,6 +25,13 @@ function config(overrides: Partial = {}): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/physical-device-watcher.test.ts b/src/leasing/physical-device-watcher.test.ts index f02e17a1..2a12f080 100644 --- a/src/leasing/physical-device-watcher.test.ts +++ b/src/leasing/physical-device-watcher.test.ts @@ -47,6 +47,13 @@ function config(options: HarnessOptions): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/leasing/physical-leasing.test.ts b/src/leasing/physical-leasing.test.ts index 8f9b619c..3cb798b1 100644 --- a/src/leasing/physical-leasing.test.ts +++ b/src/leasing/physical-leasing.test.ts @@ -42,6 +42,13 @@ function config(leaseOverrides: object = {}): Config { return { mode: "worker", agentDevice: { command: "agent-device", enabled: false }, + stream: { + bitrateKbps: 1500, + command: "simlock-screen-capture", + enabled: false, + fps: 30, + maxDimension: 720, + }, exec: { timeoutMs: 600_000 }, diskPressure: { freeBytesThreshold: 10 * gibibyte }, gateway: { diff --git a/src/stream/caller.ts b/src/stream/caller.ts new file mode 100644 index 00000000..c5f78a84 --- /dev/null +++ b/src/stream/caller.ts @@ -0,0 +1,7 @@ +export type StreamRole = "agent" | "operator"; + +export interface StreamCaller { + readonly principal: string; + readonly requesterId?: string; + readonly role: StreamRole; +} diff --git a/src/stream/capture.test.ts b/src/stream/capture.test.ts new file mode 100644 index 00000000..19a6d085 --- /dev/null +++ b/src/stream/capture.test.ts @@ -0,0 +1,269 @@ +import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { afterAll, describe, expect, it } from "vitest"; + +import { captureStream } from "./capture.js"; + +// why: these tests spawn real programs rather than faking the spawn, because the whole point of this +// module is how it treats a child process -- its stdin, its stdout mid-chunk, its death -- and a fake +// child proves the arrangement of the fake. +const dirs: string[] = []; + +async function program(source: string): Promise { + const dir = await mkdtemp(join(tmpdir(), "simlock-capture-")); + dirs.push(dir); + const path = join(dir, "capture.mjs"); + await writeFile(path, `#!/usr/bin/env node\n${source}`); + await chmod(path, 0o755); + return path; +} + +afterAll(async () => { + await Promise.all(dirs.splice(0).map((dir) => rm(dir, { force: true, recursive: true }))); +}); + +function frame(tag: number, payload: Buffer): Buffer { + const head = Buffer.alloc(5); + head.writeUInt32BE(payload.length + 1, 0); + head[4] = tag; + return Buffer.concat([head, payload]); +} + +function metaFrame(): Buffer { + return frame(0x05, Buffer.from(JSON.stringify({ codec: "h264", fps: 30, height: 720, width: 330 }))); +} + +function collect(body: ReadableStream, bytes: number): Promise { + return (async () => { + const reader = body.getReader(); + const chunks: Uint8Array[] = []; + let have = 0; + while (have < bytes) { + const step = await reader.read(); + if (step.done) break; + chunks.push(step.value); + have += step.value.length; + } + await reader.cancel().catch(() => undefined); + return new Uint8Array(Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))); + })(); +} + +describe("captureStream", () => { + it("waits for the meta frame and hands the body back from its first byte", async () => { + const command = await program(` + const meta = ${JSON.stringify([...metaFrame()])}; + process.stdout.write(Buffer.from(meta)); + setInterval(() => process.stdout.write(Buffer.from([1,2,3])), 10); + process.stdin.resume(); + `); + const stream = await captureStream({ args: [], command, env: process.env }); + + expect(stream.meta).toEqual({ codec: "h264", fps: 30, height: 720, width: 330 }); + // why: the meta frame is the head's answer, and a decoder still needs it in the body -- a reader + // that got only [1,2,3] lost the frame that says how to read them. + const first = await collect(stream.body, metaFrame().length + 3); + expect(Buffer.from(first.subarray(0, metaFrame().length)).equals(metaFrame())).toBe(true); + expect([...first.subarray(metaFrame().length)]).toEqual([1, 2, 3]); + stream.close(); + }); + + it("keeps the bytes that arrived in the same read as the meta frame", async () => { + // why: one write of meta-and-frames-together is what a pipe does under load, and a scanner that + // returned only the meta frame would drop every frame that shared the read with it. + const whole = Buffer.concat([metaFrame(), frame(0x03, Buffer.from([9, 9, 9]))]); + const command = await program(` + process.stdout.write(Buffer.from(${JSON.stringify([...whole])})); + process.stdin.resume(); + `); + const stream = await captureStream({ args: [], command, env: process.env }); + expect([...(await collect(stream.body, whole.length))]).toEqual([...whole]); + stream.close(); + }); + + it("reads a meta frame that arrives a few bytes at a time", async () => { + const command = await program(` + const bytes = Buffer.from(${JSON.stringify([...metaFrame()])}); + let at = 0; + const timer = setInterval(() => { + process.stdout.write(bytes.subarray(at, at + 2)); + at += 2; + if (at >= bytes.length) clearInterval(timer); + }, 2); + process.stdin.resume(); + `); + const stream = await captureStream({ args: [], command, env: process.env }); + expect(stream.meta.width).toBe(330); + stream.close(); + }); + + it("stops the tool the way the tool was built to be stopped, and says so exactly once", async () => { + // why: the claim is that the child sees its stdin close -- the liveness handle the capture program + // is written against -- and not merely that this process forgot about it. A tool that ignored stdin + // would keep filming, and only reading what the child itself wrote proves it stopped. + const dir = await mkdtemp(join(tmpdir(), "simlock-capture-")); + dirs.push(dir); + const seen = join(dir, "stdin-ended"); + const command = await program(` + import { writeFileSync } from "node:fs"; + process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); + process.stdin.on("end", () => writeFileSync(${JSON.stringify(seen)}, "ended")); + process.stdin.resume(); + `); + let ended = 0; + const stream = await captureStream({ + args: [], + command, + env: process.env, + onEnd: () => { + ended += 1; + }, + }); + stream.close(); + stream.close(); + expect(ended).toBe(1); + + const { readFile } = await import("node:fs/promises"); + for (let at = 0; at < 100; at += 1) { + try { + expect(await readFile(seen, "utf8")).toBe("ended"); + return; + } catch { + await new Promise((resolve) => setTimeout(resolve, 20)); + } + } + throw new Error("the capture program never saw its stdin close"); + }); + + it("reports a tool that dies before its first frame, with its own words", async () => { + const command = await program('console.error("Device not found in device set"); process.exit(1);'); + await expect(captureStream({ args: [], command, env: process.env })).rejects.toThrow( + /not found in device set/u, + ); + }); + + it("tells whoever registered a tool that died on its own, without being asked to close it", async () => { + const command = await program(` + process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); + setTimeout(() => process.exit(0), 20); + `); + let ended = 0; + const stream = await captureStream({ + args: [], + command, + env: process.env, + onEnd: () => { + ended += 1; + }, + }); + const reader = stream.body.getReader(); + // why: a simulator that was shut down mid-picture ends this stream on its own; a registry still + // naming the capture after that is a lease that looks like it still has a picture running. + for (;;) { + const step = await reader.read(); + if (step.done) break; + } + await new Promise((resolve) => setTimeout(resolve, 60)); + expect(ended).toBe(1); + }); + + it("ends the capture when its body is dropped", async () => { + const command = await program(` + process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); + setInterval(() => process.stdout.write(Buffer.from([0])), 5); + process.stdin.resume(); + `); + let ended = 0; + const stream = await captureStream({ + args: [], + command, + env: process.env, + onEnd: () => { + ended += 1; + }, + }); + await stream.body.cancel(); + expect(ended).toBe(1); + }); + + it("gives up on a tool that starts and never writes a frame", async () => { + const command = await program("process.stdin.resume();"); + // why: this is the failure a request could otherwise wait on forever -- a program that exists, that + // spawned, and that has nothing to say. The bound is what turns it into an answer. + await expect( + captureStream({ args: [], command, env: process.env, silenceMs: 250 }), + ).rejects.toThrow(/wrote nothing for 250ms/u); + }); + + it("ends a stream whose tool goes quiet mid-picture", async () => { + const command = await program(` + process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); + // why: nothing more arrives, which is the whole case under test + process.stdin.resume(); + `); + let ended = 0; + const stream = await captureStream({ + args: [], + command, + env: process.env, + onEnd: () => { + ended += 1; + }, + silenceMs: 250, + }); + const reader = stream.body.getReader(); + // why: an errored stream rejects `closed` too, and a test that lets that escape reports an + // unhandled rejection instead of the claim it is making. + reader.closed.catch(() => undefined); + // why: drained rather than read once, because the stream pulls ahead of the reader and so *which* + // read meets the silence is not knowable here -- the claim is that reading this body cannot go on + // forever, whichever step the bound is reported on. The race against a longer timer is what makes a + // bound that never fired fail with a named reason instead of hanging to vitest's own timeout, and + // the cancel inside it is what frees a read still waiting when that happens. + const drained: Promise = (async () => { + try { + for (;;) { + const step = await reader.read(); + if (step.done) return "the body ended cleanly"; + } + } catch (error) { + return error instanceof Error ? error.message : String(error); + } + })(); + const next: string = await Promise.race([ + drained, + new Promise((resolve) => { + const timer = setTimeout(() => { + void reader.cancel().catch(() => undefined); + resolve("never answered"); + }, 5_000); + timer.unref(); + }), + ]); + // why: a bound that held only before the first frame would leave a viewer watching a picture that + // froze, which is the failure mode this whole module is here to avoid. + expect(next).toMatch(/^The screen capture produced no stream \(the capture tool wrote nothing/u); + // why: the reader's failure and the capture's stop are two claims, and only the second keeps a + // simulator's framebuffer from being pinned by a stream nobody can see any more. + expect(ended).toBe(1); + }); + + it("refuses a tool that is not there rather than hanging", async () => { + await expect( + captureStream({ + args: [], + command: join(tmpdir(), "simlock-no-such-capture-program"), + env: process.env, + }), + ).rejects.toThrow(/simlock-no-such-capture-program/u); + }); + + it("refuses an output that is not a frame envelope", async () => { + const command = await program(`process.stdout.write("not a stream at all\\n"); process.exit(0);`); + await expect(captureStream({ args: [], command, env: process.env })).rejects.toThrow( + /not a frame envelope|ended before it wrote a frame/u, + ); + }); +}); diff --git a/src/stream/capture.ts b/src/stream/capture.ts new file mode 100644 index 00000000..b2daf474 --- /dev/null +++ b/src/stream/capture.ts @@ -0,0 +1,211 @@ +import { spawn } from "node:child_process"; +import { Readable } from "node:stream"; + +import { MetaFrameScanner, type CaptureMeta } from "./framing.js"; + +export interface CaptureOptions { + readonly command: string; + readonly args: readonly string[]; + readonly env: NodeJS.ProcessEnv; + onLog?(line: string): void; + onEnd?(): void; + // why: how long a capture may stay silent before its stream is called dead; see DEFAULT_SILENCE_MS + // for the default and why there is a bound at all. + readonly silenceMs?: number; +} + +export interface CapturedStream { + readonly meta: CaptureMeta; + readonly body: ReadableStream; + close(): void; +} + +const LOG_TAIL = 20; + +// why: a capture program that starts and never writes is the one failure a request cannot tell from a +// slow one, and an unbounded wait here would hold a viewer's connection, a worker's lease check and a +// simulator's framebuffer for a program that is never going to speak. Generous against a cold +// simulator; a bound rather than none because every other wait on another process has one. +const DEFAULT_SILENCE_MS = 30_000; + +export async function captureStream(options: CaptureOptions): Promise { + // why: stdin stays open on purpose. The capture tool treats it as a liveness handle, so closing it + // is the stop the tool was built for, and a daemon that is killed outright closes it by itself -- + // which is the only reason a capture cannot outlive a worker that was SIGKILLed. + const child = spawn(options.command, [...options.args], { + env: options.env, + stdio: ["pipe", "pipe", "pipe"], + }); + child.stdin.on("error", () => undefined); + + const tail: string[] = []; + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (chunk: string) => { + for (const line of chunk.split("\n")) { + if (line.trim() === "") continue; + tail.push(line); + if (tail.length > LOG_TAIL) tail.shift(); + options.onLog?.(line); + } + }); + + // why: closing stdin is the polite stop and a signal is the backstop, because a tool that ignored + // its liveness handle would otherwise pin a simulator's framebuffer forever. The half second is + // only the grace it gets to write its own last log line. + const stopTool = () => { + if (child.exitCode !== null || child.signalCode !== null) return; + child.stdin.end(); + const deadline = setTimeout(() => { + if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); + }, 500); + deadline.unref(); + }; + + // why: `onEnd` unregisters the capture from the registry of running ones, and it has to fire for a + // tool that dies on its own as much as for a stop, or a lease would keep looking like it had a + // picture running. + const notifyEnd = once(() => options.onEnd?.()); + child.on("close", notifyEnd); + const close = () => { + stopTool(); + notifyEnd(); + }; + + // why: a child that dies is reported into the stream, so a viewer watching a simulator that was + // shut down mid-picture sees the stream fail rather than a frame that froze in place. + const died = new Promise((_, reject) => { + child.on("error", (error) => reject(withLog(error.message, tail))); + child.on("close", (code, signal) => + reject( + withLog( + signal !== null + ? `the capture tool was killed by ${signal}` + : `the capture tool exited with code ${String(code)}`, + tail, + ), + ), + ); + }); + died.catch(() => undefined); + // why: a process the OS refused to spawn never fires `close`, so that one path reports by hand. + child.once("error", notifyEnd); + + const reader = Readable.toWeb(child.stdout).getReader() as ReadableStreamDefaultReader; + const scanner = new MetaFrameScanner(); + const buffered: Uint8Array[] = []; + + // why: a timeout races every read, before and after the first frame, because "started and then went + // quiet" is something a capture program can do at any point. A wait bounded only before the first + // frame would still leave a viewer watching a frozen picture forever once the tool falls silent -- + // and a picture nobody can tell from a live one is the failure this module exists to avoid. + const silenceMs = options.silenceMs ?? DEFAULT_SILENCE_MS; + let silence: ReturnType | undefined; + const readFrame = (): Promise> => + new Promise>((resolve, reject) => { + const settle = (settleWith: () => void) => { + clearTimeout(silence); + settleWith(); + }; + silence = setTimeout( + () => + reject(withLog(`the capture tool wrote nothing for ${String(silenceMs)}ms`, tail)), + silenceMs, + ); + silence.unref(); + reader.read().then( + (step) => settle(() => resolve(step)), + (error) => settle(() => reject(error)), + ); + }); + + try { + for (;;) { + const step = await Promise.race([readFrame(), died]); + if (step.done) { + throw withLog("the capture tool ended before it wrote a frame", tail); + } + buffered.push(step.value); + const found = scanner.push(step.value); + if (found !== undefined) { + // why: everything read so far goes back on the front in order. The meta frame is what the + // response head was answered from but a decoder needs it in the body too, and the bytes that + // arrived in the same read as it are the first frames of the stream -- re-emitting the whole + // buffer is what keeps any of them from being dropped, and nothing here re-frames. + return { + body: followedBy(concat(buffered), readFrame, reader, close), + close, + meta: found.meta, + }; + } + } + } catch (error) { + clearTimeout(silence); + close(); + void reader.cancel().catch(() => undefined); + throw error; + } +} + +function followedBy( + prefix: Uint8Array, + read: () => Promise>, + source: ReadableStreamDefaultReader, + onClose: () => void, +): ReadableStream { + let handed = false; + return new ReadableStream({ + async pull(controller) { + if (!handed) { + handed = true; + controller.enqueue(prefix); + return; + } + try { + const step = await read(); + if (step.done) { + onClose(); + controller.close(); + } else controller.enqueue(step.value); + } catch (error) { + // why: a read that failed is the capture's end as much as a clean close is -- a body that + // errors while a registry still names its capture is a lease that looks like it is still + // being filmed, and the tool behind it has no idea its viewer is gone. + onClose(); + controller.error(error); + } + }, + // why: a body dropped without its request being aborted still ends the capture -- a stream nobody + // is reading has no claim on a simulator's framebuffer. + cancel: (reason) => { + onClose(); + return source.cancel(reason); + }, + }); +} + +function concat(chunks: readonly Uint8Array[]): Uint8Array { + const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const out = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + out.set(chunk, offset); + offset += chunk.length; + } + return out; +} + +function once(fn: () => void): () => void { + let done = false; + return () => { + if (done) return; + done = true; + fn(); + }; +} + +function withLog(detail: string, tail: readonly string[]): Error { + return new Error( + `The screen capture produced no stream (${detail})` + + (tail.length === 0 ? "" : `: ${tail[tail.length - 1]}`), + ); +} diff --git a/src/stream/captures.test.ts b/src/stream/captures.test.ts new file mode 100644 index 00000000..bcb641b0 --- /dev/null +++ b/src/stream/captures.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; + +import { createCaptureRegistry, createStreamLeaseEnd, type RunningCapture } from "./captures.js"; + +// why: a capture that remembers being stopped, so a test can say that this capture stopped and not +// another -- a registry that only cleared its map would pass a test that counted the map. +function spy(): RunningCapture & { stops(): number } { + let stopped = 0; + return { + close: () => { + stopped += 1; + }, + stops: () => stopped, + }; +} + +describe("createCaptureRegistry", () => { + it("stops the captures of one lease and none of another", () => { + const registry = createCaptureRegistry(); + const first = spy(); + const second = spy(); + registry.track("lse_a", first); + registry.track("lse_b", second); + + expect(registry.stop("lse_a")).toBe(1); + expect(first.stops()).toBe(1); + expect(second.stops()).toBe(0); + expect(registry.leases()).toEqual(["lse_b"]); + }); + + it("stops every capture of a lease, because one device can be watched from more than one page", () => { + const registry = createCaptureRegistry(); + const viewers = [spy(), spy(), spy()]; + for (const viewer of viewers) registry.track("lse_a", viewer); + + expect(registry.stop("lse_a")).toBe(3); + expect(viewers.map((viewer) => viewer.stops())).toEqual([1, 1, 1]); + expect(registry.leases()).toEqual([]); + }); + + it("forgets a capture closed through its own handle, so a stop does not close it twice", () => { + const registry = createCaptureRegistry(); + const capture = spy(); + const handle = registry.track("lse_a", capture); + + handle.close(); + expect(registry.leases()).toEqual([]); + expect(registry.stop("lse_a")).toBe(0); + // why: one stop is the tool seeing its stdin close once; a second close here is the registry still + // naming a capture it should have let go of, aimed at a process that may be somebody else's by now. + expect(capture.stops()).toBe(1); + }); + + it("keeps the lease's other captures when one viewer goes away", () => { + const registry = createCaptureRegistry(); + const staying = spy(); + const leaving = spy(); + const handle = registry.track("lse_a", leaving); + registry.track("lse_a", staying); + + handle.close(); + expect(leaving.stops()).toBe(1); + expect(staying.stops()).toBe(0); + expect(registry.leases()).toEqual(["lse_a"]); + }); + + it("stops nothing for a lease it never heard of", () => { + const registry = createCaptureRegistry(); + registry.track("lse_a", spy()); + expect(registry.stop("lse_nope")).toBe(0); + expect(registry.leases()).toEqual(["lse_a"]); + }); +}); + +describe("createStreamLeaseEnd", () => { + it("ends a picture the moment the lease ends, with the viewer still connected", () => { + const registry = createCaptureRegistry(); + const capture = spy(); + registry.track("lse_a", capture); + const leaseEnd = createStreamLeaseEnd(registry); + + // why: this is the case an aborted request cannot cover -- a lease ended somewhere else (released by + // a CLI, expired, its device lost) while a browser still holds the stream open. Without this the + // viewer keeps watching a simulator this worker has already handed back to the pool, and the next + // lease of that device is someone else's screen. + void leaseEnd.leaseEnded(ended("lse_a"), new AbortController().signal); + expect(capture.stops()).toBe(1); + expect(registry.leases()).toEqual([]); + }); + + it("is idle for a lease that never streamed anything", async () => { + const registry = createCaptureRegistry(); + await expect( + createStreamLeaseEnd(registry).leaseEnded(ended("lse_a"), new AbortController().signal), + ).resolves.toBeUndefined(); + }); +}); + +function ended(leaseId: string) { + return { + deviceId: "dev_1", + leaseId, + ownerId: "agent-1", + platform: "ios" as const, + reason: "explicit" as const, + }; +} diff --git a/src/stream/captures.ts b/src/stream/captures.ts new file mode 100644 index 00000000..96637e44 --- /dev/null +++ b/src/stream/captures.ts @@ -0,0 +1,61 @@ +// why: a capture belongs to a lease, not to a connection -- the lease is the thing that says this +// machine still owns that simulator, and the types cannot say so. A viewer going away already stops a +// capture through its own request, which covers the ordinary case; this covers the case it cannot, a +// lease that ends somewhere else (released elsewhere, expired, the device lost) while somebody is +// still watching. Without it a stream pins a simulator's framebuffer on a device this worker has +// already handed back. +import type { LeaseEndParticipant } from "../leasing/index.js"; + +export interface RunningCapture { + close(): void; +} + +export interface CaptureRegistry { + track(leaseId: string, capture: RunningCapture): RunningCapture; + stop(leaseId: string): number; + leases(): string[]; +} + +export function createCaptureRegistry(): CaptureRegistry { + const running = new Map>(); + + return { + leases: () => [...running.keys()], + stop: (leaseId) => { + const captures = running.get(leaseId); + if (captures === undefined) return 0; + running.delete(leaseId); + for (const capture of captures) capture.close(); + return captures.size; + }, + track: (leaseId, capture) => { + const forLease = running.get(leaseId) ?? new Set(); + forLease.add(capture); + running.set(leaseId, forLease); + // why: `released` is the handle's own latch, not the capture's -- one capture can be closed + // through three doors (a stop, an aborted viewer, this registry) and the map must be corrected + // once, or a second close would remove another lease's capture that had reused the same set. + let released = false; + const close = () => { + if (!released) { + released = true; + forLease.delete(capture); + if (forLease.size === 0) running.delete(leaseId); + } + capture.close(); + }; + return { close }; + }, + }; +} + +export function createStreamLeaseEnd(captures: CaptureRegistry): LeaseEndParticipant { + return { + // why: resolves without waiting for the capture process to be reaped. The stop is a closed pipe, + // not an operation whose outcome leasing needs, and a lease's reclaim must not queue behind a + // video tool's exit; the viewer learns the picture ended when the tool's stdout reaches its end. + leaseEnded: async (ended) => { + captures.stop(ended.leaseId); + }, + }; +} diff --git a/src/stream/framing.test.ts b/src/stream/framing.test.ts new file mode 100644 index 00000000..f61b7681 --- /dev/null +++ b/src/stream/framing.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it } from "vitest"; + +import { CAPTURE_TAG, MAX_FRAME_BYTES, MetaFrameScanner } from "./framing.js"; + +const META = { codec: "h264", deviceSet: "/set", fps: 30, height: 720, udid: "UDID", width: 330 }; + +function frame(tag: number, payload: Uint8Array): Uint8Array { + const head = new Uint8Array(5); + new DataView(head.buffer).setUint32(0, payload.length + 1); + head[4] = tag; + return Uint8Array.from([...head, ...payload]); +} + +function metaFrame(payload: unknown = META): Uint8Array { + return frame(CAPTURE_TAG.meta, new TextEncoder().encode(JSON.stringify(payload))); +} + +describe("MetaFrameScanner", () => { + it("finds a meta frame only once its last byte has arrived", () => { + const bytes = metaFrame(); + const scanner = new MetaFrameScanner(); + const found = []; + // why: one byte at a time, because a header split across reads is what a pipe actually does, and a + // scanner that reads a length it has not fully received acts on bytes that are not there yet. + for (const byte of bytes) { + found.push(scanner.push(Uint8Array.from([byte]))); + } + expect(found.slice(0, -1).every((step) => step === undefined)).toBe(true); + expect(found.at(-1)?.meta).toEqual({ codec: "h264", fps: 30, height: 720, width: 330 }); + expect(found.at(-1)?.bytes).toEqual(bytes); + }); + + it("reports the meta frame it returned as exactly the bytes that frame occupies", () => { + const bytes = metaFrame(); + const scanner = new MetaFrameScanner(); + const found = scanner.push(bytes); + expect(found?.bytes.byteLength).toBe(bytes.byteLength); + }); + + it("refuses a stream that puts a video frame before the meta frame", () => { + const scanner = new MetaFrameScanner(); + // why: the meta frame is the one promise the envelope makes, and a decoder configured from a header + // that never arrived would fail later and further away from the cause. + expect(() => scanner.push(frame(CAPTURE_TAG.keyframe, new Uint8Array([1, 2, 3])))).toThrow( + /does not begin with a meta frame/u, + ); + expect(() => scanner.push(frame(CAPTURE_TAG.description, new Uint8Array([1])))).toThrow( + /does not begin with a meta frame/u, + ); + }); + + it("refuses a header whose length is not a frame", () => { + for (const length of [0, MAX_FRAME_BYTES + 10, 0xffff_ffff]) { + const head = new Uint8Array(5); + new DataView(head.buffer).setUint32(0, length); + head[4] = CAPTURE_TAG.meta; + expect(() => new MetaFrameScanner().push(head)).toThrow(/not a frame envelope/u); + } + }); + + it("refuses a meta frame whose payload is not JSON rather than reporting a stream with no size", () => { + const scanner = new MetaFrameScanner(); + // why: a size of zero would answer a response with headers saying so, and the viewer would sit on a + // stream it can never decode; the tool's own log is where that failure is explained. + expect(() => scanner.push(frame(CAPTURE_TAG.meta, new TextEncoder().encode("")))).toThrow( + /meta frame is not JSON/u, + ); + }); + + it("reads an mjpeg meta as mjpeg and defaults a size it was not told to zero", () => { + const scanner = new MetaFrameScanner(); + const found = scanner.push(metaFrame({ codec: "mjpeg" })); + expect(found?.meta).toEqual({ codec: "mjpeg", fps: 0, height: 0, width: 0 }); + }); +}); diff --git a/src/stream/framing.ts b/src/stream/framing.ts new file mode 100644 index 00000000..e6b856f6 --- /dev/null +++ b/src/stream/framing.ts @@ -0,0 +1,72 @@ +export const CAPTURE_TAG = { + description: 0x01, + keyframe: 0x02, + delta: 0x03, + seed: 0x04, + meta: 0x05, +} as const; + +const HEADER_BYTES = 5; + +export const MAX_FRAME_BYTES = 8 * 1024 * 1024; + +export interface CaptureMeta { + readonly codec: "h264" | "mjpeg"; + readonly width: number; + readonly height: number; + readonly fps: number; +} + +export interface MetaFrame { + readonly meta: CaptureMeta; + readonly bytes: Uint8Array; +} + +export class MetaFrameScanner { + #buffer = new Uint8Array(0); + + push(chunk: Uint8Array): MetaFrame | undefined { + if (this.#buffer.length > MAX_FRAME_BYTES) { + throw new Error("The screen capture tool wrote no meta frame before an oversized frame"); + } + const merged = new Uint8Array(this.#buffer.length + chunk.length); + merged.set(this.#buffer, 0); + merged.set(chunk, this.#buffer.length); + this.#buffer = merged; + + for (;;) { + if (this.#buffer.length < HEADER_BYTES) return undefined; + const length = new DataView(this.#buffer.buffer, this.#buffer.byteOffset).getUint32(0); + const total = HEADER_BYTES + length - 1; + if (length < 1 || total > MAX_FRAME_BYTES) { + throw new Error("The screen capture tool's output is not a frame envelope"); + } + if (this.#buffer.length < total) return undefined; + if (this.#buffer[4] === CAPTURE_TAG.meta) { + const meta = parseMeta(this.#buffer.subarray(HEADER_BYTES, total)); + if (meta === undefined) { + throw new Error("The screen capture tool's meta frame is not JSON"); + } + return { bytes: this.#buffer.slice(0, total), meta }; + } + // why: a frame ahead of the meta frame means a tool that broke the one promise the envelope + // makes -- write the meta first -- so the stream is refused rather than decoded blind. + throw new Error("The screen capture tool's stream does not begin with a meta frame"); + } + } +} + +function parseMeta(payload: Uint8Array): CaptureMeta | undefined { + try { + const parsed = JSON.parse(new TextDecoder().decode(payload)) as Record; + const number = (key: string) => (typeof parsed[key] === "number" ? parsed[key] : 0); + return { + codec: parsed["codec"] === "mjpeg" ? "mjpeg" : "h264", + fps: number("fps"), + height: number("height"), + width: number("width"), + }; + } catch { + return undefined; + } +} diff --git a/src/stream/gateway-link.ts b/src/stream/gateway-link.ts new file mode 100644 index 00000000..7c4107b7 --- /dev/null +++ b/src/stream/gateway-link.ts @@ -0,0 +1,91 @@ +import type { CapturedStream } from "./capture.js"; +import { relayedRefusal, unknownLease } from "./refusal.js"; +import type { StreamLink } from "./service.js"; + +export interface StreamLeasePlacement { + readonly ownerId: string; + readonly requesterId: string; + readonly workerLeaseId: string; + readonly workerId: string; +} + +export interface FleetPort { + holder(leaseId: string): StreamLeasePlacement | undefined; + // why: `gw::`, the exact prefix this index recognizes a requester it + // sent by, so a stream namespaces with the same value its leases do. + readonly requesterPrefix: string; + relay(workerId: string, request: Request, tenant: string, leaseId: string): Promise; +} + +export interface GatewayStreamLinkOptions { + readonly fleet: FleetPort; +} + +const WORKER_STREAM_PATH = "/stream/leases/"; + +export function createGatewayStreamLink(options: GatewayStreamLinkOptions): StreamLink { + return { + async serve(leaseId, caller, signal): Promise { + const held = options.fleet.holder(leaseId); + if (held === undefined) throw unknownLease(leaseId); + if (caller.role !== "operator" && held.ownerId !== caller.principal) { + throw unknownLease(leaseId); + } + + // why: the tenant is the same namespaced requester id `device.exec` forwards (ADR 0005 §27), + // namespaced from the lease's own recorded requester rather than from the caller's claim -- so + // a caller cannot reach a screen by naming someone else, and the worker proves ownership + // against its own registry instead of trusting this relay. + const namespacedRequesterId = `${options.fleet.requesterPrefix}${held.requesterId}`; + const response = await options.fleet.relay( + held.workerId, + new Request( + // why: the worker's own name for the lease, not the fleet id. A fleet id is this gateway's + // naming and a worker knows nothing of it, so a request that reached the wrong lease would + // be answered with the wrong screen rather than refused. + `http://simlock-worker${WORKER_STREAM_PATH}${encodeURIComponent(held.workerLeaseId)}`, + { method: "GET", signal }, + ), + namespacedRequesterId, + held.workerLeaseId, + ); + return fromResponse(held.workerId, leaseId, response); + }, + }; +} + +async function fromResponse( + workerId: string, + leaseId: string, + response: Response, +): Promise { + // why: the codec header is what tells a relayed stream from a relayed refusal -- a gateway reads a + // stream's headers and never one video byte, which is also why a refusal is read as JSON here and a + // picture never is. + const codec = response.headers.get("simlock-stream-codec") ?? ""; + if (!response.ok || codec === "") { + const reason = await response.text().catch(() => ""); + throw relayedRefusal(workerId, leaseId, response.status, reason); + } + if (response.body === null) { + throw new Error(`Worker ${workerId} answered the stream for lease ${leaseId} with no body`); + } + return { + body: response.body, + // why: a browser that goes away aborts its request, which aborts the relay, which the worker + // answers by killing its own capture -- so this cancel covers only the case where the gateway + // itself gives up on a viewer of its own. + close: () => response.body?.cancel().catch(() => undefined), + meta: { + codec: codec === "mjpeg" ? "mjpeg" : "h264", + fps: numberHeader(response, "simlock-stream-fps"), + height: numberHeader(response, "simlock-stream-height"), + width: numberHeader(response, "simlock-stream-width"), + }, + }; +} + +function numberHeader(response: Response, name: string): number { + const raw = Number(response.headers.get(name)); + return Number.isFinite(raw) ? raw : 0; +} diff --git a/src/stream/index.ts b/src/stream/index.ts new file mode 100644 index 00000000..e127728e --- /dev/null +++ b/src/stream/index.ts @@ -0,0 +1,13 @@ +export { + createCaptureRegistry, + createStreamLeaseEnd, + type CaptureRegistry, +} from "./captures.js"; +export { + createGatewayStreamLink, + type FleetPort, + type StreamLeasePlacement, +} from "./gateway-link.js"; +export { createWorkerStreamLink, type StreamRegistry } from "./worker-link.js"; +export { createStreamMount, createStreamRelayTarget, STREAM_TARGET } from "./mounts.js"; +export type { StreamLink } from "./service.js"; diff --git a/src/stream/mounts.ts b/src/stream/mounts.ts new file mode 100644 index 00000000..f40ee2d2 --- /dev/null +++ b/src/stream/mounts.ts @@ -0,0 +1,48 @@ +import { handleStreamRequest, leaseIdOf, STREAM_PREFIX, type StreamServiceOptions } from "./service.js"; + +export const STREAM_TARGET = "stream"; + +// why: the shape the HTTP layer mounts, declared here rather than imported from `src/http`, because a +// stream is the same exchange whether a console on this host asks for it or a gateway relays one, and a +// component that imports the front end it is mounted into cannot be mounted into a relay as well. +export interface StreamMount { + readonly prefix: string; + handle( + request: Request, + identity: { readonly requesterId: string; readonly role: string }, + ): Promise; +} + +export interface StreamRelayTarget { + (request: Request, tenant: string, leaseId: string | undefined): Promise; +} + +export function createStreamMount(options: StreamServiceOptions): StreamMount { + return { + handle: (request, identity) => + handleStreamRequest( + options, + request, + { + principal: identity.requesterId, + role: identity.role === "operator" ? "operator" : "agent", + }, + leaseIdOf(new URL(request.url).pathname), + ), + prefix: STREAM_PREFIX, + }; +} + +export function createStreamRelayTarget(options: StreamServiceOptions): StreamRelayTarget { + return (request, tenant, leaseId) => + // why: a relayed exchange arrives carrying the tenant it was relayed under, which is what the + // worker checks its own registry against -- the same way `device.exec` names the requester a + // gateway speaks for. Nothing on this door is a claim the worker takes on faith, and a relay is + // admitted only from a gateway session, so it is as trusted as the uplink it came on and no more. + handleStreamRequest( + options, + request, + { principal: tenant, requesterId: tenant, role: "agent" }, + leaseId, + ); +} diff --git a/src/stream/refusal.ts b/src/stream/refusal.ts new file mode 100644 index 00000000..f10d06dd --- /dev/null +++ b/src/stream/refusal.ts @@ -0,0 +1,78 @@ +// why: a stream refuses for reasons a link knows and the HTTP layer must not guess at, and every +// other route in this API answers `{ "error": { "code", "message" } }`. A link that throws one of +// these instead of a plain Error is what lets the mount keep that shape and lets a gateway forward +// the worker's own code rather than rewriting it into prose a viewer has to read. +export class StreamRefusalError extends Error { + // why: `code` is a free string rather than a closed union, because a gateway forwards the code its + // worker answered with and that worker may be a build this one has never seen. The codes this + // service raises itself are `UNKNOWN_LEASE`, `BAD_REQUEST`, `STREAM_NOT_SUPPORTED`, + // `STREAM_UNAVAILABLE` and `INTERNAL`, and `docs/HTTP-API.md` is where they are promised. + constructor( + readonly status: number, + readonly code: string, + message: string, + ) { + super(message); + this.name = "StreamRefusalError"; + } +} + +export function unknownLease(leaseId: string): StreamRefusalError { + return new StreamRefusalError(404, "UNKNOWN_LEASE", `Unknown lease: ${leaseId}`); +} + +export function noSuchStream(): StreamRefusalError { + // why: the same status and code as an unknown lease on purpose -- a daemon with `stream.enabled` off + // never mounts this route at all and answers any path it has not heard of as a 404, so a client that + // could tell these apart would be able to learn which daemons can stream by asking one. + return new StreamRefusalError(404, "UNKNOWN_LEASE", "This path names no stream lease"); +} + +export function notStreamable(message: string): StreamRefusalError { + return new StreamRefusalError(415, "STREAM_NOT_SUPPORTED", message); +} + +export function streamUnavailable(message: string): StreamRefusalError { + return new StreamRefusalError(503, "STREAM_UNAVAILABLE", message); +} + +export function relayedRefusal( + workerId: string, + leaseId: string, + status: number, + body: string, +): StreamRefusalError { + const error = asRecord(parse(body))?.["error"]; + const fields = asRecord(error); + const message = fields?.["message"]; + if (typeof message === "string" && message !== "") { + const code = fields?.["code"]; + return new StreamRefusalError( + status, + typeof code === "string" ? code : "STREAM_UNAVAILABLE", + message, + ); + } + // why: a body that will not parse is still a refusal, and its status is the part that is certain -- + // but a worker that answered 200 with no picture has broken the one promise a stream makes, which is + // this gateway's own failure to deliver, not the worker refusing. + return new StreamRefusalError( + status >= 200 && status < 300 ? 502 : status, + "STREAM_UNAVAILABLE", + `Worker ${workerId} refused the stream for lease ${leaseId}${body === "" ? "" : `: ${body}`}`, + ); +} + +function asRecord(value: unknown): Record | undefined { + return typeof value === "object" && value !== null + ? (value as Record) + : undefined; +} + +function parse(body: string): unknown { + try { + return JSON.parse(body) as unknown; + } catch { + return undefined; + } +} diff --git a/src/stream/service.test.ts b/src/stream/service.test.ts new file mode 100644 index 00000000..758903ff --- /dev/null +++ b/src/stream/service.test.ts @@ -0,0 +1,145 @@ +import { describe, expect, it } from "vitest"; + +import type { CapturedStream } from "./capture.js"; +import { StreamRefusalError } from "./refusal.js"; +import { + handleStreamRequest, + leaseIdOf, + streamResponse, + type StreamLink, +} from "./service.js"; + +function stream(codec: "h264" | "mjpeg" = "h264"): CapturedStream { + return { + body: new ReadableStream(), + close: () => undefined, + meta: { codec, fps: 30, height: 720, width: 330 }, + }; +} + +function linkThat(serve: StreamLink["serve"]): StreamLink { + return { serve }; +} + +const OPERATOR = { principal: "tok_op", role: "operator" } as const; + +function get(path = "/v1/stream/leases/lse_1"): Request { + return new Request(`http://daemon${path}`); +} + +describe("leaseIdOf", () => { + it("reads the lease a stream path names, including a fleet id with a dot in it", () => { + expect(leaseIdOf("/v1/stream/leases/lse_1")).toBe("lse_1"); + expect(leaseIdOf("/v1/stream/leases/28e5b99c.lse_c09b")).toBe("28e5b99c.lse_c09b"); + }); + + it("reads nothing for a path that is not one lease", () => { + for (const path of [ + "/v1/stream/leases", + "/v1/stream/leases/", + "/v1/stream/leases/lse_1/frames", + "/v1/stream/devices/lse_1", + "/v1/stream/leases/%zz", + ]) { + expect(leaseIdOf(path), path).toBeUndefined(); + } + }); + + it("unescapes an id that arrived escaped", () => { + expect(leaseIdOf("/v1/stream/leases/lse%5F1")).toBe("lse_1"); + }); +}); + +describe("handleStreamRequest", () => { + it("refuses a path that names no lease with the body shape every other route uses", async () => { + const response = await handleStreamRequest( + { link: linkThat(async () => stream()) }, + get("/v1/stream/leases"), + OPERATOR, + leaseIdOf("/v1/stream/leases"), + ); + expect(response.status).toBe(404); + expect(await response.json()).toEqual({ + error: { code: "UNKNOWN_LEASE", message: "This path names no stream lease" }, + }); + }); + + it("refuses to be written to", async () => { + const response = await handleStreamRequest( + { link: linkThat(async () => stream()) }, + new Request("http://daemon/v1/stream/leases/lse_1", { method: "POST" }), + OPERATOR, + "lse_1", + ); + expect(response.status).toBe(405); + expect(((await response.json() as { error: { code: string } }).error.code)).toBe("BAD_REQUEST"); + }); + + it("carries a link's refusal out as its own status and code", async () => { + // why: the codes are the point. A gateway forwards a worker's refusal, so a viewer that is told 415 + // learned something the gateway could not have invented, and a client that reads `error.code` needs + // no special case for a stream that never started. + const refusal = new StreamRefusalError(415, "STREAM_NOT_SUPPORTED", "not a simulator"); + const response = await handleStreamRequest( + { link: linkThat(async () => Promise.reject(refusal)) }, + get(), + OPERATOR, + "lse_1", + ); + expect(response.status).toBe(415); + expect(await response.json()).toEqual({ + error: { code: "STREAM_NOT_SUPPORTED", message: "not a simulator" }, + }); + }); + + it("answers an unexpected failure as an internal one rather than leaking a bare message", async () => { + const response = await handleStreamRequest( + { link: linkThat(async () => Promise.reject(new Error("spawn ENOENT"))) }, + get(), + OPERATOR, + "lse_1", + ); + expect(response.status).toBe(500); + expect(((await response.json() as { error: { code: string } }).error.code)).toBe("INTERNAL"); + }); + + it("answers a stream with its picture described in the headers", async () => { + const response = await handleStreamRequest( + { link: linkThat(async () => stream("mjpeg")) }, + get(), + OPERATOR, + "lse_1", + ); + expect(response.status).toBe(200); + expect([...response.headers].sort()).toEqual( + [ + ["cache-control", "no-store"], + ["content-type", "application/vnd.simlock.stream+mjpeg"], + ["simlock-stream-codec", "mjpeg"], + ["simlock-stream-fps", "30"], + ["simlock-stream-height", "720"], + ["simlock-stream-width", "330"], + ["x-simlock-lease", "lse_1"], + ].sort(), + ); + }); + +}); + +describe("streamResponse", () => { + it("hands the body out unchanged", async () => { + const bytes = new Uint8Array([0, 0, 0, 5, 5, 123]); + const response = streamResponse("lse_1", { + ...stream(), + body: new ReadableStream({ + start: (controller) => { + controller.enqueue(bytes); + controller.close(); + }, + }), + }); + // why: nothing between a simulator's framebuffer and a browser re-frames the envelope, so a reader + // of this body must see exactly the bytes the capture program wrote. + expect(new Uint8Array(await response.arrayBuffer())).toEqual(bytes); + }); +}); diff --git a/src/stream/service.ts b/src/stream/service.ts new file mode 100644 index 00000000..b0483cc2 --- /dev/null +++ b/src/stream/service.ts @@ -0,0 +1,101 @@ +import type { CapturedStream } from "./capture.js"; +import type { StreamCaller } from "./caller.js"; +import { noSuchStream, StreamRefusalError } from "./refusal.js"; + +export interface StreamLink { + serve(leaseId: string, caller: StreamCaller, signal: AbortSignal): Promise; +} + +export interface StreamServiceOptions { + readonly link: StreamLink; + readonly logger?: { + info(message: string, fields?: Record): void; + warn(message: string, fields?: Record): void; + }; +} + +export const STREAM_PREFIX = "stream"; + +export function leaseIdOf(pathname: string): string | undefined { + const match = /^\/v1\/stream\/leases\/([^/]+)$/.exec(pathname); + const encoded = match?.[1]; + if (encoded === undefined) return undefined; + try { + return decodeURIComponent(encoded); + } catch { + return undefined; + } +} + +export async function handleStreamRequest( + options: StreamServiceOptions, + request: Request, + caller: StreamCaller, + leaseId: string | undefined, +): Promise { + if (leaseId === undefined) return refusalResponse(noSuchStream()); + if (request.method !== "GET") { + // why: 405 rather than a 404 for a path that does exist -- a caller that posted to a stream is one + // command away from the right ask, and "no such thing" would send it looking elsewhere. + return refusalResponse( + new StreamRefusalError(405, "BAD_REQUEST", "A stream is read, not written"), + ); + } + + let stream: CapturedStream; + try { + stream = await options.link.serve(leaseId, caller, request.signal); + } catch (error) { + const refusal = + error instanceof StreamRefusalError + ? error + : new StreamRefusalError(500, "INTERNAL", error instanceof Error ? error.message : String(error)); + options.logger?.warn("Screen stream refused", { + code: refusal.code, + leaseId, + message: refusal.message, + }); + return refusalResponse(refusal); + } + + options.logger?.info("Screen stream opened", { + codec: stream.meta.codec, + height: stream.meta.height, + leaseId, + width: stream.meta.width, + }); + return streamResponse(leaseId, stream); +} + +export function streamResponse(leaseId: string, stream: CapturedStream): Response { + const { meta } = stream; + // why: the codec and size are headers as well as the body's first frame, because a browser must + // decide how to read a body before it has read any of it, and because a gateway reads the headers + // instead of the body -- which is what lets it relay video without touching a video byte. + return new Response(stream.body, { + headers: { + "cache-control": "no-store", + "content-type": `application/vnd.simlock.stream+${meta.codec}`, + "simlock-stream-codec": meta.codec, + "simlock-stream-fps": String(meta.fps), + "simlock-stream-height": String(meta.height), + "simlock-stream-width": String(meta.width), + "x-simlock-lease": leaseId, + }, + status: 200, + }); +} + +// why: one body shape for every refusal this route can make -- `{ "error": { "code", "message" } }`, +// the shape every other route answers with -- so a client that reads `error.code` needs no special +// case for a stream that never started, and a gateway can forward a worker's code unchanged. +function refusalResponse(refusal: StreamRefusalError): Response { + return json(refusal.status, { error: { code: refusal.code, message: refusal.message } }); +} + +function json(status: number, body: unknown): Response { + return new Response(JSON.stringify(body), { + headers: { "content-type": "application/json" }, + status, + }); +} diff --git a/src/stream/worker-link.test.ts b/src/stream/worker-link.test.ts new file mode 100644 index 00000000..fa6f118c --- /dev/null +++ b/src/stream/worker-link.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from "vitest"; + +import { createWorkerStreamLink, type StreamDevice, type StreamRegistry } from "./worker-link.js"; +import { StreamRefusalError } from "./refusal.js"; +import type { StreamCaller } from "./caller.js"; + +const AGENT: StreamCaller = { principal: "tok_agent", requesterId: "agent-1", role: "agent" }; +const OPERATOR: StreamCaller = { principal: "tok_op", role: "operator" }; + +function registry( + leases: Readonly>, + devices: Readonly> = {}, +): StreamRegistry { + return { + deviceOfLease: (leaseId) => devices[leaseId], + leaseOwner: (leaseId) => leases[leaseId], + }; +} + +const HELD = { lse_mine: { ownerId: "tok_agent", requesterId: "agent-1" } }; +const SIMULATOR = { lse_mine: { address: "UDID", physical: false, platform: "ios" } }; + +async function refusalOf( + link: { serve(id: string, caller: StreamCaller, signal: AbortSignal): Promise }, + leaseId: string, + caller: StreamCaller, +): Promise { + const served = await link.serve(leaseId, caller, new AbortController().signal).then( + () => undefined, + (error: unknown) => error, + ); + expect(served, "the stream was expected to be refused").toBeInstanceOf(StreamRefusalError); + return served as StreamRefusalError; +} + +function linkFor(registryValue: StreamRegistry, options: { available?: boolean } = {}) { + return createWorkerStreamLink({ + ...(options.available === undefined + ? {} + : { available: async () => options.available === true }), + args: [], + command: "/does/not/matter", + leaseEnvironment: {}, + registry: registryValue, + }); +} + +describe("the worker's stream link", () => { + it("refuses a lease it does not hold with the answer every other route gives", async () => { + const refusal = await refusalOf( + linkFor(registry(HELD, SIMULATOR)), + "lse_nope", + OPERATOR, + ); + expect([refusal.status, refusal.code]).toEqual([404, "UNKNOWN_LEASE"]); + }); + + it("answers an agent reaching for another requester's lease exactly as if it did not exist", async () => { + const link = linkFor( + registry( + { lse_theirs: { ownerId: "tok_other", requesterId: "agent-2" } }, + { lse_theirs: SIMULATOR["lse_mine"] }, + ), + ); + const refusal = await refusalOf(link, "lse_theirs", AGENT); + expect([refusal.status, refusal.code]).toEqual([404, "UNKNOWN_LEASE"]); + // why: the message must not say "not yours" either -- an agent that can tell a lease it cannot have + // from a lease that does not exist can enumerate the fleet's leases by asking for their pictures. + expect(refusal.message).toBe("Unknown lease: lse_theirs"); + }); + + it("refuses a device that can never produce a picture, naming that as its own reason", async () => { + for (const [name, device] of [ + ["a physical device", { address: "UDID", physical: true, platform: "ios" }], + ["an emulator", { address: "emulator-5554", physical: false, platform: "android" }], + ] as const) { + const link = linkFor(registry(HELD, { lse_mine: device })); + const refusal = await refusalOf(link, "lse_mine", OPERATOR); + expect([refusal.status, refusal.code], name).toEqual([415, "STREAM_NOT_SUPPORTED"]); + } + }); + + it("says a capture program that is not there is unavailable rather than a bad request", async () => { + // why: 503 and not 400 -- the lease and the ask are both fine, so the viewer that asks again after + // its operator installs the tool is right to ask the same thing. + const refusal = await refusalOf( + linkFor(registry(HELD, SIMULATOR), { available: false }), + "lse_mine", + OPERATOR, + ); + expect([refusal.status, refusal.code]).toEqual([503, "STREAM_UNAVAILABLE"]); + expect(refusal.message).toContain("/does/not/matter"); + }); + + it("refuses on ownership before it looks for a capture program", async () => { + // why: the order is what keeps an agent's reach the only question asked of an agent. A link that + // checked its tooling first would answer an agent with a different refusal for a lease that exists + // on this worker than for one that does not. + const link = createWorkerStreamLink({ + available: async () => false, + args: [], + command: "/does/not/matter", + leaseEnvironment: {}, + registry: registry(HELD, SIMULATOR), + }); + const refusal = await refusalOf(link, "lse_nope", AGENT); + expect([refusal.status, refusal.code]).toEqual([404, "UNKNOWN_LEASE"]); + }); + + it("an operator reaches a lease without naming a requester", async () => { + // why: an operator owns the whole machine, so the refusal this link must NOT raise is the point -- + // a link that demanded a requester id it never gets from an operator's token would answer an + // operator's own console with 404s. Reaching the spawn is as far as this goes without a real tool. + const refusal = await refusalOf(linkFor(registry(HELD, SIMULATOR)), "lse_mine", OPERATOR); + expect(refusal.code).toBe("STREAM_UNAVAILABLE"); + }); +}); diff --git a/src/stream/worker-link.ts b/src/stream/worker-link.ts new file mode 100644 index 00000000..7652bdce --- /dev/null +++ b/src/stream/worker-link.ts @@ -0,0 +1,100 @@ +import { captureStream, type CapturedStream } from "./capture.js"; +import type { CaptureRegistry, RunningCapture } from "./captures.js"; +import type { StreamCaller } from "./caller.js"; +import { notStreamable, streamUnavailable, unknownLease } from "./refusal.js"; +import type { StreamLink } from "./service.js"; + +export interface StreamLeaseOwner { + readonly ownerId: string; + readonly requesterId: string; +} + +export interface StreamDevice { + readonly address: string; + readonly physical: boolean; + readonly platform: string; +} + +export interface StreamRegistry { + leaseOwner(leaseId: string): StreamLeaseOwner | undefined; + deviceOfLease(leaseId: string): StreamDevice | undefined; +} + +export interface WorkerStreamLinkOptions { + readonly registry: StreamRegistry; + readonly command: string; + readonly args: readonly string[]; + readonly leaseEnvironment: Readonly>; + readonly onLog?: (line: string) => void; + readonly available?: () => Promise; + readonly captures?: CaptureRegistry; +} + +const noopRegistry: CaptureRegistry = { + leases: () => [], + stop: () => 0, + track: (_leaseId, capture) => capture, +}; + +export function createWorkerStreamLink(options: WorkerStreamLinkOptions): StreamLink { + return { + async serve(leaseId, caller, signal): Promise { + // why: ownership is proved before anything is asked of the machine. A link that looked for its + // capture program first would answer an agent with one refusal for a lease this worker holds and + // another for a lease that does not exist, which is a way of listing the fleet's leases. + requireLease(options.registry, leaseId, caller); + if ((await options.available?.()) === false) { + throw streamUnavailable(`No screen capture program to run at ${options.command}`); + } + const device = options.registry.deviceOfLease(leaseId); + if (device === undefined) throw unknownLease(leaseId); + if (device.platform !== "ios" || device.physical) { + throw notStreamable( + "This lease is not on an iOS simulator, and a live screen is captured from a simulator only", + ); + } + // why: the capture registers itself through `onEnd` before the handle it will close exists, so + // the closure reads `running` when the tool actually ends; registering late would let a tool + // that died in its first milliseconds leave an entry nothing can clear. + let running: RunningCapture = { close: () => undefined }; + let stream: CapturedStream; + try { + stream = await captureStream({ + args: [...options.args, device.address], + command: options.command, + env: { ...process.env, ...options.leaseEnvironment }, + onEnd: () => running.close(), + ...(options.onLog === undefined ? {} : { onLog: options.onLog }), + }); + } catch (error) { + // why: a tool that died before its first frame is an unavailable picture rather than a broken + // request -- the lease is fine and the retry that succeeds needs no different ask, so the + // status says "not right now" and not "you did something wrong". + throw streamUnavailable(error instanceof Error ? error.message : String(error)); + } + running = (options.captures ?? noopRegistry).track(leaseId, stream); + // why: the listener, and not only the body's cancel path, because a request aborted before its + // first frame has no body being read yet to cancel -- which is what a page opened and closed + // quickly does, and it would otherwise leave a capture running for a viewer that is gone. + if (signal.aborted) running.close(); + else signal.addEventListener("abort", () => running.close(), { once: true }); + return { ...stream, close: () => running.close() }; + }, + }; +} + +// why: ownership is proved against this worker's own registry rather than taken from whoever relayed +// the request. A caller that names a requester is checked against the lease's requester (the rule +// `device.exec` follows, ADR 0005 §19b); one that names none is checked against the principal that +// owns the lease. An unknown lease and one held by someone else get the same answer, because an +// agent cannot be allowed to discover which leases exist by asking for pictures of them. +function requireLease(registry: StreamRegistry, leaseId: string, caller: StreamCaller): void { + const owner = registry.leaseOwner(leaseId); + if (owner === undefined) throw unknownLease(leaseId); + if (caller.role === "operator") return; + const held = + caller.requesterId === undefined + ? owner.ownerId === caller.principal + : owner.requesterId === caller.requesterId; + if (!held) throw unknownLease(leaseId); +} diff --git a/ui/src/screen/stream.test.ts b/ui/src/screen/stream.test.ts new file mode 100644 index 00000000..635bb35b --- /dev/null +++ b/ui/src/screen/stream.test.ts @@ -0,0 +1,581 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { + codecString, + parseMeta, + readStream, + type StreamMeta, + type StreamSink, + type StreamStats, + TAG, +} from "./stream"; + +/** + * why: the unit project has no browser, and the claims worth pinning here are what this reader hands + * the two browser objects it drives. So these record exactly that instead of standing in for a + * decoder's own behaviour, which is the browser's to have. + */ +let initialBacklog = 0; +let created: FakeDecoder[] = []; +let bitmaps: Blob[] = []; + +const BROKEN = 0xee; + +class FakeFrame { + closed = 0; + + constructor(readonly displayHeight: number) {} + + close(): void { + this.closed += 1; + } +} + +class FakeDecoder { + readonly configs: { codec: string; description: Uint8Array }[] = []; + readonly chunks: { data: Uint8Array; type: string }[] = []; + resets = 0; + closed = 0; + backlog = initialBacklog; + + constructor( + readonly callbacks: { + output: (frame: FakeFrame) => void; + error: (error: Error) => void; + }, + ) { + created.push(this); + } + + get decodeQueueSize(): number { + return this.backlog; + } + + configure(config: { codec: string; description: Uint8Array }): void { + this.configs.push({ codec: config.codec, description: config.description }); + } + + decode(chunk: { init: { data: Uint8Array; type: string } }): void { + const { data, type } = chunk.init; + this.chunks.push({ data, type }); + // why: a decoder spends a backlog on the frame it was asked for, which is the only thing the reader + // can watch in order to decide to drop; the BROKEN payload is the one case where the fake reports + // the failure a real decoder would report on its own. + if (data[0] === BROKEN) this.callbacks.error(new Error("decoded to nothing")); + if (this.backlog > 0) this.backlog -= 1; + else this.callbacks.output(new FakeFrame(data.length)); + } + + reset(): void { + this.resets += 1; + this.backlog = 0; + } + + close(): void { + this.closed += 1; + } +} + +class Recorded { + readonly images: { height: number; width: number }[] = []; + readonly metas: StreamMeta[] = []; + readonly stats: StreamStats[] = []; + readonly ends: (string | undefined)[] = []; + readonly frames: FakeFrame[] = []; + + sink(): StreamSink { + return { + onEnd: (reason) => { + this.ends.push(reason); + }, + onFrame: (frame) => { + this.frames.push(frame as unknown as FakeFrame); + }, + onImage: (image) => { + this.images.push(image); + }, + onMeta: (meta) => { + this.metas.push(meta); + }, + onStats: (stats) => { + this.stats.push(stats); + }, + }; + } + + get last(): StreamStats { + const stats = this.stats.at(-1); + if (stats === undefined) throw new Error("the reader never reported stats"); + return stats; + } +} + +/** + * why: the envelope's promise is what is under test — a 4-byte big-endian length that covers the tag + * byte as well as the payload, the tag, then the payload — so it is written out here in full rather + * than hidden behind a helper a reader would have to trust. + */ +function frame(tag: number, payload: object | number[] | Uint8Array | string): Uint8Array { + const bytes = + typeof payload === "string" + ? new TextEncoder().encode(payload) + : payload instanceof Uint8Array + ? payload + : Array.isArray(payload) + ? Uint8Array.from(payload) + : new TextEncoder().encode(JSON.stringify(payload)); + const total = bytes.length + 1; + const out = new Uint8Array(bytes.length + 5); + out[0] = (total >>> 24) & 0xff; + out[1] = (total >>> 16) & 0xff; + out[2] = (total >>> 8) & 0xff; + out[3] = total & 0xff; + out[4] = tag; + out.set(bytes, 5); + return out; +} + +function meta( + payload: Record = { codec: "h264", fps: 30, height: 4, width: 4 }, +): Uint8Array { + return frame(TAG.meta, payload); +} + +function avcC(profile = 0x64, compatibility = 0x00, level = 0x1e): Uint8Array { + return Uint8Array.from([0x01, profile, compatibility, level, 0xff, 0xe1]); +} + +/** + * why: one chunk per read, because that is what a streamed response does — a body is never handed over + * in units of the encoder's frames. + */ +function bodyOf(chunks: readonly Uint8Array[]): ReadableStream { + const rest = [...chunks]; + return new ReadableStream({ + pull(controller) { + const next = rest.shift(); + if (next === undefined) controller.close(); + else controller.enqueue(next); + }, + }); +} + +function split(bytes: Uint8Array, sizes: readonly number[]): Uint8Array[] { + const out: Uint8Array[] = []; + let at = 0; + for (const size of sizes) { + out.push(bytes.subarray(at, Math.min(at + size, bytes.length))); + at += size; + if (at >= bytes.length) break; + } + if (at < bytes.length) out.push(bytes.subarray(at)); + return out; +} + +function concatAll(parts: readonly Uint8Array[]): Uint8Array { + const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0)); + let at = 0; + for (const part of parts) { + out.set(part, at); + at += part.length; + } + return out; +} + +async function read( + chunks: readonly Uint8Array[], + recorded = new Recorded(), +): Promise { + await readStream(bodyOf(chunks), recorded.sink(), new AbortController().signal); + return recorded; +} + +function decoder(): FakeDecoder { + const last = created.at(-1); + if (last === undefined) throw new Error("the reader never built a decoder"); + return last; +} + +function payloadOf(index: number): number[] { + const chunk = decoder().chunks[index]; + if (chunk === undefined) throw new Error(`the reader handed over no chunk ${index}`); + return [...chunk.data]; +} + +beforeEach(() => { + initialBacklog = 0; + created = []; + bitmaps = []; + vi.stubGlobal("VideoDecoder", FakeDecoder); + vi.stubGlobal("EncodedVideoChunk", class { + constructor(readonly init: { data: Uint8Array; type: string }) {} + }); + vi.stubGlobal("createImageBitmap", async (blob: Blob) => { + bitmaps.push(blob); + return { close: () => undefined, height: 6, width: 7 }; + }); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("reading the envelope", () => { + it("reads a stream that arrives one whole frame per read", async () => { + const recorded = await read([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1, 2, 3]), + frame(TAG.delta, [4, 5]), + ]); + + expect(recorded.metas).toEqual([{ codec: "h264", fps: 30, height: 4, width: 4 }]); + expect(decoder().configs).toEqual([{ codec: "avc1.64001e", description: avcC() }]); + expect(decoder().chunks.map((chunk) => [...chunk.data])).toEqual([[1, 2, 3], [4, 5]]); + expect(decoder().chunks.map((chunk) => chunk.type)).toEqual(["key", "delta"]); + expect(recorded.last.painted).toBe(2); + expect(recorded.ends).toEqual([undefined]); + }); + + it("reads a frame whose four-byte length is cut across two reads", async () => { + const whole = concatAll([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [7, 8, 9, 10]), + ]); + + const recorded = await read(split(whole, [3])); + + expect(payloadOf(0)).toEqual([7, 8, 9, 10]); + expect(recorded.last.error).toBeUndefined(); + expect(recorded.ends).toEqual([undefined]); + }); + + it("reads a frame whose payload is cut mid-way, arriving one byte per read", async () => { + const whole = concatAll([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1, 2, 3]), + ]); + + const recorded = await read(split(whole, [...whole].map(() => 1))); + + expect(payloadOf(0)).toEqual([1, 2, 3]); + expect(recorded.last.error).toBeUndefined(); + }); + + it("keeps the frame before a cut inside the next one, and hands over both", async () => { + const both = concatAll([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1, 2, 3, 4]), + ...split(frame(TAG.delta, [5, 6, 7]), [6]), + ]); + + const recorded = await read([both]); + + expect(decoder().chunks.map((chunk) => [...chunk.data])).toEqual([[1, 2, 3, 4], [5, 6, 7]]); + expect(decoder().chunks.map((chunk) => chunk.type)).toEqual(["key", "delta"]); + expect(recorded.last.painted).toBe(2); + }); + + it("refuses a length larger than any frame the tool writes, and lets go of the body", async () => { + const cancelled: unknown[] = []; + const body = new ReadableStream({ + cancel: (reason) => { + cancelled.push(reason); + }, + start(controller) { + controller.enqueue(Uint8Array.from([0xff, 0xff, 0xff, 0xff, TAG.keyframe, 0, 0, 0])); + }, + }); + const recorded = new Recorded(); + + await readStream(body, recorded.sink(), new AbortController().signal); + + expect(recorded.ends).toEqual(["The stream is not a frame envelope"]); + // why: the request is what keeps the worker capturing, so a reader that gives up has to close it — + // otherwise one broken byte leaves a device being filmed for a viewer that already left. + expect(cancelled).toEqual(["The stream is not a frame envelope"]); + expect(created).toHaveLength(0); + }); + + it("refuses a length that claims a frame with nothing in it", async () => { + const recorded = await read([Uint8Array.from([0, 0, 0, 0, TAG.keyframe])]); + + expect(recorded.ends).toEqual(["The stream is not a frame envelope"]); + }); + + it("reads a stream whose meta frame is the first frame and reports the picture it names", async () => { + const recorded = await read([ + frame( + TAG.meta, + '{"udid":"X","codec":"h264","width":330,"height":720,"fps":30,"deviceSet":"default"}', + ), + frame(TAG.description, avcC()), + ]); + + expect(recorded.metas).toEqual([{ codec: "h264", fps: 30, height: 720, width: 330 }]); + }); + + it("refuses a body whose first frame is not JSON as what it is", async () => { + const recorded = await read([frame(TAG.meta, "{not json")]); + + expect(recorded.ends).toEqual(["The stream's meta frame is not JSON"]); + }); + + it("refuses a codec no browser here can show, and says which", async () => { + const recorded = await read([meta({ codec: "vp9", fps: 30, height: 4, width: 4 })]); + + expect(recorded.ends).toEqual(["This console cannot show a vp9 stream"]); + }); + + it("refuses a video byte that arrives before any parameter sets, rather than decoding it", async () => { + const recorded = await read([meta(), frame(TAG.keyframe, [1, 2])]); + + expect(recorded.ends).toEqual(["The stream sent video before it sent its parameter sets"]); + expect(created).toHaveLength(0); + }); + + it("configures the decoder again when the stream sends new parameter sets mid-picture", async () => { + const recorded = await read([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1]), + frame(TAG.description, avcC(0x4d, 0x03, 0x1f)), + frame(TAG.keyframe, [2]), + ]); + + expect(decoder().configs.map((config) => config.codec)).toEqual([ + "avc1.64001e", + "avc1.4d031f", + ]); + expect([...(decoder().configs[1]?.description.slice(1, 4) ?? [])]).toEqual([0x4d, 0x03, 0x1f]); + // why: a decoder still holding frames of the old parameter sets has to be emptied first, or the + // first frame after the change is decoded against the wrong picture. + expect(decoder().resets).toBe(1); + expect(recorded.last.error).toBeUndefined(); + }); + + it("refuses bytes claimed to be parameter sets that are not an avcC, and names that", async () => { + const recorded = await read([ + meta(), + frame(TAG.description, Uint8Array.from([0x02, 0x64, 0x00, 0x1e])), + ]); + + expect(recorded.ends).toEqual(["The stream's parameter sets are not an AVC description"]); + expect(created).toHaveLength(0); + }); + + it("ends with the decoder's own words when it fails, and lets go of the body", async () => { + const cancelled: unknown[] = []; + const recorded = new Recorded(); + // why: the body is left open, as a live one is — what this has to prove is that a stream still + // running gets closed, and a body that already ended has nothing left to close. + const body = new ReadableStream({ + cancel: (reason) => { + cancelled.push(reason); + }, + start(controller) { + controller.enqueue(meta()); + controller.enqueue(frame(TAG.description, avcC())); + controller.enqueue(frame(TAG.keyframe, [BROKEN, 0, 0])); + }, + }); + + await readStream(body, recorded.sink(), new AbortController().signal); + + expect(recorded.ends).toEqual(["The decoder failed: decoded to nothing"]); + expect(cancelled).toEqual(["The decoder failed: decoded to nothing"]); + expect(recorded.last.error).toBe("The decoder failed: decoded to nothing"); + }); + + it("closes the body when the viewer aborts, and ends once with nothing to show", async () => { + const cancelled: unknown[] = []; + const controller = new AbortController(); + const body = new ReadableStream({ + cancel: (reason) => { + cancelled.push(reason); + }, + start(handle) { + handle.enqueue(meta()); + }, + }); + const recorded = new Recorded(); + + const reading = readStream(body, recorded.sink(), controller.signal); + await new Promise((resolve) => setTimeout(resolve, 0)); + controller.abort(); + await reading; + + expect(cancelled).toHaveLength(1); + expect(recorded.ends).toEqual([undefined]); + }); + + it("says nothing when the body fails at the moment the viewer goes away", async () => { + const controller = new AbortController(); + let handle: ReadableStreamDefaultController | undefined; + // why: a body that is aborted has usually already failed for the browser's own reason — a dropped + // connection errors the stream as the tab goes away — and the viewer that asked to leave is not the + // one to be told about it. The pull never settles, so the read is parked when both happen. + const body = new ReadableStream({ + pull() { + return new Promise(() => undefined); + }, + start(source) { + handle = source; + source.enqueue(meta()); + }, + }); + const recorded = new Recorded(); + + const reading = readStream(body, recorded.sink(), controller.signal); + await new Promise((resolve) => setTimeout(resolve, 0)); + handle?.error(new Error("the connection dropped")); + controller.abort(); + await reading; + + expect(recorded.ends).toEqual([undefined]); + expect(recorded.last.error).toBeUndefined(); + }); + + it("tells the viewer nothing more when the decoder fails after the stream is over", async () => { + const recorded = await read([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1]), + ]); + + // why: a decoder returns the frame it was holding on its own time, which can be after the body + // ended; a pane told twice shows a reason that was already replaced. + decoder().callbacks.error(new Error("late")); + + expect(recorded.ends).toEqual([undefined]); + expect(recorded.last.error).toBeUndefined(); + }); + + it("reports the bytes read and the frames painted as rates over the whole stream", async () => { + const whole = concatAll([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1, 2, 3, 4, 5, 6, 7, 8]), + ]); + + const recorded = await read([whole]); + + const stats = recorded.last; + expect(stats.fed).toBe(1); + expect(stats.painted).toBe(1); + expect(stats.dropped).toBe(0); + // why: how long the reads took is the browser's, so the claim is the arithmetic the two figures + // have to agree on: kilobits of every byte of the envelope, over the span they took. + expect(stats.kilobitsPerSecond).toBeGreaterThanOrEqual((whole.length * 8) / 1000); + expect(stats.framesPerSecond).toBeGreaterThan(0); + }); +}); + +describe("a decoder that falls behind", () => { + it("stops handing deltas over once the queue is over the bound and waits for a keyframe", async () => { + initialBacklog = 20; + + const recorded = await read([ + meta(), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1]), + frame(TAG.delta, [2]), + frame(TAG.delta, [3]), + frame(TAG.keyframe, [4]), + frame(TAG.delta, [5]), + ]); + + // why: the keyframe that filled the queue is the last frame the decoder was given, the two deltas + // behind it are the ones refused, and the picture only resumes at the next keyframe. + expect(decoder().chunks.map((chunk) => [...chunk.data])).toEqual([[1], [4], [5]]); + expect(decoder().resets).toBe(1); + expect(decoder().configs.map((config) => config.codec)).toEqual([ + "avc1.64001e", + "avc1.64001e", + ]); + expect(recorded.last.dropped).toBe(3); + expect(recorded.last.error).toBeUndefined(); + }); +}); + +describe("a stream carried as stills", () => { + it("paints each delta as a JPEG and never builds a decoder", async () => { + const first = Uint8Array.from([0xff, 0xd8, 1, 2, 0xff, 0xd9]); + const second = Uint8Array.from([0xff, 0xd8, 3, 4, 0xff, 0xd9]); + + const recorded = await read([ + meta({ codec: "mjpeg", fps: 12, height: 6, width: 7 }), + frame(TAG.delta, first), + frame(TAG.delta, second), + ]); + + expect(created).toHaveLength(0); + expect(bitmaps.map((blob) => blob.size)).toEqual([first.length, second.length]); + expect(recorded.last.painted).toBe(2); + expect(recorded.metas).toEqual([{ codec: "mjpeg", fps: 12, height: 6, width: 7 }]); + }); + + it("paints a still seed frame for a video stream as well as the frames after it", async () => { + const seed = Uint8Array.from([0xff, 0xd8, 9, 0xff, 0xd9]); + + const recorded = await read([ + meta(), + frame(TAG.seed, seed), + frame(TAG.description, avcC()), + frame(TAG.keyframe, [1]), + ]); + + expect(bitmaps.map((blob) => blob.size)).toEqual([seed.length]); + expect(decoder().chunks).toHaveLength(1); + expect(recorded.last.painted).toBe(2); + }); +}); + +describe("the codec string an avcC describes", () => { + it("keeps a parameter byte's leading zero, which is what makes the name one a browser knows", () => { + expect(codecString(avcC(0x64, 0x00, 0x1e))).toBe("avc1.64001e"); + }); + + it("writes each of the three parameters as exactly two digits", () => { + expect(codecString(avcC(0x0a, 0x0b, 0x0c))).toBe("avc1.0a0b0c"); + expect(codecString(avcC(0x6d, 0xc8, 0x2a))).toBe("avc1.6dc82a"); + expect(codecString(avcC(0x00, 0x00, 0x00))).toBe("avc1.000000"); + }); + + it("has no name for bytes that are not an avcC", () => { + expect(codecString(Uint8Array.from([0x02, 0x64, 0x00, 0x1e]))).toBeUndefined(); + expect(codecString(Uint8Array.from([0x01, 0x64, 0x00]))).toBeUndefined(); + expect(codecString(new Uint8Array(0))).toBeUndefined(); + }); +}); + +describe("what a meta frame claims", () => { + it("takes the picture's size and rate as the tool wrote them", () => { + const payload = new TextEncoder().encode( + JSON.stringify({ codec: "h264", fps: 24, height: 720, width: 330 }), + ); + + expect(parseMeta(payload)).toEqual({ + meta: { codec: "h264", fps: 24, height: 720, width: 330 }, + }); + }); + + it("reads a size or rate the frame does not carry as zero rather than guessing one", () => { + expect(parseMeta(new TextEncoder().encode(JSON.stringify({ codec: "mjpeg" })))).toEqual({ + meta: { codec: "mjpeg", fps: 0, height: 0, width: 0 }, + }); + }); + + it("reads a size the frame gave as something else as zero too", () => { + const payload = new TextEncoder().encode( + JSON.stringify({ codec: "h264", height: null, width: "330" }), + ); + + expect(parseMeta(payload)).toEqual({ + meta: { codec: "h264", fps: 0, height: 0, width: 0 }, + }); + }); +}); diff --git a/ui/src/screen/stream.ts b/ui/src/screen/stream.ts new file mode 100644 index 00000000..1c365681 --- /dev/null +++ b/ui/src/screen/stream.ts @@ -0,0 +1,359 @@ +/** + * why: this file reads a wire format it does not own. The body of `GET /v1/stream/leases/` is the + * capture program's own envelope, byte for byte — a 4-byte big-endian length covering the tag byte that + * follows it, a tag, then the payload — because nothing between a simulator's framebuffer and a browser + * re-frames it: a gateway relays a stream as bytes and never reads one. So the reader has to match the + * writer's promise exactly, and the one cost of reading it wrong is a picture that never arrives. + * + * The envelope is shaped for WebCodecs already: a keyframe's or delta's payload is length-prefixed AVCC + * NAL units, which is what `VideoDecoder.decode` takes, and the description frame's payload is an `avcC` + * blob, which is what `configure` takes. Neither is rewritten on the way. + */ + +export const TAG = { + description: 0x01, + keyframe: 0x02, + delta: 0x03, + seed: 0x04, + meta: 0x05, +} as const; + +// why: the capture tool refuses a frame longer than 8 MiB, so a header claiming more than that is not a +// big frame but a stream this tool never wrote; the bound is what stops a viewer waiting forever for the +// rest of a length a byte-stream desync invented. +const MAX_FRAME_BYTES = 8 * 1024 * 1024; + +const HEADER_BYTES = 5; + +/** + * why: a decoder holding more frames than this is one the picture is running ahead of, and a live view + * of a screen is worth its newest frame only — every frame behind it is latency paid for nothing. Past + * the bound, arriving frames are dropped and the decoder is reset to the encoder's next keyframe: a + * decoder fed a delta whose neighbours it never saw goes on painting a torn picture, which costs a + * viewer more than the short wait a reset costs. + */ +const MAX_DECODE_QUEUE = 8; + +export type StreamCodec = "h264" | "mjpeg"; + +export interface StreamMeta { + readonly codec: StreamCodec; + readonly width: number; + readonly height: number; + readonly fps: number; +} + +/** + * why: three frame counts rather than one, because the three places a frame can stop answer different + * questions — the capture sent it, the decoder was given it, the browser painted it — and the gap + * between two of them is where a slow viewer's problem lives. + */ +export interface StreamStats { + readonly fed: number; + readonly painted: number; + readonly dropped: number; + readonly kilobitsPerSecond: number; + readonly framesPerSecond: number; + readonly error: string | undefined; +} + +/** + * why: everything a stream learns arrives through one of these rather than through a return value, + * because what a viewer does with a frame — draw it, count it, close it — is the viewer's business, and + * this file's job ends at handing frames over in order. + */ +export interface StreamSink { + onMeta(meta: StreamMeta): void; + onFrame(frame: VideoFrame): void; + onImage(image: ImageBitmap): void; + onStats(stats: StreamStats): void; + /** why: exactly once however the stream ended, because a pane told twice shows a reason that was + * already replaced, or two of them at once. */ + onEnd(reason: string | undefined): void; +} + +/** + * why: a stream that fails is closed rather than abandoned. The request *is* the command that keeps the + * capture running on the worker, so a viewer that has given up on a picture has to let go of it here or + * the worker goes on producing one for nobody. + */ +export async function readStream( + body: ReadableStream, + sink: StreamSink, + signal: AbortSignal, +): Promise { + const reader = body.getReader(); + const clock = new Stopwatch(); + const counters = { + decoded: 0, + dropped: 0, + fed: 0, + bytes: 0, + error: undefined as string | undefined, + }; + let ended = false; + let decoder: VideoDecoder | undefined; + let description: Uint8Array | undefined; + let meta: StreamMeta | undefined; + /** why: set when a drop cut the decode chain; nothing after it decodes until the next keyframe. */ + let awaitingKeyframe = false; + + const report = () => { + const seconds = Math.max(clock.elapsed(), 0.001); + sink.onStats({ + dropped: counters.dropped, + error: counters.error, + fed: counters.fed, + framesPerSecond: counters.decoded / seconds, + kilobitsPerSecond: (counters.bytes * 8) / 1000 / seconds, + painted: counters.decoded, + }); + }; + + /** why: the first reason a stream dies is the one a viewer is told — a decoder error that arrives + * afterwards would overwrite the reason the reader already had. */ + const end = (reason?: string) => { + if (ended) return; + ended = true; + if (reason !== undefined && counters.error === undefined) counters.error = reason; + // why: the figures go out before the end is announced, so the last numbers a viewer is shown name + // the failure the stream died of rather than the healthy run two frames before it. + report(); + sink.onEnd(counters.error); + }; + + const fail = (reason: string) => { + end(reason); + void reader.cancel(reason).catch(() => undefined); + }; + + const configure = (bytes: Uint8Array): void => { + const codec = codecString(bytes); + if (codec === undefined) { + fail("The stream's parameter sets are not an AVC description"); + return; + } + if (decoder === undefined) { + if (typeof VideoDecoder === "undefined") { + fail("This browser cannot decode video: it has no WebCodecs"); + return; + } + decoder = new VideoDecoder({ + output: (frame) => { + counters.decoded += 1; + sink.onFrame(frame); + report(); + }, + error: (error) => fail(`The decoder failed: ${message(error)}`), + }); + } + decoder.configure({ + codec, + description: bytes, + // why: a screen is looked at live, so a frame is worth painting the moment it decodes; a decoder + // left to its own defaults holds frames to reorder them and adds latency for nothing. + optimizeForLatency: true, + }); + description = bytes; + }; + + /** why: a decoder still holding frames made under the old parameter sets has to be emptied before new + * ones are configured, or the first frame after the change decodes against the wrong picture. */ + const applyDescription = (bytes: Uint8Array) => { + if (decoder !== undefined) decoder.reset(); + configure(bytes); + awaitingKeyframe = false; + }; + + const decode = (chunk: Uint8Array, type: "delta" | "key") => { + if (decoder === undefined) { + fail("The stream sent video before it sent its parameter sets"); + return; + } + if (type === "key") awaitingKeyframe = false; + else if (awaitingKeyframe) { + counters.dropped += 1; + report(); + return; + } + counters.fed += 1; + const each = 1_000_000 / Math.max(meta?.fps ?? 30, 1); + decoder.decode( + new EncodedVideoChunk({ + data: chunk, + duration: each, + timestamp: counters.fed * each, + type, + }), + ); + if (decoder.decodeQueueSize > MAX_DECODE_QUEUE) { + awaitingKeyframe = true; + counters.dropped += 1; + decoder.reset(); + if (description !== undefined) configure(description); + report(); + } + }; + + /** why: one still that cannot be painted is one frame missed, not a dead stream — a stream carried as + * stills carries no inter-frame state, so the next one is a whole picture on its own. */ + const paintJpeg = async (bytes: Uint8Array) => { + const image = await createImageBitmap(new Blob([bytes.buffer as ArrayBuffer])).catch( + () => undefined, + ); + if (image === undefined) return; + sink.onImage(image); + counters.decoded += 1; + report(); + }; + + // why: closing the reader is what closes the request, and closing the request is the only + // off-command this surface has. + signal.addEventListener( + "abort", + () => void reader.cancel().catch(() => undefined), + { once: true }, + ); + + let buffer: Uint8Array = new Uint8Array(0); + try { + for (;;) { + const step = await reader.read(); + if (step.done) break; + counters.bytes += step.value.length; + buffer = concat(buffer, step.value); + for (;;) { + // why: once a frame has failed, the frames that arrived beside it belong to a stream nobody is + // reading, and feeding a closed decoder would fail again for a reason a viewer never hears. + if (ended) return; + if (buffer.length < HEADER_BYTES) break; + const length = new DataView(buffer.buffer, buffer.byteOffset).getUint32(0); + // why: the length covers the tag byte as well as the payload, so a frame is one byte shorter + // than its header claims. + const total = HEADER_BYTES + length - 1; + if (length < 1 || total > MAX_FRAME_BYTES) { + fail("The stream is not a frame envelope"); + return; + } + if (buffer.length < total) break; + const tag = buffer[4]; + const payload = buffer.subarray(HEADER_BYTES, total); + buffer = buffer.slice(total); + clock.startIfNeeded(); + if (tag === TAG.meta) { + const parsed = parseMeta(payload); + if (parsed.meta === undefined) { + fail(parsed.error); + return; + } + meta = parsed.meta; + sink.onMeta(meta); + } else if (tag === TAG.description) { + applyDescription(copy(payload)); + } else if (tag === TAG.keyframe) { + decode(copy(payload), "key"); + } else if (tag === TAG.delta) { + if (meta?.codec === "mjpeg") await paintJpeg(copy(payload)); + else decode(copy(payload), "delta"); + } else if (tag === TAG.seed) { + await paintJpeg(copy(payload)); + } + } + report(); + } + end(); + } catch (error) { + if (signal.aborted) end(); + else fail(message(error)); + } finally { + try { + decoder?.close(); + } catch { + // why: a decoder that failed has closed itself, and a browser refuses the second close; nothing + // is left running either way. + } + if (!ended) void reader.cancel().catch(() => undefined); + } +} + +export type ParsedMeta = + | { readonly error?: undefined; readonly meta: StreamMeta } + | { readonly error: string; readonly meta?: undefined }; + +export function parseMeta(payload: Uint8Array): ParsedMeta { + let parsed: unknown; + try { + parsed = JSON.parse(new TextDecoder().decode(payload)); + } catch { + return { error: "The stream's meta frame is not JSON" }; + } + // why: a codec no browser here decodes is named rather than treated as the one this file was written + // for, because the alternative is a picture that never arrives and no line explaining it. + const record = (parsed ?? {}) as Record; + const codec = record["codec"]; + if (codec !== "h264" && codec !== "mjpeg") { + return { error: `This console cannot show a ${String(codec)} stream` }; + } + const number = (key: string) => (typeof record[key] === "number" ? record[key] : 0); + return { + meta: { codec, fps: number("fps"), height: number("height"), width: number("width") }, + }; +} + +/** + * why: each of the three parameter bytes is written as exactly two hexadecimal digits, because dropping + * a leading zero — the thing that trimming usually buys — makes a codec name no browser knows. A + * simulator's encoder describes itself as `64 00 1e`, and `avc1.6401e` is refused before a frame is + * tried, so the failure reads as a broken stream rather than as a name. + */ +export function codecString(avcC: Uint8Array): string | undefined { + const [marker, profile, compatibility, level] = avcC; + if ( + marker !== 0x01 || + profile === undefined || + compatibility === undefined || + level === undefined + ) { + return undefined; + } + const part = (byte: number) => byte.toString(16).padStart(2, "0"); + return `avc1.${part(profile)}${part(compatibility)}${part(level)}`; +} + +function concat(head: Uint8Array, tail: Uint8Array): Uint8Array { + const out = new Uint8Array(head.length + tail.length); + out.set(head, 0); + out.set(tail, head.length); + return out; +} + +/** why: a copy, because a subarray shares the buffer a reader handed back, and a chunk outlives the + * read that produced it. */ +function copy(bytes: Uint8Array): Uint8Array { + const out = new Uint8Array(bytes.length); + out.set(bytes); + return out; +} + +function message(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +/** + * why: the span a stream actually ran, not the span the page has been open — a rate averaged over a tab + * that spent a minute in the background is a number no one can act on. + */ +class Stopwatch { + #started = 0; + #running = false; + + startIfNeeded(): void { + if (this.#running) return; + this.#running = true; + this.#started = performance.now(); + } + + elapsed(): number { + return this.#running ? (performance.now() - this.#started) / 1000 : 0; + } +} diff --git a/ui/src/screen/viewer.test.tsx b/ui/src/screen/viewer.test.tsx new file mode 100644 index 00000000..247fa413 --- /dev/null +++ b/ui/src/screen/viewer.test.tsx @@ -0,0 +1,92 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import { ApiError, type ApiClient } from "../api"; +import { ConsoleProvider } from "../console-context"; +import type { Session } from "../session"; +import { refusalOf, ScreenPane } from "./viewer"; + +/** + * why: these are the sentences whoever pressed the button is left with, and each is the answer to a + * different daemon refusal — so each is pinned to the status and the words that produce it, including + * what the daemon's own refusal contributes. + */ +describe("what a refused stream says", () => { + it("says which devices can be streamed when the daemon answers that this one cannot", () => { + const refused = new ApiError( + 415, + undefined, + "This lease is not on an iOS simulator, and the PoC captures a screen only from a simulator", + ); + + expect(refusalOf(refused)).toBe( + "This device cannot be streamed. A live screen is captured from an iOS simulator only.", + ); + }); + + it("keeps the daemon's own reason when it named one, and says what else to check", () => { + const refused = new ApiError(404, undefined, "Unknown lease: lse_1"); + + expect(refusalOf(refused)).toBe( + "Unknown lease: lse_1 A stream is only offered where stream.enabled is on, and only for a lease that is still held.", + ); + }); + + it("keeps a capture program's own words when the worker could not run one", () => { + const refused = new ApiError( + 500, + undefined, + "The screen capture produced no stream (the capture tool exited with code 1)", + ); + + expect(refusalOf(refused)).toBe( + "The screen capture produced no stream (the capture tool exited with code 1)", + ); + }); + + it("keeps the daemon's words for a refusal this console has no advice about", () => { + expect(refusalOf(new ApiError(401, "UNAUTHENTICATED", "Unknown bearer token"))).toBe( + "Unknown bearer token", + ); + }); + + it("says what the browser itself threw, rather than that something went wrong", () => { + expect(refusalOf(new TypeError("Failed to fetch"))).toBe("Failed to fetch"); + expect(refusalOf("nope")).toBe("nope"); + }); +}); + +describe("the pane before anything is asked for", () => { + // why: a pane rendered for a claim about its markup never talks to a daemon, so the services it + // reads are there to satisfy the context, and a call into either would be the finding. + const services = { + api: {} as unknown as ApiClient, + session: {} as unknown as Session, + }; + const html = renderToStaticMarkup( + + + , + ); + + it("offers the one control a screen has, and says nothing runs while it is off", () => { + expect(html).toContain("Show screen"); + expect(html).toContain("Nothing runs while this is off."); + expect(html).toContain("No picture while this is off."); + }); + + it("shows the state as a word, and every figure it cannot yet fill as missing", () => { + const words = [...html.matchAll(/
([^<]*)<\/dt>]*>(.*?)<\/dd>/g)].map( + ([, label = "", value = ""]) => `${label}=${value.replace(/<[^>]+>/g, "").trim()}`, + ); + + expect(words).toEqual([ + "State=off", + "Picture=—", + "Frames=—", + "Rate=—", + "Bandwidth=—", + "Dropped=—", + ]); + }); +}); diff --git a/ui/src/screen/viewer.tsx b/ui/src/screen/viewer.tsx new file mode 100644 index 00000000..715f7b4a --- /dev/null +++ b/ui/src/screen/viewer.tsx @@ -0,0 +1,222 @@ +/** + * why: one button turns a device's picture on and off and nothing else controls it, because this + * surface has no command of its own to send — the request *is* the on command and aborting it *is* the + * off one. So the pane's lifecycle is the capture's lifecycle: closing the page, navigating away, + * signing out and pressing Stop are four ways of doing the same single thing, and a capture cannot + * outlive the pane that asked for it. That is also why nothing here polls, retries, or holds a stream + * open across a navigation. + */ +import { useCallback, useEffect, useRef, useState } from "react"; + +import { ApiError } from "../api"; +import { useApi } from "../console-context"; +import { Panel } from "../layout"; +import { Status, type Tone } from "../status"; +import { readStream, type StreamMeta, type StreamStats } from "./stream"; + +type Phase = "idle" | "starting" | "live" | "stopped"; + +const EMPTY: StreamStats = { + dropped: 0, + error: undefined, + fed: 0, + framesPerSecond: 0, + kilobitsPerSecond: 0, + painted: 0, +}; + +export function ScreenPane({ leaseId }: { readonly leaseId: string }) { + const api = useApi(); + const canvas = useRef(null); + const controller = useRef(undefined); + const [phase, setPhase] = useState("idle"); + const [meta, setMeta] = useState(undefined); + const [stats, setStats] = useState(EMPTY); + const [note, setNote] = useState(undefined); + + const stop = useCallback(() => { + controller.current?.abort(); + controller.current = undefined; + setPhase("stopped"); + }, []); + + // why: leaving the page stops the capture with the same abort the Stop button performs — a page that + // was navigated away from must not leave a device being filmed for a viewer that is no longer there. + // Navigating to another lease is that same event, and asks for the other lease's picture instead. + useEffect(() => stop, [leaseId, stop]); + + const start = useCallback(() => { + const abort = new AbortController(); + controller.current = abort; + setNote(undefined); + setMeta(undefined); + setStats(EMPTY); + setPhase("starting"); + void (async () => { + try { + const { body } = await api.stream( + `/v1/stream/leases/${encodeURIComponent(leaseId)}`, + abort.signal, + ); + setPhase("live"); + await readStream( + body, + { + onEnd: (reason) => { + // why: a stream that ended because this pane asked it to says nothing — the operator + // pressed Stop, and telling them the stream they stopped has stopped is noise. + if (abort.signal.aborted) return; + setNote(reason ?? "The stream ended."); + setPhase("stopped"); + }, + onFrame: (frame) => { + paint(canvas.current, frame.displayWidth, frame.displayHeight, frame); + frame.close(); + }, + onImage: (image) => paint(canvas.current, image.width, image.height, image), + onMeta: setMeta, + onStats: setStats, + }, + abort.signal, + ); + } catch (error) { + if (abort.signal.aborted) return; + setNote(refusalOf(error)); + setPhase("stopped"); + } + })(); + }, [api, leaseId]); + + const live = phase === "live" || phase === "starting"; + const failure = stats.error ?? (phase === "stopped" ? note : undefined); + const tone: Tone = failure !== undefined ? "error" : phase === "live" ? "ok" : "idle"; + // why: the canvas is mounted as soon as a picture has been asked for rather than when the first frame + // arrives — a frame can arrive before React has painted anything, and a pane that mounts its canvas + // in reply to that frame has nothing to draw the first picture on. It goes away with the stream + // rather than staying: a last frame kept on the page after Stop looks like a live device and shows a + // screen its viewer has already asked not to see. + const shown = live; + const waiting = shown && stats.painted === 0; + return ( + + {live ? "Stop" : "Show screen"} + + } + > + {shown ? ( + + ) : ( +

No picture while this is off.

+ )} + {waiting ? ( +

+ {phase === "starting" + ? "Starting the capture on the worker." + : "Waiting for the first frame."} +

+ ) : null} +
+
+
State
+
+ {stateWord(phase, failure !== undefined)} +
+
+
+
Picture
+
{meta === undefined ? "—" : pictureOf(meta)}
+
+
+
Frames
+
{counted(stats, (frames) => `${frames}`)}
+
+
+
Rate
+
{counted(stats, () => `${stats.framesPerSecond.toFixed(1)} fps`)}
+
+
+
Bandwidth
+
+ {counted(stats, () => `${Math.round(stats.kilobitsPerSecond)} kbit/s`)} +
+
+
+
Dropped
+
{counted(stats, () => `${stats.dropped}`)}
+
+
+ {note === undefined ? null : ( +

+ {note} +

+ )} +
+ ); +} + +function counted(stats: StreamStats, show: (frames: number) => string): string { + return stats.painted === 0 ? "—" : show(stats.painted); +} + +function pictureOf(meta: StreamMeta): string { + return `${meta.width} × ${meta.height} ${meta.codec}`; +} + +function stateWord(phase: Phase, failed: boolean): string { + if (failed) return "failed"; + if (phase === "live") return "live"; + if (phase === "starting") return "starting"; + return "off"; +} + +/** + * why: the daemon names its reason in its own words and those words are kept, because they say what its + * operator has to fix; the status only decides what to add, since a refusal the console can explain + * better than the daemon did — a device that cannot be streamed at all — would otherwise leave the + * reader with a sentence that does not say what to do. + */ +export function refusalOf(error: unknown): string { + if (!(error instanceof ApiError)) { + return error instanceof Error ? error.message : String(error); + } + // why: an unknown route and an unknown lease are the same answer, because a daemon with + // `stream.enabled` off does not mount the route at all and answers like any path it has not heard of. + if (error.status === 404) { + return `${error.message} A stream is only offered where stream.enabled is on, and only for a lease that is still held.`; + } + if (error.status === 415) { + return "This device cannot be streamed. A live screen is captured from an iOS simulator only."; + } + return error.message; +} + +function paint( + target: HTMLCanvasElement | null, + width: number, + height: number, + source: CanvasImageSource, +): void { + // why: the canvas goes away with the stream, and a frame the decoder was still holding arrives after + // that; drawing into nothing is what a stream ending looks like from here. + if (target === null) return; + if (target.width !== width || target.height !== height) { + target.width = width; + target.height = height; + } + target.getContext("2d")?.drawImage(source, 0, 0); +} diff --git a/ui/src/styles.css b/ui/src/styles.css index df9eb27b..e18f8cee 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -610,6 +610,21 @@ input[aria-invalid="true"] { font-size: var(--text-sm); } +/* The live screen. A device's picture is taller than it is wide and its own size is what the capture + * cost, so the canvas keeps the ratio it was painted at and is scaled to fit the panel: capped in + * height so a phone's picture does not push the rest of the page away, and in width so a landscape one + * never grows past the panel. Both maxima with both sides auto is what keeps the ratio. */ +.screen { + display: block; + width: auto; + max-width: 100%; + height: auto; + max-height: min(60vh, 40rem); + margin: 0 auto var(--space-4); + background: var(--field); + border: 1px solid var(--border); +} + .panel-body > .facts { margin-top: 0; } diff --git a/ui/src/views/leases.tsx b/ui/src/views/leases.tsx index 54927059..01487bf7 100644 --- a/ui/src/views/leases.tsx +++ b/ui/src/views/leases.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { ApiError } from "../api"; +import { ScreenPane } from "../screen/viewer"; import { Id, PageHeader, Panel, StatCards } from "../layout"; import { useLiveResource, useNow } from "../live/live-context"; import { Loaded } from "../live/route-state"; @@ -278,6 +279,7 @@ function Lease({ id }: { readonly id: string }) { )} + )} From 639d699dff0ccc630d1e6562a06a8b642af4d87c Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 15 Oct 2026 11:14:23 +0200 Subject: [PATCH 03/29] chore(vendor): one home for third-party code, with its licensing provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything third-party this repo carries lives under vendor/, one directory per upstream, each holding its artifacts, its license texts and a NOTICE.md. A CI check in source-hygiene enforces the shape; a rules clause states it. serve-sim (Apache-2.0, commit 6d00968): the capture tool moves from tools/ to vendor/screen-capture/ with license text, per-file origin table, the two deviations, and the one command that re-diffs against pristine upstream — the early warning when an Xcode major moves a private API. Three provably dead files (242 lines) are gone; H264Encoder, VideoEncoder, PixelBufferUtils and Xcode are rewritten as ours against public API only, live-verified: the avcC description payload is byte-identical before and after on a real capture. scrcpy (Apache-2.0, v5.0.1): the server artifact is vendored byte-identical at a pinned SHA-256, with the nested Kotlin-stdlib and JetBrains-annotation attributions. No code consumes it yet — its notice says so. --- .fallowrc.json | 4 +- .gitignore | 1 - AGENTS.md | 7 + docs/internal/VENDORED-CODE.md | 77 ++++ docs/internal/agent-rules/always-in-scope.md | 8 + docs/internal/agent-rules/toolchain.md | 6 +- .../vendor-and-third-party-code.md | 110 ++++++ src/source-hygiene.test.ts | 238 +++++++++++- .../DeviceHubKeyboardRouting.swift | 147 -------- .../simlock-screen-capture/DevicePose.swift | 49 --- .../simlock-screen-capture/H264Encoder.swift | 229 ------------ .../PixelBufferUtils.swift | 62 --- .../simlock-screen-capture/StreamFormat.swift | 46 --- .../simlock-screen-capture/VideoEncoder.swift | 60 --- .../simlock-screen-capture/Xcode.swift | 25 -- vendor/README.md | 25 ++ vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt | 204 ++++++++++ vendor/scrcpy/NOTICE.md | 142 +++++++ vendor/scrcpy/SHA256SUMS.txt | 1 + vendor/scrcpy/scrcpy-server-v5.0.1 | Bin 0 -> 733930 bytes {tools => vendor}/screen-capture/.gitignore | 0 .../LICENSES/serve-sim.APACHE-2.0.txt | 202 ++++++++++ vendor/screen-capture/NOTICE.md | 235 ++++++++++++ .../screen-capture/Package.swift | 11 +- .../DeviceDisplaySizes.swift | 0 .../simlock-screen-capture/FrameCapture.swift | 6 + .../FramebufferSurfaceSelector.swift | 0 .../simlock-screen-capture/H264Encoder.swift | 352 ++++++++++++++++++ .../PixelBufferUtils.swift | 98 +++++ .../SimFrameworks.swift | 0 .../simlock-screen-capture/VideoEncoder.swift | 88 +++++ .../simlock-screen-capture/Xcode.swift | 42 +++ .../Sources/simlock-screen-capture/main.swift | 0 .../screen-capture/scripts/unframe.mjs | 0 34 files changed, 1835 insertions(+), 640 deletions(-) create mode 100644 docs/internal/VENDORED-CODE.md create mode 100644 docs/internal/agent-rules/vendor-and-third-party-code.md delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/StreamFormat.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift delete mode 100644 tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift create mode 100644 vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt create mode 100644 vendor/scrcpy/NOTICE.md create mode 100644 vendor/scrcpy/SHA256SUMS.txt create mode 100644 vendor/scrcpy/scrcpy-server-v5.0.1 rename {tools => vendor}/screen-capture/.gitignore (100%) create mode 100644 vendor/screen-capture/LICENSES/serve-sim.APACHE-2.0.txt create mode 100644 vendor/screen-capture/NOTICE.md rename {tools => vendor}/screen-capture/Package.swift (68%) rename {tools => vendor}/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift (100%) rename {tools => vendor}/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift (97%) rename {tools => vendor}/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift (100%) create mode 100644 vendor/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift create mode 100644 vendor/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift rename {tools => vendor}/screen-capture/Sources/simlock-screen-capture/SimFrameworks.swift (100%) create mode 100644 vendor/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift create mode 100644 vendor/screen-capture/Sources/simlock-screen-capture/Xcode.swift rename {tools => vendor}/screen-capture/Sources/simlock-screen-capture/main.swift (100%) rename {tools => vendor}/screen-capture/scripts/unframe.mjs (100%) diff --git a/.fallowrc.json b/.fallowrc.json index 3df4274d..147c7777 100644 --- a/.fallowrc.json +++ b/.fallowrc.json @@ -12,7 +12,7 @@ // them (budget-guard, comment-lint) import from their neighbours (usage, comments). // .dependency-cruiser.cycles.mjs is named only by src/lint-rules.test.ts, which runs depcruise // with it (ADR 0027). - // tools/screen-capture/scripts/unframe.mjs is a diagnostic a person runs by hand against a + // vendor/screen-capture/scripts/unframe.mjs is a diagnostic a person runs by hand against a // captured file, the way the .agents scripts are. "entry": [ "src/cli/main.ts", @@ -23,7 +23,7 @@ "vitest.mutate.config.ts", ".agents/scripts/*.mjs", ".dependency-cruiser.cycles.mjs", - "tools/screen-capture/scripts/*.mjs" + "vendor/screen-capture/scripts/*.mjs" ], "dynamicallyLoaded": ["e2e/fake-driver/index.ts"], // The web console's libraries are bundled into dist/ui at build time and ship as files, so diff --git a/.gitignore b/.gitignore index a02e21dd..917ef5a2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,3 @@ test-results/ playwright-report/ .agent-device .slow-lane.env -tools/screen-capture/.build/ diff --git a/AGENTS.md b/AGENTS.md index 21a9d416..2d1601be 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,11 @@ changes in this repo: agents run and the checks they leave to hooks and CI; the slow lane (real devices); project-only checks. Agents and skills take every command from here. +- [vendor-and-third-party-code.md](docs/internal/agent-rules/vendor-and-third-party-code.md) + — third-party code lives under `vendor/`, one directory per upstream, each + with its license text and a `NOTICE.md`; source vendoring records the commit, + the deviations and how to diff against upstream; binaries are vendored + byte-identical with a checksum; nothing is fetched at runtime. - [always-in-scope.md](docs/internal/agent-rules/always-in-scope.md) — what every change includes without its spec asking (docs and strings it makes false, both `EVENTS.md` files, a test for every new path, Fallow entries), @@ -94,4 +99,6 @@ Maintainer/agent docs live under [docs/internal/](docs/internal/): spec session writes, and the module manual template - [EVENTS.md](docs/internal/EVENTS.md) — the same catalog with rationale and ADR references - [IDEAS.md](docs/internal/IDEAS.md) — post-v1 ideas; don't implement these unless asked +- [VENDORED-CODE.md](docs/internal/VENDORED-CODE.md) — every third-party artifact in the + repo: upstream, version, license, where it lives, who consumes it, how to re-verify - [adr/](docs/internal/adr/) — architecture decision records and their status diff --git a/docs/internal/VENDORED-CODE.md b/docs/internal/VENDORED-CODE.md new file mode 100644 index 00000000..a2bc630a --- /dev/null +++ b/docs/internal/VENDORED-CODE.md @@ -0,0 +1,77 @@ +# Vendored code + +Every piece of third-party code this repository carries, and how to re-verify +each one. The rules that decide what goes in this list are in +[agent-rules/vendor-and-third-party-code.md](agent-rules/vendor-and-third-party-code.md); +this page is the inventory, one row per dependency. A change under `vendor/` +updates the affected row here, its directory's `NOTICE.md`, and +[`vendor/README.md`](../../vendor/README.md)'s index, in the same change. + +The convention, in one line: `vendor//` holds the artifact(s), the +license text(s) they require under `LICENSES/`, and a `NOTICE.md`. + +## The inventory + +| What | Upstream | Version / commit | License | Where it lives | Consumed by | Re-verify with | +|---|---|---|---|---|---|---| +| `@agent-device/proxy` — the transport proxy in front of an agent-device daemon, packed from a branch of agent-device that is not on npm | `callstack/agent-device`, `simlock-integration` branch | commit `253dca023edf13410f7928c76e9e604e5e5871a0` | MIT (Callstack) | `vendor/agent-device-proxy-0.21.28-dev-253dca0.tgz`; license inside the archive at `package/LICENSE` | `package.json`'s `"@agent-device/proxy": "file:vendor/…tgz"`, which `src/agent-device/host/` imports | `tar -tzf vendor/agent-device-proxy-*.tgz \| grep LICENSE`; the commit is what `scripts/slow-e2e.sh` checks a local checkout against, so keep it the first 40-char hash in `vendor/README.md` | +| serve-sim's capture and encode pipeline — CoreSimulator framebuffer capture and the H.264 encode path, patched by us | `EvanBacon/serve-sim` | commit `6d009686ed08f7d8bd19bca9f9f67a9237931f44` | Apache-2.0 | `vendor/screen-capture/Sources/simlock-screen-capture/`, license at `vendor/screen-capture/LICENSES/serve-sim.APACHE-2.0.txt` | the `simlock-screen-capture` program `stream.command` points at, run by `src/stream/capture.ts` | the `curl … \| diff -u -` command in [`vendor/screen-capture/NOTICE.md`](../../vendor/screen-capture/NOTICE.md), per file | +| `scrcpy-server` — the Android screen-streaming server artifact, byte-identical to upstream's release asset | `Genymobile/scrcpy` | release `v5.0.1`; revision built `dc8a55a24c27ae78ae202b5a3288da681d0f141a` | Apache-2.0 | `vendor/scrcpy/scrcpy-server-v5.0.1`, license at `vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt`, digest in `vendor/scrcpy/SHA256SUMS.txt` | **nothing yet** — the planned Android streaming PoC will be its first consumer | `cd vendor/scrcpy && shasum -a 256 -c SHA256SUMS.txt`, and the same against upstream's own release `SHA256SUMS.txt` | + +`vendor/scrcpy/NOTICE.md` names what is *inside* that artifact besides scrcpy's +own classes — the Kotlin standard library and the JetBrains/IntelliJ annotation +classes, all Apache-2.0 — because they are in the file we distribute even though +we did not choose them one by one. + +## What is third-party but not vendored + +These are third-party code the repo depends on and ships or installs through +their own distribution, so `vendor/` is the wrong place for them and this is the +reason they are not above. + +| What | Upstream | Version | License | Where it is | Why it is not vendored | +|---|---|---|---|---|---| +| React, React DOM, Hono, `ws`, Zod, the MCP SDK, TanStack table/virtual, Recharts | npm | as `pnpm-lock.yaml` pins | per package | `node_modules/`, resolved from the lockfile | A registry gives us these, with their licenses; vendoring a tree we never patch only loses upstream's fixes | +| The Geist and Geist Mono variable fonts | `vercel/geist-font`, repackaged as `@fontsource-variable/geist` | `^5.3.0` | OFL-1.1 | `ui/src/main.tsx` imports them; the built console serves the `.woff2` files out of `dist/ui/assets/` | Installed from npm and bundled at build time. **Known gap:** the font license travels with the npm package but Vite does not copy it beside the emitted `.woff2`, and OFL-1.1 wants the license to accompany the font; the fix is to ship the license with the console build, not to vendor the fonts | +| The agent-device CLI itself | `callstack/agent-device` | the commit above | MIT | a separate checkout, at the absolute path `SIMLOCK_E2E_AGENT_DEVICE` gives, on machines that run the slow lane | It is a tool we invoke, not code we carry; only its proxy is vendored, and `toolchain.md` says how to read its source at that commit | + +The license question for the first row is answered by npm, which ships each +package's own license text next to it; nothing in that row is a file in this +repo, which is what separates it from the three rows above. + +## External tools are not vendored + +`simctl` and the rest of Xcode's command-line tools, the Android SDK's +`emulator`, `avdmanager` and `sdkmanager`, and `adb` are **prerequisites this +project invokes and does not carry**. They arrive with the user's Xcode or Android +SDK installation, at whatever version that installation happens to be, and +simlock never downloads, bundles, or pins one. `docs/CONFIGURATION.md` and the +driver prerequisite checks are where their absence is reported. + +Worth keeping the two ideas apart, because the confusion is cheap to make: a +vendored artifact is a byte we distribute and are therefore obliged to license +correctly, while an external tool is something we shell out to and can only ask +for. That is why the scrcpy *server* is vendored and `adb` — which is how it +reaches the device — is not; and why the capture program's Swift source is +vendored while the Xcode that compiles it is not. + +## How a new vendored thing gets here + +1. Decide it cannot stay an upstream dependency: a registry does not carry the + line we need, or we must patch it. +2. Make `vendor//`, put the artifact(s) in it, upstream's verbatim + license text in `LICENSES/..txt`, and `NOTICE.md` written + to the list in rule 4 of + [agent-rules/vendor-and-third-party-code.md](agent-rules/vendor-and-third-party-code.md). + A binary also gets `SHA256SUMS.txt` in `shasum -c` format. +3. Add the row here and the directory to `vendor/README.md`'s index. Keep the + agent-device commit the first 40-character hash in that file — the slow lane + reads the first one it finds. +4. `pnpm exec vitest run --project unit src/source-hygiene.test.ts` checks the + shape (license text, `NOTICE.md`, the digest, and that a vendored binary is + excluded from the text scan for a reason it can prove). + +Nothing here is fetched at install, at build or at run time: an artifact is in +the tree because a person or an agent fetched it once, checked it, and committed +it, which is what safety rule 4's consent requirement looks like for vendored +code. diff --git a/docs/internal/agent-rules/always-in-scope.md b/docs/internal/agent-rules/always-in-scope.md index 3df7dd74..6c8e64a1 100644 --- a/docs/internal/agent-rules/always-in-scope.md +++ b/docs/internal/agent-rules/always-in-scope.md @@ -51,6 +51,14 @@ spec says otherwise in its own words, the spec wins. 11. **Comments say why.** Every comment the change adds or edits starts with `why:` and says why, or is deleted; the commit hook and CI reject the rest (architecture rule 17). +12. **A vendored artifact's notice and row.** A change under `vendor/` updates + that directory's `NOTICE.md`, its row in `docs/internal/VENDORED-CODE.md` + and `vendor/README.md`'s index in the same change + (vendor-and-third-party-code rule 11). A new first-level `vendor/` + directory carries its license text under `LICENSES/` and a `NOTICE.md`; + `src/source-hygiene.test.ts` enforces that shape, and counts a vendored + binary as excluded from the text scan only where a `SHA256SUMS.txt` pins + its digest (vendor-and-third-party-code rules 2, 3, 7). **CI proves the suite.** CI runs the checks [toolchain.md](toolchain.md) lists on every push, and the merge gate refuses a PR whose checks are not diff --git a/docs/internal/agent-rules/toolchain.md b/docs/internal/agent-rules/toolchain.md index d5b563c1..387aeb7f 100644 --- a/docs/internal/agent-rules/toolchain.md +++ b/docs/internal/agent-rules/toolchain.md @@ -63,11 +63,11 @@ that goes through the reviews its diff's kind gets and the merge gate: ## The screen capture program `stream.command` names a program this repo does not ship. The one it is written -against lives in `tools/screen-capture/` — a Swift package, so nothing in +against lives in `vendor/screen-capture/` — a Swift package, so nothing in `pnpm build` or `pnpm test` builds or runs it: ```sh -cd tools/screen-capture && swift build -c release --scratch-path .build/out +cd vendor/screen-capture && swift build -c release --scratch-path .build/out # the binary: .build/out/Products/Release/simlock-screen-capture ``` @@ -80,7 +80,7 @@ is everything this repo writes — the envelope reader, the refusal paths, the lifecycle — against a program that writes the same envelope from stills (`e2e/console/screen-capture.ts`). A change to the Swift program is checked the way the slow lane checks anything else: by hand, on a machine with a simulator, -where `node tools/screen-capture/scripts/unframe.mjs out.264` says +where `node vendor/screen-capture/scripts/unframe.mjs out.264` says whether the bytes it wrote decode. ## Slow lane diff --git a/docs/internal/agent-rules/vendor-and-third-party-code.md b/docs/internal/agent-rules/vendor-and-third-party-code.md new file mode 100644 index 00000000..48a8c95e --- /dev/null +++ b/docs/internal/agent-rules/vendor-and-third-party-code.md @@ -0,0 +1,110 @@ +# Agent rules: vendored and third-party code + +Rules for anything third-party this repository carries in its own tree: +someone else's source, someone else's binary, someone else's package. The +inventory of what is here today is [../VENDORED-CODE.md](../VENDORED-CODE.md); +this file is the rule that keeps the inventory true. + +1. **Third-party code lives under `vendor/`, one directory per upstream.** + `src/`, `ui/`, `scripts/` and `e2e/` hold no file this project did not + write. A dependency npm resolves stays an npm dependency — vendoring is for + what a registry does not give us: a fork at an unreleased commit, a source + tree we patch, an artifact we ship. + +2. **A first-level directory under `vendor/` is third-party _or derived-from_ + third-party code, and never either by accident.** It holds the artifact(s) we + took or derived, every license text they require, and a `NOTICE.md`. A + directory here may also hold a build of that code — a tool compiled from + vendored sources, with our CLI and envelope around it — because keeping a + derivative beside what it derives from is what keeps the provenance in `rule + 5`'s diff readable; `vendor/screen-capture/` is that case. What a directory + under `vendor/` must never hold is code with no upstream at all: that belongs + in `src/`, `ui/`, `scripts/` or `e2e/`. `NOTICE.md`'s per-file origin table + is what separates ours from theirs, and rule 6 makes that table mandatory + precisely because the directory itself no longer does. The check + `src/source-hygiene.test.ts` runs is the licensing half of this claim, so a + directory that fails it misrepresents itself. A file sitting at `vendor/`'s + root is a packed archive, not a directory, and carries its license inside it + (the tarball rule 1's npm case does not cover) — that is why the check is per + directory. + +3. **A license text is upstream's verbatim text, named for what it covers.** + It lives in `/LICENSES/..txt`. Do not name it + `LICENSE`: a bare `LICENSE` in a directory states that it licenses + everything there, and a vendored directory often holds our own files + beside theirs. Where upstream ships no `NOTICE` file, say so in `NOTICE.md` + — that is the difference between Apache §4(d) being satisfied and being + vacuous, and a reader cannot tell from the archive. + +4. **`NOTICE.md` states these, in this order.** Upstream repository and + author; the exact commit or release every line came from; what we vendored + and, as its own list, what we deliberately left out; every deviation we made + from upstream, each with the reason that forced it; the one command that + re-verifies provenance against upstream; and what in the directory is ours. + Each of those is a checkable item, so the file is either complete or it is + not. The reason a deviation needs its reason written down is that the only + thing worse than an unexplained patch is a patch someone "cleans up" back to + upstream's behaviour, breaking the feature that needed it. + +5. **Vendored source records the commit and the diff command; it never stores + a pristine copy.** `NOTICE.md` names the commit and prints the command that + fetches upstream's version of a file and diffs it against ours. Committing + pristine copies too gives a source file two homes, and sooner or later + somebody edits the wrong one. The diff against upstream is also the + early-warning system for a vendored file that reached an API the upstream + platform moved — a major toolchain update shows up there before it shows up + as a failure in the field. + +6. **Vendored source names its origin per file, never by a count.** `NOTICE.md` + carries a table of file → origin, and the line counts stay out of it: they + are the first thing a rewrite invalidates, and a stale count reads as + authority. Origin is not remembered, it is measured — `NOTICE.md` gives the + command, and the row is wrong the moment the command says it is. Where a + file is ours in shape and upstream's in idea, that is what the row says. + +7. **A vendored binary is byte-identical to what upstream published, with its + checksum beside it.** `SHA256SUMS.txt`, in upstream's own + ` ` form, so `shasum -a 256 -c SHA256SUMS.txt` verifies + the directory from inside it; the CI check runs that verification, so the + claim is proved rather than asserted. Never patch, re-pack, re-sign or + strip a vendored binary — an artifact we cannot reproduce byte for byte from + the release is an artifact nobody can re-verify. When upstream publishes a + checksum file for the release, check ours against it and say in `NOTICE.md` + that we did. + +8. **A vendored binary names what is inside it.** A packaged artifact carries + other people's code we did not choose file by file — a runtime library, + annotation jars, a bundled dependency. Name every one of those in + `NOTICE.md` with its license, whether or not it conflicts with ours: it is + in the artifact we distribute, so attribution reaches it, and the next + reader cannot see it without unpacking the file. + +9. **Nothing arrives over the network without the consent + [safety.md](safety.md) rule 4 requires.** Vendoring is a commit: a person or + an agent fetched the artifact once, checked it, and put it in the tree. + Nothing in `vendor/` is downloaded at install, at build, or when the daemon + starts, and no postinstall step reaches a package registry or a release URL + for it. That rule is about multi-GB downloads the user never asked for; the + same reason covers a 700 KB jar — a fetch nobody consented to is a failure + nobody can attribute, and it makes a build's output depend on the state of + someone else's server. Pinning a version and fetching it lazily is not a + smaller version of this rule: it is the thing the rule forbids. + +10. **A vendored artifact nothing consumes yet says so in its own notice.** A + notice that reads as though the artifact were in use is a claim about the + code, and it is false. Name the planned consumer and that it does not + exist yet; the alternative — leaving the artifact unexplained — reads as + somebody else's mistake and invites its deletion. + +11. **A change under `vendor/` updates the notice and the inventory in the same + change** (architecture rule 13, always-in-scope item 12): `NOTICE.md`, the + row in [../VENDORED-CODE.md](../VENDORED-CODE.md), and `vendor/README.md`'s + index. A toolchain upgrade of a vendored artifact is a new commit or + release in those three places, not an edit of the old one: what the + previous line was is the information a later diff needs. + +12. **An external tool is not vendored, and is not listed as if it were.** + Xcode's `simctl`, the Android SDK, `adb` and an emulator image are + prerequisites we invoke and do not carry. They belong in the note that + separates the two, not in the inventory's rows; conflating them is how a + "we vendor it" claim ends up in a doc that a user then cannot install from. diff --git a/src/source-hygiene.test.ts b/src/source-hygiene.test.ts index 2a2f52b3..3a3ce662 100644 --- a/src/source-hygiene.test.ts +++ b/src/source-hygiene.test.ts @@ -1,6 +1,7 @@ import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; import { readFileSync, statSync } from "node:fs"; -import { dirname, join } from "node:path"; +import { basename, dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; @@ -9,9 +10,59 @@ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); const NUL = 0x00; -/** why: built binaries committed for tests or vendored packages are never searched as text. */ +const lsFiles = (args: string[]): string[] => + execFileSync("git", ["ls-files", "-z", ...args], { + cwd: REPO_ROOT, + encoding: "buffer", + maxBuffer: 32 * 1024 * 1024, + }) + .toString("utf8") + .split("\0") + .filter((name) => name.length > 0); + +/** + * why: a vendored artifact lands untracked -- the directory is complete, at the right checksum, + * long before anybody stages it, and a check that read only the index would wave a half-vendored + * directory through the very run meant to catch it. So `vendor/` is enumerated from the index + * *and* the working tree. Ignored files stay out: `vendor/`'s own build scratch (`.build/`) is not + * part of what anyone clones. + */ +const vendoredFiles = lsFiles(["--cached", "--others", "--exclude-standard", "vendor/"]); + +/** + * why: pinning an artifact's digest is the statement that its bytes are someone else's and are not + * to be read as text, so the same list says which vendored files the text scan must skip -- and the + * digest test below proves the list means what it says. It also keeps the skip from growing on its + * own: a new vendored binary joins it by being pinned, not by being named here. + */ +const pinnedVendoredArtifacts = new Set( + vendoredFiles + .filter((name) => name.endsWith("/SHA256SUMS.txt")) + .flatMap((sums) => { + const dir = sums.slice(0, -"SHA256SUMS.txt".length); + return sha256Sums(sums).map(([, file]) => `${dir}${file}`); + }), +); + +/** why: the digest and file name a `SHA256SUMS.txt` pins, read in the format `shasum -c` reads. */ +function sha256Sums(name: string): [string, string][] { + return readFileSync(join(REPO_ROOT, name), "utf8") + .split("\n") + .map((line) => line.trim().split(/\s+/)) + .filter((pair): pair is [string, string] => pair.length === 2 && pair[1]!.length > 0); +} + +/** + * why: a committed binary is never searched as text -- built fixtures the e2e lane installs, and + * the vendored third-party artifacts a `SHA256SUMS.txt` pins bytes for (an npm tarball, a packed + * server APK). Both kinds hold raw NUL bytes by nature, which is what makes them unsearchable: that + * is a claim about their contents, so for `vendor/` the test that compares this list against the + * files in `vendor/` that actually carry a NUL is what makes the skip mean something. + */ const isCommittedBinary = (name: string): boolean => - name.startsWith("e2e/fixtures/") || (name.startsWith("vendor/") && name.endsWith(".tgz")); + name.startsWith("e2e/fixtures/") || + (name.startsWith("vendor/") && name.endsWith(".tgz")) || + pinnedVendoredArtifacts.has(name); /** * why: a raw NUL byte in a text file makes `grep` and `ripgrep` classify the whole file as @@ -21,19 +72,13 @@ const isCommittedBinary = (name: string): boolean => * * Enumerated from `git ls-files` rather than a directory walk so the check sees every * tracked file in the repository (testing rule 4), not just the directories this test - * happened to name. The exceptions are `e2e/fixtures/` and the vendored `vendor/*.tgz` tarballs: built binaries - * committed for tests, never searched as text, so they are skipped. Untracked and ignored files are out of scope: they are not part of - * what anyone clones or searches. + * happened to name. The exceptions are the committed binaries `isCommittedBinary` names: + * built binaries committed for tests, and the vendored third-party artifacts, none of + * which is ever searched as text. Untracked and ignored files are out of scope: + * they are not part of what anyone clones or searches. */ describe("tracked files", () => { - const trackedFiles = execFileSync("git", ["ls-files", "-z"], { - cwd: REPO_ROOT, - encoding: "buffer", - maxBuffer: 32 * 1024 * 1024, - }) - .toString("utf8") - .split("\0") - .filter((name) => name.length > 0) + const trackedFiles = lsFiles([]) .filter((name) => !isCommittedBinary(name)) // A submodule or a path deleted from the working tree is still listed by // `git ls-files`; only regular files can be read. @@ -72,3 +117,168 @@ describe("tracked files", () => { expect(offenders).toEqual([]); }); }); + +/** + * why: `vendor/` is the claim that what is in there is someone else's code, so the check walks + * every first-level directory (testing rule 4) rather than the two it happens to know about. Built + * from `vendoredFiles`, which sees the working tree as well as the index. + */ +const vendoredDirs = new Map(); +for (const name of vendoredFiles) { + const [, dir = "", rest = ""] = name.split("/"); + // why: a file at `vendor/`'s root belongs to no directory, and has its own section below. + if (rest.length === 0) continue; + const paths = vendoredDirs.get(dir); + if (paths === undefined) vendoredDirs.set(dir, [name]); + else paths.push(name); +} + +/** why: a vendored directory's license texts are exactly what its `LICENSES/` holds (rule 3). */ +const licenseTextsOf = (dir: string): string[] => + (vendoredDirs.get(dir) ?? []).filter((name) => name.startsWith(`vendor/${dir}/LICENSES/`)); + +/** + * why: a license text named for a license is all a reader has to trust that it *is* + * that license, so the name and the contents have to agree; the expected words are + * read out of the file name (`serve-sim.APACHE-2.0.txt` asks the text to say + * `APACHE`) so no list here can go stale. + */ +const licensesClaimedOf = (name: string): string[] => { + const base = name.slice(name.lastIndexOf("/") + 1).replace(/\.txt$/i, ""); + const separator = base.indexOf("."); + if (separator <= 0) return []; + return base + .slice(separator + 1) + .split(/[.\-_]/) + .filter((token) => /^[a-z]{2,}$/i.test(token)); +}; + +/** + * why: the rule has to be enforced everywhere it applies or it only guards the directories someone + * remembered (testing rule 4). Vendor and third-party code rule 2 + * (`docs/internal/agent-rules/vendor-and-third-party-code.md`): a first-level directory under + * `vendor/` holds the artifact(s), the license text(s) they require, and a `NOTICE.md`. + */ +describe("a first-level directory under vendor/", () => { + it("was enumerated, and holds the vendored code this repo carries", () => { + // why: without this, an empty enumeration would leave every claim below with nothing to check + // and the suite would pass while enforcing the rule on no directory at all. + expect([...vendoredDirs.keys()].toSorted()).toEqual( + expect.arrayContaining(["scrcpy", "screen-capture"]), + ); + expect(vendoredFiles.length).toBeGreaterThan(10); + }); + + it("holds a NOTICE.md", () => { + const withoutNotice = [...vendoredDirs.keys()].filter( + (dir) => !vendoredDirs.get(dir)?.includes(`vendor/${dir}/NOTICE.md`), + ); + expect(withoutNotice).toEqual([]); + }); + + it("holds a license text under LICENSES/ whose text matches its name", () => { + const missing = [...vendoredDirs.keys()].filter((dir) => licenseTextsOf(dir).length === 0); + const mismatched = [...vendoredDirs.keys()].flatMap((dir) => + licenseTextsOf(dir).flatMap((name) => { + const claimed = licensesClaimedOf(name); + const text = readFileSync(join(REPO_ROOT, name), "utf8"); + if (claimed.length === 0) return [`${name}: its file name names no license`]; + return claimed + .filter((word) => !new RegExp(`\\b${word}\\b`, "i").test(text)) + .map((word) => `${name}: its text never says ${word}`); + }), + ); + expect({ missing, mismatched }).toEqual({ missing: [], mismatched: [] }); + }); + + it("holds no file named LICENSE, which would claim to license the whole directory", () => { + // why: rule 3 -- our own code often sits beside theirs, so a license text names what it covers + // instead of taking the name that states it covers everything nearby. + const isBareLicense = (name: string): boolean => /^LICENSE(\.|$)/.test(basename(name)); + const bare = vendoredFiles.filter( + (name) => name.split("/").length > 2 && isBareLicense(name), + ); + expect(bare).toEqual([]); + }); +}); + +/** + * why: the digest in a `SHA256SUMS.txt` is the claim that a vendored binary is byte-identical to + * what upstream published, and a claim nobody recomputes is a claim that rots. Computed here rather + * than read back out of `NOTICE.md`, so both the notice and the text-scan exclusion this list feeds + * rest on bytes on disk (vendor and third-party code rule 7). + */ +describe("a vendored artifact pinned by a SHA256SUMS.txt", () => { + const pinned = [...pinnedVendoredArtifacts].toSorted(); + + it("was enumerated, and holds the artifacts that are here", () => { + // why: without this, a `SHA256SUMS.txt` that stopped being listed would empty the two checks + // below and the suite would pass having verified no digest at all. + expect(pinned).toEqual(expect.arrayContaining(["vendor/scrcpy/scrcpy-server-v5.0.1"])); + }); + + it("matches the digest its SHA256SUMS.txt gives", () => { + const wrong = pinned.flatMap((name) => { + const separator = name.lastIndexOf("/"); + const sums = sha256Sums(`${name.slice(0, separator + 1)}SHA256SUMS.txt`); + const expected = sums.find(([, file]) => file === name.slice(separator + 1))?.[0]; + if (expected === undefined) return [`${name}: its SHA256SUMS.txt does not list it`]; + const actual = createHash("sha256") + .update(readFileSync(join(REPO_ROOT, name))) + .digest("hex"); + return actual === expected ? [] : [`${name}: ${actual} != ${expected}`]; + }); + expect(wrong).toEqual([]); + }); + + it("is exactly the vendored file that a raw NUL byte would make unsearchable", () => { + // why: this proves the vendored clause of `isCommittedBinary` skips what it says it skips -- an + // artifact pinned but not a binary is skipped for no reason, which costs every reader a + // searchable file, and a binary left off the list turns the NUL check red instead of being + // excluded from it. + const withNul = vendoredFiles + .filter((name) => { + try { + return statSync(join(REPO_ROOT, name)).isFile(); + } catch { + return false; + } + }) + .filter((name) => !name.endsWith(".tgz")) + .filter((name) => readFileSync(join(REPO_ROOT, name)).includes(NUL)) + .toSorted(); + expect(withNul).toEqual(["vendor/scrcpy/scrcpy-server-v5.0.1"]); + expect(pinned).toEqual(withNul); + }); +}); + +/** + * why: a loose file at `vendor/`'s root would sit outside the per-directory rule + * entirely, so it has to be accounted for. The one case is an npm `file:` + * dependency's tarball: it cannot be a directory, and it carries its license + * inside itself -- which is why the three-part shape above is checked per vendored + * directory and not over `vendor/` as a whole. + */ +describe("a file at vendor/'s root", () => { + const rootFiles = vendoredFiles + .filter((name) => !name.slice("vendor/".length).includes("/")) + .toSorted(); + + it("are the index and a packed archive, never third-party code outside a directory", () => { + expect(rootFiles).toEqual([ + "vendor/README.md", + "vendor/agent-device-proxy-0.21.28-dev-253dca0.tgz", + ]); + }); + + it.each(rootFiles.filter((name) => name.endsWith(".tgz")))( + "carries a license inside itself: %s", + (archive) => { + const members = execFileSync("tar", ["-tzf", archive], { + cwd: REPO_ROOT, + encoding: "utf8", + }).split("\n"); + expect(members.some((member) => /(^|\/)LICENSE(\.|$)/i.test(member))).toBe(true); + }, + ); +}); diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift b/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift deleted file mode 100644 index ccee8fe1..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/DeviceHubKeyboardRouting.swift +++ /dev/null @@ -1,147 +0,0 @@ -import Foundation - -/// Environment policy for the optional Xcode 27 Device Hub keyboard route. -public enum DeviceHubKeyboardConfiguration { - /// Only the documented value `1` opts out. Values such as `0` and `false` - /// leave the route enabled. - public static func isDisabled(environmentValue: String?) -> Bool { - environmentValue == "1" - } -} - -public struct DeviceHubWindow: Equatable, Sendable { - public let processIdentifier: Int32 - public let windowNumber: UInt32 - public let name: String - public let layer: Int - public let isOnScreen: Bool - - public init( - processIdentifier: Int32, - windowNumber: UInt32, - name: String, - layer: Int = 0, - isOnScreen: Bool = true - ) { - self.processIdentifier = processIdentifier - self.windowNumber = windowNumber - self.name = name - self.layer = layer - self.isOnScreen = isOnScreen - } -} - -public enum DeviceHubWindowRoutingFailure: Error, Equatable, Sendable, CustomStringConvertible { - case noEligibleWindows - case targetNotFound - case ambiguousTarget(count: Int) - case targetNotFocused(focusedName: String) - - public var description: String { - switch self { - case .noEligibleWindows: - return "Device Hub has no visible simulator window" - case .targetNotFound: - return "the target simulator window is not visible" - case .ambiguousTarget(let count): - return "the target simulator name matches \(count) windows" - case .targetNotFocused(let focusedName): - return "another Device Hub window has keyboard focus (\(focusedName))" - } - } -} - -/// Chooses a Device Hub simulator window from a key-window-first snapshot. A -/// simulator name is not globally unique, so duplicate matches are deliberately -/// rejected instead of risking keyboard input in the wrong guest. -public enum DeviceHubWindowRouter { - public static func route( - windows: [DeviceHubWindow], - processIdentifier: Int32, - targetWindowTitle: String - ) -> Result { - let eligible = windows.filter { - $0.processIdentifier == processIdentifier - && $0.layer == 0 - && $0.isOnScreen - && !$0.name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty - } - guard let focused = eligible.first else { return .failure(.noEligibleWindows) } - - // Device Hub's AX title is canonical (` – `). Match - // it exactly: simulator names are user-controlled and prefix matching - // could route input to a different device with a longer name. - let matches = eligible.filter { $0.name == targetWindowTitle } - guard !matches.isEmpty else { return .failure(.targetNotFound) } - guard matches.count == 1 else { return .failure(.ambiguousTarget(count: matches.count)) } - guard matches[0].windowNumber == focused.windowNumber else { - return .failure(.targetNotFocused(focusedName: focused.name)) - } - return .success(matches[0]) - } -} - -public enum HIDModifier: Equatable, Sendable { - case control - case shift - case option - case command -} - -/// USB HID Keyboard/Keypad usage (page 0x07) to macOS virtual key code. -public enum HIDKeyboardMapping { - public static func macVirtualKeyCode(for usage: UInt32) -> UInt16? { - keyCodes[usage] - } - - public static func modifier(for usage: UInt32) -> HIDModifier? { - switch usage { - case 0xE0, 0xE4: return .control - case 0xE1, 0xE5: return .shift - case 0xE2, 0xE6: return .option - case 0xE3, 0xE7: return .command - default: return nil - } - } - - private static let keyCodes: [UInt32: UInt16] = [ - // A-Z - 0x04: 0, 0x05: 11, 0x06: 8, 0x07: 2, 0x08: 14, 0x09: 3, - 0x0A: 5, 0x0B: 4, 0x0C: 34, 0x0D: 38, 0x0E: 40, 0x0F: 37, - 0x10: 46, 0x11: 45, 0x12: 31, 0x13: 35, 0x14: 12, 0x15: 15, - 0x16: 1, 0x17: 17, 0x18: 32, 0x19: 9, 0x1A: 13, 0x1B: 7, - 0x1C: 16, 0x1D: 6, - - // Number row - 0x1E: 18, 0x1F: 19, 0x20: 20, 0x21: 21, 0x22: 23, - 0x23: 22, 0x24: 26, 0x25: 28, 0x26: 25, 0x27: 29, - - // Editing and punctuation - 0x28: 36, 0x29: 53, 0x2A: 51, 0x2B: 48, 0x2C: 49, - 0x2D: 27, 0x2E: 24, 0x2F: 33, 0x30: 30, 0x31: 42, - 0x32: 42, 0x33: 41, 0x34: 39, 0x35: 50, 0x36: 43, - 0x37: 47, 0x38: 44, 0x39: 57, - - // Function keys - 0x3A: 122, 0x3B: 120, 0x3C: 99, 0x3D: 118, 0x3E: 96, 0x3F: 97, - 0x40: 98, 0x41: 100, 0x42: 101, 0x43: 109, 0x44: 103, 0x45: 111, - 0x46: 105, 0x47: 107, 0x48: 113, - - // Navigation - 0x49: 114, 0x4A: 115, 0x4B: 116, 0x4C: 117, 0x4D: 119, - 0x4E: 121, 0x4F: 124, 0x50: 123, 0x51: 125, 0x52: 126, - - // Numeric keypad - 0x53: 71, 0x54: 75, 0x55: 67, 0x56: 78, 0x57: 69, 0x58: 76, - 0x59: 83, 0x5A: 84, 0x5B: 85, 0x5C: 86, 0x5D: 87, 0x5E: 88, - 0x5F: 89, 0x60: 91, 0x61: 92, 0x62: 82, 0x63: 65, - 0x64: 10, 0x67: 81, - - // Media keys commonly emitted by browsers - 0x7F: 74, 0x80: 72, 0x81: 73, - - // Modifiers - 0xE0: 59, 0xE1: 56, 0xE2: 58, 0xE3: 55, - 0xE4: 62, 0xE5: 60, 0xE6: 61, 0xE7: 54, - ] -} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift b/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift deleted file mode 100644 index 930d0fc4..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/DevicePose.swift +++ /dev/null @@ -1,49 +0,0 @@ -/// Named hinge angles for iPhone Duo (V68). -/// -/// Which framebuffer is live is decided in TypeScript from SpringBoard's -/// primary-panel readback, with the 90° rule only before that readback. -/// Touch routing uses the captured pixel size (`touchTarget`), not the pose. -/// Do not toggle SimScreen power — FrontBoard's bookend display monitor -/// asserts if a foldable panel is missing at SpringBoard launch. -public struct DevicePoseSpec: Equatable, Sendable { - public let name: String - public let hingeDegrees: Double - - public init(name: String, hingeDegrees: Double) { - self.name = name - self.hingeDegrees = hingeDegrees - } -} - -public enum DevicePose { - public static let coverWidth = 1398 - public static let coverHeight = 2034 - public static let innerWidth = 2007 - public static let innerHeight = 2853 - - /// Duo's own Integrated screen registrations, including SimulatorHID's mask. - /// Do not apply this mapping to TVOut, CarPlay, or single-panel devices. - public static func touchTarget(width: Int, height: Int) -> UInt32? { - let size = [width, height].sorted() - if size == [coverWidth, coverHeight] { return 0x40000001 } - if size == [innerWidth, innerHeight] { return 0x40000003 } - return nil - } - - public static let presets: [DevicePoseSpec] = [ - DevicePoseSpec(name: "closed", hingeDegrees: 0), - DevicePoseSpec(name: "tent", hingeDegrees: 80), - DevicePoseSpec(name: "tabletop", hingeDegrees: 100), - DevicePoseSpec(name: "book", hingeDegrees: 130), - DevicePoseSpec(name: "open", hingeDegrees: 180), - ] - - public static func preset(named raw: String) -> DevicePoseSpec? { - let name = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() - if name == "cover" { return preset(named: "closed") } - if name == "inner" || name == "openflat" || name == "flat" { - return preset(named: "open") - } - return presets.first { $0.name == name } - } -} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift b/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift deleted file mode 100644 index b76426cd..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift +++ /dev/null @@ -1,229 +0,0 @@ -import Foundation -import CoreVideo -import CoreMedia -import VideoToolbox - -/// Real-time H.264 encoder backed by `VTCompressionSession`, producing AVCC -/// (length-prefixed NAL) output for the `/stream.avcc` endpoint. -/// -/// Submission is fire-and-forget: the caller hands a `CVPixelBuffer` in and -/// the encoded chunk comes back via `onEncoded` on VideoToolbox's own queue. -/// The incoming buffer wraps SimulatorKit's live framebuffer IOSurface, which -/// SimulatorKit recycles in place — VT encodes asynchronously, so we deep-copy -/// into a private pooled buffer before submitting to avoid a torn frame race. -actor H264Encoder { - let queue = DispatchSerialQueue(label: "h264-encoder", qos: .userInteractive) - nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } - - struct Encoded { - /// avcC parameter-set blob — emitted once on the first IDR per session. - let description: Data? - let kind: Kind - /// Length-prefixed AVCC NAL bytes (not Annex-B start codes). - let avcc: Data - enum Kind { case keyframe, delta } - } - - private var session: VTCompressionSession? - private var width: Int32 = 0 - private var height: Int32 = 0 - private let fps: Int32 - private var bitrate: Int - private var emittedDescription = false - private var frameCount: Int64 = 0 - - init(fps: Int = 60, bitrate: Int = 6_000_000) { - self.fps = Int32(fps) - self.bitrate = bitrate - } - - deinit { - if let session { VTCompressionSessionInvalidate(session) } - } - - /// Encode one frame. Real-time VideoToolbox sessions may deliberately drop - /// a frame under pressure; that is reported as `nil`, not as an error. - func encode(_ source: CVPixelBuffer, forceKeyframe: Bool = false) async throws -> Encoded? { - let w = Int32(CVPixelBufferGetWidth(source)) - let h = Int32(CVPixelBufferGetHeight(source)) - if session == nil || w != width || h != height { - width = w - height = h - rebuildSession() - } - guard let session else { - throw Errors.couldNotCreateSession - } - - frameCount += 1 - let pts = CMTime(value: frameCount, timescale: fps) - let frameProps: NSDictionary? = forceKeyframe - ? [kVTEncodeFrameOptionKey_ForceKeyFrame: kCFBooleanTrue!] as NSDictionary - : nil - - let result: (buffer: CMSampleBuffer?, status: OSStatus, flags: VTEncodeInfoFlags) = await withCheckedContinuation { continuation in - let status = VTCompressionSessionEncodeFrame( - session, - imageBuffer: source, - presentationTimeStamp: pts, - duration: .invalid, - frameProperties: frameProps, - infoFlagsOut: nil - ) { @Sendable status, flags, sampleBuffer in - guard status == noErr, let sampleBuffer else { - continuation.resume(returning: (nil, status, flags)) - return - } - continuation.resume(returning: (sampleBuffer, status, flags)) - } - if status != noErr { - continuation.resume(returning: (nil, status, [])) - } - } - if result.status == noErr, result.flags.contains(.frameDropped) { return nil } - guard result.status == noErr else { throw Errors.encodingFailed(result.status) } - guard let buffer = result.buffer else { throw Errors.missingSampleBuffer } - return try extract(from: buffer) - } - - func stop() { - if let session { - VTCompressionSessionInvalidate(session) - self.session = nil - } - } - - // MARK: - private - - private func rebuildSession() { - if let session { - VTCompressionSessionInvalidate(session) - self.session = nil - } - - // Low-latency rate control puts VideoToolbox in its real-time/low-delay - // pipeline and, crucially, emits a bitstream the *decoder* treats as - // low-latency (small max_dec_frame_buffering). Without it the decoder - // fills a large DPB before emitting, adding ~300ms of latency on the - // client even though the stream carries no B-frames. Falls back to the - // default spec on the rare hardware that rejects it. - let lowLatencySpec: NSDictionary = [ - kVTVideoEncoderSpecification_EnableLowLatencyRateControl: kCFBooleanTrue!, - ] - var sess: VTCompressionSession? - func create(spec: CFDictionary?) -> OSStatus { - VTCompressionSessionCreate( - allocator: kCFAllocatorDefault, - width: width, height: height, - codecType: kCMVideoCodecType_H264, - encoderSpecification: spec, - imageBufferAttributes: nil, - compressedDataAllocator: kCFAllocatorDefault, - outputCallback: nil, - refcon: nil, - compressionSessionOut: &sess - ) - } - var status = create(spec: lowLatencySpec) - if status != noErr || sess == nil { - sess = nil - status = create(spec: nil) - } - guard status == noErr, let sess else { return } - - let props: [(CFString, Any)] = [ - (kVTCompressionPropertyKey_RealTime, kCFBooleanTrue!), - (kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel), - (kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse!), - (kVTCompressionPropertyKey_AverageBitRate, NSNumber(value: bitrate)), - (kVTCompressionPropertyKey_ExpectedFrameRate, NSNumber(value: fps)), - // 5s keyframe interval: IDRs are far larger than P-frames, so - // spacing them out keeps scroll/animation smooth. Late joiners - // don't wait for the natural IDR — we force one on connect. - (kVTCompressionPropertyKey_MaxKeyFrameInterval, NSNumber(value: fps * 5)), - ] - for (key, value) in props { - VTSessionSetProperty(sess, key: key, value: value as CFTypeRef) - } - VTCompressionSessionPrepareToEncodeFrames(sess) - session = sess - emittedDescription = false - } - - private func extract(from sample: CMSampleBuffer) throws -> Encoded { - let isKeyframe = !notSync(sample) - guard let dataBuf = CMSampleBufferGetDataBuffer(sample) else { - throw Errors.invalidSampleBuffer - } - - var totalLength = 0 - var dataPointer: UnsafeMutablePointer? - guard CMBlockBufferGetDataPointer( - dataBuf, atOffset: 0, lengthAtOffsetOut: nil, - totalLengthOut: &totalLength, dataPointerOut: &dataPointer - ) == noErr, let dataPointer else { - throw Errors.invalidSampleBuffer - } - let avcc = Data(bytes: dataPointer, count: totalLength) - - var description: Data? - if isKeyframe, let format = CMSampleBufferGetFormatDescription(sample) { - let nextDescription = avcCBlob(from: format) - if !emittedDescription && nextDescription != nil { - emittedDescription = true - description = nextDescription - } - } - return Encoded(description: description, kind: isKeyframe ? .keyframe : .delta, avcc: avcc) - } - - private func notSync(_ sample: CMSampleBuffer) -> Bool { - guard let attachments = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: false), - CFArrayGetCount(attachments) > 0, - let dict = CFArrayGetValueAtIndex(attachments, 0) else { return false } - let cfDict = unsafeBitCast(dict, to: CFDictionary.self) - return CFDictionaryContainsKey(cfDict, Unmanaged.passUnretained(kCMSampleAttachmentKey_NotSync).toOpaque()) - } - - /// avcC parameter-set blob (ISO/IEC 14496-15 §5.2.4.1) carrying SPS + PPS. - private func avcCBlob(from format: CMFormatDescription) -> Data? { - var spsCount = 0 - var spsPtr: UnsafePointer? - var spsSize = 0 - var nalSize: Int32 = 0 - guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( - format, parameterSetIndex: 0, - parameterSetPointerOut: &spsPtr, parameterSetSizeOut: &spsSize, - parameterSetCountOut: &spsCount, nalUnitHeaderLengthOut: &nalSize - ) == noErr, let spsPtr, spsSize >= 4 else { return nil } - - var ppsPtr: UnsafePointer? - var ppsSize = 0 - guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( - format, parameterSetIndex: 1, - parameterSetPointerOut: &ppsPtr, parameterSetSizeOut: &ppsSize, - parameterSetCountOut: nil, nalUnitHeaderLengthOut: nil - ) == noErr, let ppsPtr else { return nil } - - let sps = UnsafeBufferPointer(start: spsPtr, count: spsSize) - let pps = UnsafeBufferPointer(start: ppsPtr, count: ppsSize) - var blob = Data() - blob.append(0x01) - blob.append(sps[1]); blob.append(sps[2]); blob.append(sps[3]) - blob.append(0xFF) - blob.append(0xE1) - blob.append(UInt8((spsSize >> 8) & 0xFF)); blob.append(UInt8(spsSize & 0xFF)) - blob.append(contentsOf: sps) - blob.append(0x01) - blob.append(UInt8((ppsSize >> 8) & 0xFF)); blob.append(UInt8(ppsSize & 0xFF)) - blob.append(contentsOf: pps) - return blob - } - - enum Errors: Error { - case couldNotCreateSession - case encodingFailed(OSStatus) - case missingSampleBuffer - case invalidSampleBuffer - } -} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift b/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift deleted file mode 100644 index fa1c6694..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift +++ /dev/null @@ -1,62 +0,0 @@ -import CoreVideo - -struct Dimensions: Hashable, Sendable { - var width: Int - var height: Int -} - -extension CVPixelBuffer { - var dimensions: Dimensions { - Dimensions(width: CVPixelBufferGetWidth(self), height: CVPixelBufferGetHeight(self)) - } -} - -struct Photocopier { - private var _pool: CVPixelBufferPool? - private var dimensions: Dimensions? - - init() {} - - private mutating func pool(dimensions: Dimensions) -> CVPixelBufferPool? { - if let _pool, self.dimensions == dimensions { - return _pool - } - let attrs: [String: Any] = [ - kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, - kCVPixelBufferWidthKey as String: Int(dimensions.width), - kCVPixelBufferHeightKey as String: Int(dimensions.height), - kCVPixelBufferIOSurfacePropertiesKey as String: [:], - ] - var newPool: CVPixelBufferPool? - CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, attrs as CFDictionary, &newPool) - self._pool = newPool - self.dimensions = dimensions - return newPool - } - - /// Deep-copy `source` (which wraps the recycled framebuffer IOSurface) - /// into a private pooled buffer that sinks can retain. - mutating func copy(_ source: CVPixelBuffer) -> CVPixelBuffer? { - guard let pool = self.pool(dimensions: source.dimensions) else { return nil } - var out: CVPixelBuffer? - guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &out) == kCVReturnSuccess, - let dst = out else { return nil } - - CVPixelBufferLockBaseAddress(source, .readOnly) - CVPixelBufferLockBaseAddress(dst, []) - defer { - CVPixelBufferUnlockBaseAddress(dst, []) - CVPixelBufferUnlockBaseAddress(source, .readOnly) - } - guard let srcAddr = CVPixelBufferGetBaseAddress(source), - let dstAddr = CVPixelBufferGetBaseAddress(dst) else { return nil } - let srcStride = CVPixelBufferGetBytesPerRow(source) - let dstStride = CVPixelBufferGetBytesPerRow(dst) - let rows = CVPixelBufferGetHeight(source) - let copyBytes = min(srcStride, dstStride) - for row in 0..`; high bandwidth. -/// - `avcc`: length-prefixed H.264 NAL chunks (AVCC framing) decoded by the -/// browser's WebCodecs `VideoDecoder`. ~5-10x less bandwidth; needs a -/// canvas + `VideoDecoder`, so the client feature-detects and falls back -/// to `mjpeg`. -enum StreamFormat: String { - case mjpeg - case avcc -} - -/// Bytes that wrap each chunk on the `/stream.avcc` wire. Every chunk is a -/// 4-byte big-endian length (covering the tag byte + payload) followed by a -/// one-byte tag and the payload: -/// -/// - `0x01` description — avcC parameter-set blob (SPS/PPS); configures the -/// decoder. Emitted once per encoder session and replayed to late joiners. -/// - `0x02` keyframe — IDR (decoder can start here). -/// - `0x03` delta — non-IDR P-frame (depends on prior frames). -/// - `0x04` seed — a JPEG painted immediately on connect so the viewer sees -/// the current screen before the first IDR decodes. -/// -enum AVCCEnvelope { - static let descriptionTag: UInt8 = 0x01 - static let keyframeTag: UInt8 = 0x02 - static let deltaTag: UInt8 = 0x03 - static let seedTag: UInt8 = 0x04 - - static func description(avcc: Data) -> Data { wrap(tag: descriptionTag, payload: avcc) } - static func keyframe(avcc: Data) -> Data { wrap(tag: keyframeTag, payload: avcc) } - static func delta(avcc: Data) -> Data { wrap(tag: deltaTag, payload: avcc) } - static func seed(jpeg: Data) -> Data { wrap(tag: seedTag, payload: jpeg) } - - private static func wrap(tag: UInt8, payload: Data) -> Data { - let length = UInt32(payload.count + 1) - var out = Data(capacity: 5 + payload.count) - withUnsafeBytes(of: length.bigEndian) { out.append(contentsOf: $0) } - out.append(tag) - out.append(payload) - return out - } -} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift b/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift deleted file mode 100644 index 0e77fa01..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/VideoEncoder.swift +++ /dev/null @@ -1,60 +0,0 @@ -import Foundation -import CoreVideo -import CoreGraphics -import ImageIO -import UniformTypeIdentifiers - -/// Encodes CVPixelBuffer frames as JPEG data for MJPEG streaming. -actor VideoEncoder { - // makes sure we don't block the main thread / cooperative thread pool - let queue = DispatchSerialQueue(label: "video-encoder", qos: .userInteractive) - - nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } - - private let quality: CGFloat - - init(quality: CGFloat = 0.7) { - self.quality = quality - } - - func encode(pixelBuffer: CVPixelBuffer) throws -> Data { - CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) - defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } - - let width = CVPixelBufferGetWidth(pixelBuffer) - let height = CVPixelBufferGetHeight(pixelBuffer) - guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { - throw Errors.invalidPixelBuffer - } - let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) - - let colorSpace = CGColorSpaceCreateDeviceRGB() - guard let context = CGContext( - data: baseAddress, - width: width, - height: height, - bitsPerComponent: 8, - bytesPerRow: bytesPerRow, - space: colorSpace, - bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue - ), let cgImage = context.makeImage() else { - throw Errors.encodingFailed - } - - let data = NSMutableData() - guard let dest = CGImageDestinationCreateWithData(data as CFMutableData, "public.jpeg" as CFString, 1, nil) else { - throw Errors.encodingFailed - } - CGImageDestinationAddImage(dest, cgImage, [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary) - guard CGImageDestinationFinalize(dest) else { - throw Errors.encodingFailed - } - - return data as Data - } - - enum Errors: Error { - case invalidPixelBuffer - case encodingFailed - } -} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift b/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift deleted file mode 100644 index 9b209224..00000000 --- a/tools/screen-capture/Sources/simlock-screen-capture/Xcode.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -enum Xcode { - /// Absolute path to the active Xcode's Developer dir (`xcode-select -p`), - /// e.g. `/Applications/Xcode.app/Contents/Developer`. Falls back to the - /// default install path if `xcode-select` can't be run. - static func developerDir() -> String { - let fallback = "/Applications/Xcode.app/Contents/Developer" - let pipe = Pipe() - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/xcode-select") - process.arguments = ["-p"] - process.standardOutput = pipe - // Only read output if the process actually launched; otherwise - // waitUntilExit() on an unstarted process traps. - do { - try process.run() - } catch { - return fallback - } - process.waitUntilExit() - return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)? - .trimmingCharacters(in: .whitespacesAndNewlines) ?? fallback - } -} diff --git a/vendor/README.md b/vendor/README.md index 0c80f1e7..c3162015 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -11,3 +11,28 @@ pnpm --filter @agent-device/proxy pack --pack-destination Its `prepack` runs the package's own build, which bundles agent-device's internal packages into `dist`, so the tarball has no `workspace:` dependency. It is swapped for the npm release later. + +The commit above is the one the slow lane checks a local agent-device checkout +against, so it stays the first 40-character commit hash in this file: +`scripts/slow-e2e.sh` reads the first one it finds. + +## What else lives here + +| Directory | Upstream | License | Notice | +| --- | --- | --- | --- | +| `screen-capture/` | `EvanBacon/serve-sim` — a capture and encode pipeline we patched | Apache-2.0 | [`screen-capture/NOTICE.md`](screen-capture/NOTICE.md) | +| `scrcpy/` | `Genymobile/scrcpy` — the server artifact, byte-identical, not yet consumed | Apache-2.0 | [`scrcpy/NOTICE.md`](scrcpy/NOTICE.md) | + +A first-level directory here is third-party or derived-from-third-party code, and +it holds the artifact(s), the license text(s) they require in `LICENSES/`, and a +`NOTICE.md`: upstream and author, the exact commit or release, what was taken and +what was left out, every deviation and why, the command that re-verifies it all +against upstream, and — per file, because a directory here can hold our code +beside theirs — which files are ours. `src/source-hygiene.test.ts` enforces +the three-part shape, and the rules are +[`docs/internal/agent-rules/vendor-and-third-party-code.md`](../docs/internal/agent-rules/vendor-and-third-party-code.md); +the whole inventory, including this tarball, is +[`docs/internal/VENDORED-CODE.md`](../docs/internal/VENDORED-CODE.md). + +Nothing under the directories above is downloaded at install, at build or at run +time — they are in the tree because somebody fetched and checked them once. diff --git a/vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt b/vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt new file mode 100644 index 00000000..3325e9f3 --- /dev/null +++ b/vendor/scrcpy/LICENSES/scrcpy.APACHE-2.0.txt @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (C) 2018 Genymobile + Copyright (C) 2018-2026 Romain Vimont + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/scrcpy/NOTICE.md b/vendor/scrcpy/NOTICE.md new file mode 100644 index 00000000..16245f5b --- /dev/null +++ b/vendor/scrcpy/NOTICE.md @@ -0,0 +1,142 @@ +# Vendored: scrcpy server + +**Not used by any code yet.** Nothing in this repository reads or pushes this +artifact. It is vendored as the pinned dependency the planned Android screen +streaming proof of concept will consume, so that the PoC starts from an +artifact whose bytes, license and version were settled before it was written. + +## Upstream + +| | | +|---|---| +| Repository | | +| Author | Romain Vimont / Genymobile | +| Release | `v5.0.1` | +| Release asset | `scrcpy-server-v5.0.1` (733,930 bytes) | +| Revision built | `dc8a55a24c27ae78ae202b5a3288da681d0f141a` — from `META-INF/version-control-info.textproto` inside the artifact | +| License | Apache-2.0, verbatim: [`LICENSES/scrcpy.APACHE-2.0.txt`](LICENSES/scrcpy.APACHE-2.0.txt) | + +Upstream's own warning, from the top of its README, applies to this file: + +> This GitHub repo () is the only official +> source for the project. Do not download releases from random websites, even if +> their name contains `scrcpy`. + +## What we vendored, and what we left out + +One file: the release asset `scrcpy-server-v5.0.1`. It is byte-identical to the +asset upstream published — see `SHA256SUMS.txt`, and run +`shasum -a 256 -c SHA256SUMS.txt` from this directory to check. + +We vendored no client, no build files, no documentation, no submodules. scrcpy's +client links FFmpeg and SDL; the client is not in this artifact and we do not use +it, so neither library is here. Upstream ships one root `LICENSE` and no `NOTICE` +file, so Apache §4(d) has nothing to carry, and we ship that `LICENSE` verbatim +beside the binary as §4(a) requires of a distribution. + +### What is inside the binary + +Despite the name and the `.jar` habit of other projects, the artifact is **not a +jar**: it is an unsigned APK (a ZIP) with 13 members, of which `classes.dex` +(2,302,128 bytes) is the whole program. Inspecting it — the commands are below — +shows whose code is in there besides scrcpy's own: + +| Inside `classes.dex` | What | License | +|---|---|---| +| `Lcom/genymobile/scrcpy/…` | 164 references, 149 distinct classes — scrcpy's own server | Apache-2.0 (upstream's) | +| `Lkotlin/jvm/…`, `Lkotlin/collections/…`, `Lkotlin/…` | 1,106 distinct Kotlin standard library classes, plus the `kotlin/*.kotlin_builtins` metadata files in the ZIP | Apache-2.0 | +| `Lorg/jetbrains/annotations/…` | `NotNull`, `Nullable`, `Contract`, `Nls`, `NonNls`, `PropertyKey`, `TestOnly` | Apache-2.0 | +| `Lorg/intellij/lang/annotations/…` | `Flow`, `Identifier`, `Language`, `MagicConstant`, `Pattern`, `PrintFormat`, `RegExp`, `Subst`, `JdkConstants$…` | Apache-2.0 | + +We checked for the libraries that would have been a problem and found none of +them: **no** `androidx/`, **no** `kotlinx/`, **no** `com/google/` class +references occur in the dex. Every bundled component is Apache-2.0 like the +server itself, so nothing conflicts; they are named here because they are +literally inside the file we distribute. + +The server's own code is compiled against the Android framework and reaches +hidden framework members by reflection; that is upstream's design and needs no +header of ours. + +## Deviations + +**None.** We never patch, re-pack, re-sign, strip or rename this artifact, so +Apache §4(b) (stating changes) never engages. Renaming it would be a deviation: +the filename _is_ the version, and `SHA256SUMS.txt` names it. + +## Why the version can never drift + +The server refuses to start unless the version string it is handed as its first +argument equals its own `BuildConfig.VERSION_NAME` by exact string equality: + +```java +// server/src/main/java/com/genymobile/scrcpy/Options.java, at v5.0.1 +String clientVersion = args[0]; +if (!clientVersion.equals(BuildConfig.VERSION_NAME)) { + throw new IllegalArgumentException( + "The server version (" + BuildConfig.VERSION_NAME + ") does not match the client (" + clientVersion + ")"); +} +``` + +Upstream documents the reason in the same breath as the protocol: + +> The protocol between the client and the server may change from version to +> version … and there is no backward or forward compatibility. + +and, of the wire format itself: + +> [It] may (and will) change at any time for any reason. Everything may change +> … from version to version. A client must always be run with a matching server +> version. + +So three things are one value and must move together or not at all: the +filename, the version string a future caller passes as the first argument, and +this notice. Bumping the artifact means re-downloading it, re-checking its +checksum against upstream's `SHA256SUMS.txt`, changing the filename, and +changing this file, in one change. + +## Re-verifying provenance + +Checksum, from this directory — the same command the CI check runs: + +```sh +shasum -a 256 -c SHA256SUMS.txt +``` + +Against upstream's own published checksums for the release: + +```sh +curl -sL https://github.com/Genymobile/scrcpy/releases/download/v5.0.1/SHA256SUMS.txt \ + | grep ' scrcpy-server-v5.0.1$' | shasum -a 256 -c - +``` + +Both print `scrcpy-server-v5.0.1: OK`. The expected digest is +`764eb6f79811d5211fe9df341120882ba9994c7a61b897d7bf3fb662e53bc536`, which is +what upstream's `SHA256SUMS.txt` for `v5.0.1` gives for this asset. + +The contents of the artifact, for the table above: + +```sh +unzip -l scrcpy-server-v5.0.1 +mkdir -p /tmp/scrcpy && unzip -o -q scrcpy-server-v5.0.1 -d /tmp/scrcpy +grep -aoE 'Lcom/genymobile/scrcpy/[A-Za-z0-9_/$]+;' /tmp/scrcpy/classes.dex | sort -u | wc -l +grep -aoE 'L(androidx|kotlinx)/|Lcom/google/' /tmp/scrcpy/classes.dex | sort -u # expect no output +cat /tmp/scrcpy/META-INF/version-control-info.textproto +``` + +Upstream's license text, to compare with the copy in `LICENSES/`: + +```sh +curl -sL https://raw.githubusercontent.com/Genymobile/scrcpy/v5.0.1/LICENSE \ + | diff -u - LICENSES/scrcpy.APACHE-2.0.txt +``` + +## What is ours + +Nothing inside the artifact. Ours in this directory are `NOTICE.md`, +`SHA256SUMS.txt`, and `LICENSES/`'s file name; the license text and the binary +are upstream's bytes unchanged. + +The precedent this layout follows is NetrisTV/ws-scrcpy (MIT), which vendors the +same upstream as `vendor/Genymobile/scrcpy/{scrcpy-server.jar,LICENSE}` — the +binary beside the license text in the same directory. diff --git a/vendor/scrcpy/SHA256SUMS.txt b/vendor/scrcpy/SHA256SUMS.txt new file mode 100644 index 00000000..a8144a7b --- /dev/null +++ b/vendor/scrcpy/SHA256SUMS.txt @@ -0,0 +1 @@ +764eb6f79811d5211fe9df341120882ba9994c7a61b897d7bf3fb662e53bc536 scrcpy-server-v5.0.1 diff --git a/vendor/scrcpy/scrcpy-server-v5.0.1 b/vendor/scrcpy/scrcpy-server-v5.0.1 new file mode 100644 index 0000000000000000000000000000000000000000..e50571a9b76e6141a8e5c71125b5b058a0317b7f GIT binary patch literal 733930 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 z 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
  • L9rNXS_5Wa(Sm)3#;=h=K7>1p!jt8M)#r0w63!*iK z!*S8AVk7->1FA3C0v%!(4XT@L3vCN(*)7Cx($%Pwx>p4y)!n2mdtX5``Br-XNa_?n zS2M58sNcwQF}TSirY!D0Ggdx~8Cw@uI2=<5P0IHk#uMg>WHQ~JL9ak`UUCFY&$@1K z>7@alG0^O{3fcq3GMS#r{qSa#8vUsm|H&r|R@z0mJy3EVB$RJaC z^%^oU|6&jVD!95}s~`}8%D1Sw!5KDF1i*j^i!7V9KUVg#54fL+XY6#KQMbmV-LKKO z?r0n6{Zs`{vn*}cI|crqL1KxqsD7Zl>XF8;bEgIYqF_S%x#{`2N#ePZq@oA3;1;-f z(@dt;G#M4vIFr!fR?7Y~CY5t-H zhK_e;GZxz+hmefcNJ7<=W1`v4Tw+Xhl5h>II(hYeanEJ^uP0?mg?Y1LJOrTY@ceKu zsbHg^X7j%mtVS_j%JRRarW%+$b;O*m!dtE6cG@Fqy>pr;Oag1;YB~gJl1CEu_2Nj^ z-70l^&%oL`KW`d8vIEV$nymby2a)-?8`<9Q(BbRluP03p7>S7;s5x(^Z+k-Qq}-dw z0cXFBUOroO;?9Flui^^+a@SCzAC?P1P60(KAmYBc{mzkU9SWg*E6F|L(Ee?C3BJc#Kzt%bOUHfmZE8 z+G=#-UNJFgD={$s zyI4FM2S`RCx^w@7?7Uh&rGa;;r3!Z{*hoHoYiKQ52`c^iU&58$?jYN5g%>H$;fij} z>@$%zJ#*WAZ9kdk-9?9{d(9`gs?>{hw;bbc4+9w1A*^NhKg;4DH5MwqKV9-)DJW-s ziu6W=xW$zXe-EHt4{z5=6iyosTwheB^c;nhDhw>&qr7Lh!hDv+C`wrr8K=D!J}bG^ zP`Y8!m(dO^P8$RYek1?Ss! z)T*8VvtiCFYy>KP=g4*L;h|s%q(}#~qa0c&1gSel%*(u>4gvVFZ)wO)jAwr>&j5dx zOtm^vH&9=akBw}ZwOP-}*>)4(&8*B@TwnSc&~&1{XOL<9@ERL@jY7>D)X!J{vl@jX zM}eKcB6UP|%S<{d>P@05%rBna-b>%pxD^E6CAPy_j7Vrf>7+9Lcw+Z55wRK4!F);j zaG@^P`-yXx+JP0^_s>buDY+|tWy6fh#**-_Nwm2Fzc+~C@i7Eu*GFVxuH>mvbYe~qeGp0976pBdi9KPzNEh(Kwn#k^P+ za^fXwVL6$a&`N&;X!TA{qAA~y?XfK@KXQ`xd+X++Y>||Uo zd~n|C2+I25u+8VU{(rifgFPr8dB=B;;jV#JLZcK2%EL{|hi{=K5NKW;g*?HSmEAhN z7QOi)PBmpYHL!h8_gkUEQr)3#-L>p>NaQHu=H?SOD!VOLHkHo)?_oJi+Fi0ElJ8>BaA` zY?iL=EM8iv*dqO)N=r^%aI*Nb{DG%2oTb^gw;~MzZv$_)y0ZEA!RKZajL%e^9z=`1 z{avu(upKIr#=k*12F}${pwIWp#_o9U9f0=5Mw|k9cB+WpJdFPSDdIRRMf|azyNq|( zp}c~dZ$9%1t#0&~trTb;E&TD|QLw^hk4=Q6P03S<<2~h&ySsealqHYPxx~x5gvXeV z5_X#zxv0|CcDq_p-3)J-Ej*s$e;q&FMBp49;7d?ID37YC?mf>s6TlvZO`X2-2R zPRPl*y5qre#NeRwGa~Z*OG^Md*|7Acy`+#CN+yvT^s#hqq6RP^{wyu(m&e^bz?S(W z^tJbskGX^%&3y`sm-F8N7R6eA**$e+Row~z4nt~jGlGJLUo4s+Wwj_#%Hv3nn1476 zubF4NUSOE}>MjW$^6Sid-vLk0vKErO3#IEaUxypCfZ0rPr8+U*;ih!UVOxpS+Q~E(kPU>U^L=9J+7m)V$XIm7Ei5B(P zurcZU-eTGkVjQa?+VlswH?7ZkcX(WO@-%-8kG<*Y@NOYSz?L-3k7k*gHgi*H?x|DT zIh*tB$L-k5hOQFqe^3u7&r?*C@U#7AOya^l&Tk6;;9Bh1vUq=;Bn4%1Y$I0vS=X*O z99XXUt9Q3kL}>96G3(h=PgdL`TS6*<@knQ#`c)x9@e}M$)R)|v^`v=;Hek%tx;BlN zI<^b*BJB=y(Ump7aJAm^ZZko58u4@P)Jgp0awamNBPJQER2kg6@kQQb)2>#gJR;-m zay2OkbQ%!GD95oId{?d!VOwkL#UI~dg~sBrWf7=1GfN+_)x|#pzmgGe>73gMVW&h? zb11U*mxtrZ$LDQcbBuf`mCX!WT=dl$yC`qJ+t+;Secc@=Caw%LKLiTOQbqZ~O0`cr zJGT~Ya%;#p3z#aMk8p)gM~|y?-j)Qv2``|ROXx#!@0DRzR>Q5!p(yx)VDLY zb27*X77=&Z3N_%(C3@ATBINBlF>asz){jRdM!iPZ1d=wbXW9{%&#of0D8`Lp^FJ=L zcNUL5W_c1*H*YT*w4BT}_bgsak8wPUEeB$o&DY_HS9zLbmY{zGa%- z;0k(pWZs$8MvGy*82F+`gJ5&aT}~MkP!H()bq?Oob)H7)?neANiVHWb)9kxulWv^2 zjNA5ZkiPazI&I*vAC#fB&T9KdeDR`dRozh~8|*`WhlFA3u`J&15r%nv)au`gtY&8@DH|h<4rF|Kau~N9IWbaXqbZ7V(^9tvy z@aESU${=xtgn_DrO?-%pp1e)xoLyykW^hvgLe0;0H<HvnE#=WLkMC6HwSwC9gyxhfwSn0xT;$^mtMG&@{|u}N+(oP= zm-Dkv8rzo@+yK{VE@_SYQrC8ox?*dNdQEkDO1jwedXJ0YIKn+nWqA-AE?;5TwiFHz ziUrOpA7=j`O+QAO`o7e>u=mwDWAE|H81eC;3qy?CxlskQj&R@mJH~k{!%mN%@6HD_ z%u3ChZmcq}6w{}^+?+`GdhABy-;UtmGUoeC2B^9-B_uYR@zO!Xp=-Y{&12K8VpuM= zrZwU^&HyDFsXv4vUXGn_&gVo7irZz{Sd}Iy?IqwD)( zjN^HHZgW&^k#1j2)C$;lMPE(%L0qDJ1adZ(I5gT80!fBz&W#sVCvr0+7W?DJ{Cd{j zT&G@_L0x-Apc=GlD(;ncSfL7)hPgU7jRp#83O@mYjwLqd&y^Sv`^M~v{#tcU5DZMT z>;EC_J;0h=md5epQ4U8@5RfKKMZ`i;5ePL$Q4vumoNV?u=rp)-Srr9s}U|;eZ=r)n_AkZo+C+P*8v8wFzJ3oury12(asl{IqeWT zb;ht0reX|^muM*+&3r49voSMZRqO08vSl=L1(oZ$blU_7ohp-|1uaCplCucEbkSIw z8sOYzLy4c6(OV*B1T-jB+t0Iphr}g=dJV2DB5QbFm8dmv9ZtR3dhx?e)L4ei@TbpR zmUHAiUA0%5n$K=Vrouw$)}av>^A-qiT|d(Bh0gEFG_oS*_Co3!ZVe`>cpv={f37#3 z`uNB9RXn-%mhuXDrDwx*$SBna@-CCHv$B3MeUsU|cfvXl_-s0DT5f4xz!*+l!s zTK11{)0w<4DXd6dms7GVS$eGyxVwi@Hn9s@MND2^QVH!J1=GdZ1_HIOWr&ZfdA z40x2JyG+Z$Sx^pBid87BU_FA7@ZQuzZ&a`=$l;U=?8}zjDzs9J*zi>!{BUYRwkgYB zn|5@K{RU*2x3!tQWrx2;cgo;@F5uG)5jgg=Rnn{a8FC&LD6t&Y;58MwCew0)_0AXF zZiQRb-!+x5ok2~x8>w%XoYXE85~y6=m6PduOL^Ayy@vS3DeC14#cq$|IEdne;4*dP z5k-l1Ukh`Y`U+irrFNrh95K*87TDdYH`j5_iSL(n0!!A1ICtwT#|wH0Str-%@ncPR zwJh;5?0(#HmELm}yEv?8eEN%q3o`?7{Tk%p3fZbMwL*{vp=V|nE>*VrcaZ1%LT@N~ zg%?V_5B+2o*?d*^F3;|Fl37ixW0aN>r8{Ojpi7s zdN5+Tt=w!GWryrCNT&`{YwIn~tU@hOx{6c$kBUxtw$ceY@IeTBGNL2G#7c=0mehTO z0PFCUV(w!J{Znx{Un4Y&8X|0pRxITNdkw@VNlsY^%IbA|&M~|SS}e4*mvK+CrugpN0tR(2yUJ!q?D1Uj#6$)Ld)0JN>X>ieO$VsWyXk)itx8 zj4e z2@df@SNWx(IVFP4`j`}hvo`0_s9UZT{elgY1afp!f(^46mRsx#h54cp5rh{K22!EV zQ_c-j=bGy!Awlo+!$HHJyy|f0wPHi;%qvbU`7L+0XXf0IZ?f`LYMS~X+(%wNVmikC zYU&k-bJB|6yzxn(LZm93&-=*>_F+kF!KoQ=3Fvdh*A<)*0?r(-mm0doL6xR&MZ&19 zk1$HKACEMG6L5~7uB@fG)7H8QG^xdG>qX$#Kvu-u;UR2HYpKw6lw8Vl>km;go?BB@ z1D!8dHOe62KxiyQ*3pk3DLxFig4Qr`#_)in4Ap|2BK=#2-+=AR>@cge0`Fy}Ae$=~ zwh`>xxc*V2z*;3!%X-fg<2hnqisnn|ZiA7=n5H$83+z8}9u{q<)Ev6vWEt;qb~x4I znUvun3r}(({*m0xPi-lGKqBVHw})LVJSz*^sm;z9<$V>q8r}n=D>bWDOUiy&yH#G8 z@_X}I_QMAPt?g<*Br#C66oxrLfb@k<_lK-5MC{I>0~SoIS;?YPgjeoUR;=WkC=$MU zeV+*16r_cLY$Kk9n4afKcbT4_md48*WbC5Zd?Q%z7YNh+j9n0$a0l-uo3kjJ8_1ik zVlMELjnB-=39L_rSaTbi$z}$l#Z1DqJCY|npV*{kQ7&hyiyhA2+-)_36>1jH2~fVg z9N{7iKjw8rXssLJvH9cc1(WQ2!n*&m=p1^iSyESPBgN7O)DTg2sM6k_!Izs zV_9Zvl}fuoSSwXuy~SyP#|zRIt!UX7lPRGo8eU;HpYimq42&PVGI;n}9q`v+oZl$* zsKe#ug@mMcTYG$1V{DfP6d;(Her`>nJ%sV#74>2K_O#j^Rk$@e-3PO9tq<|=8})Tb z!jNf+edC_sXPmqFKaIe#>CX2{#=n}DTZg!h$<5$$HcTI!!Z@N#F~TU9It!_r{B!g- zM#lW^$Mio{#;8Y~nubc8i3(d?N~XwyRe>Vkaxld7oM44%b#|cnj!hjrSf6(_OTx}Z z!=U%9PXA}-L=$mtJy_&5aPqFH6w1}~BboGiwCtg+%?F$7h^qigG{)qT0Oh0`i|~sM zJ5l0hGqx6`Zn~fRq4zs^i!<0zy3$|Ms1=Mz?*K0`a$Sl-16s9dt1N*!+(r|Jo*La) zHT2XHz_;wsKv`1Xx1%|NC5dm3l@WoT9Y6fo5^}0>;(u;Cq|oyF_;yht2h%meU7L&y z!=8E3Q|x$>{Bv~x?%96w?DUs&ec7^Iydu;ihKMIz-Fi={yLqs0XOH_VvJFU62LY@}A7~lI!UW4Mqs%!NK z?hfRsJUk`fHhzEcF0&!{o7rdd4}Ld3zS*s~0eJh`x_(K5^d|N6Ctl;v;)#d8tt$Rs z6lvD2V$^n5sOomR97KEwBeSgT8l1{H#ZKyuzKho92Sd;}IZ!g%esPAak8DUH9P@h~ z9z1<+E0d|vc-19!qn_=>HD1nRj?E(+@)ZPy!fvb)e4!6H54_K@Yyx!FJAFXqe7>s1 zFvC^_!lKO2b~o5%J)8_TMAYt1_D=HAEw7I&dGGRW<_=yFdBdP*M#)*i*#_8Dzz;IF z#pdJ?gCSHA4CC^Um;r05t$u6XnB%S|(BeM=nhp-winaOZ9S--dH?;Mg^X-##3M&li zR+${~jf`*|$=dd$Oa>w_PVrD}{#eCHj?OYKG6M0r zp&7*zPU;Sywi(+G;`C};iGKxG4Us*mzz+*Qzw(Vfqmx%E)i{(cwsko*v4PeS?6l6w zz2#6ox^ablpOtKHOK5YYll?bbB#Zm##$six6MiUJ{%+dlM(}nlMn^_5UN;6ytDx**+K%T$;wm#{Z(IO|S6!fbngyxCCZfWCY<&gFjVqQ=F*0cVmV^9ix1S;K{_Pmd4dw z2qeH&aT*Lxjrm3%3y!eh=*VxN zhxGcWjg)H!gtV|pzHu}6FhXzWiaw~v5g=Fg9d4kyhs|@`8mJk&T3OP36I)4KT}FL7 zz7_xT0L)=EYNoKVUl{LRJPrT8KoW5UWxN7<_C13$(v9oHehA5XAkYC%@CRoa&p!UE zE^PfX>L}(%?skh#zrg3Ht31!p#yN@_9k|i$aY9S%lXK*l@%IY3XWX}tWs5t&h3`kE z5Ov`Yn4TvqGOM=fZi>yf3g+R8b{mL}Y*#|}W`f&Le>Y!EvPLSRt3UWwH9=Z9SjXj5 zzrdM?%%1ZgN0U3gL;YK)<%CY6KPqRtp+mV6j`jGJC`MsjL^T4`TI*kp2!&M}89LkE z+iG^IqaE97YpmuXa#K^=H(Z}g+Z4s8cSH<#MZPUNE7V&9*)cp(wbeZXJD#ee25pr8 zYT375dU4 zSr42g9w2r`(Gv$86y%-JeH^!_XS7oUuu2zo?a;QtS5U!OmpZuV@o|)O0rOrL>`%mX z6~!)vYut7v#_%jR_!AejzaqV#yEEM7Tn6RSTYlrq;!|mqKc*z;tEQ><+qZ0Sdm-gn z{=xu%pu`8gp&)J(O>P$@MjG4*DhNDZ@~RY@6rGN^7=SQUzml@ILQruxv6@3{3o!YCE+iU)Rm4TWB$0{&b$Zc+DU3Vx^$GWDL ztNeKyBu}VKEhcRYDx9svfhtIkG$EKkp8gL)5u|G}_*$IC)z)f0_@GVRkFQyB^RJ~4 zM6UZis&~DI&wPf^F-fl)u!`B)BjaBcGf2=;A=~jiDeq&n7 zM0@i}caeqna-K3zd>jkC8lZY?D_4rke`U`b^YKuR?Kd(hNAdsk~_Z<+} z%m@w$bWDn@Jz75S(XY|NKqbLb7^nv*rNg+H$^Xvu7W(!kP3^K4`+-&y7|aNa;Ws`) z_oQ?qk={RUru6g^JW4p|o_;!aiSOBpZE1cBwWBAbAuijZ-}%(+1>LlGAr`fPoo|L_ zfppFnpa=FWZ`scJu(x~=Cz2BoI*&>XH4!uZSyeX|J=wleewKPu_rZ0i-)4th<-b5pO25mBGzZl7GP|vd?NjXQ4Yu#@Qx9G?@D=~^b zdIC67pH99kReGkreQu&2{(Mr$D0-oCHh03lvgk6gtfFaIqt;Hj0%GtH+2VB2u{C=% zBV+EYV8rIC(ua^M@}DZMpVO6k_%TUp_yMXEar=TnU0BCob(cEamlgRs%VWC{da{Z~ zztWR&Jp1L}lQ9tL=mE{e^?Y=?odhT-b8luuTPp;0VK3&g}X6<-5JlH zo6vK6>bX;2P73UOkqhnANs+Bf5%`9*RR?x@dV~lK{|?d9#oLtyxP&azQkf#3@vHWzYg?aK^POyx@_K�<5~8Br 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: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ξb~bmy2O-1CF;^-U@~Bm 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

    J_Uicf*j`$F3F{+@^^L zq&&UQQ9XHS#z_s^>!u`3K9?^+mda;vaKZb`DxV~-Fy;zYnE~Hwbz|qvy@oKGm5cZG z_2Yiw&(PzVKGzJ-+8Qu9PxSfkp(YnuVrpHC84@3jir5VevQVb}P}i{U&_6EG9~PNd z>c9DoPv>cHDf3_l+kbnoww77w%l6T^3%>Wv3$uFHN6;gm@hZH-*p$C@z7rwlTJqq^Q&`*^#Z zhkY7L!jzH?s5dJQ@K%Dkaf;Mts( zv2As)$7FR)$0aSN$7FqpBq9BAV?S;lnUB*wau2nlXil^o9Zk9iK33$mi;QKM{_6bo z74c9W6x|BVBZi7K7CAfdX9_R!)q`5gy6nE&i>tkS@x@*McHjx$2p@0Jr=#LOjynI~ z)m8o{%vHqxbjGVwu3S8?7LJ+vY>FGTDj?3Ehs=avY5%0DSTY>u*O1RhC>+Vba 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

  • DUeFLGTKdVu77%_zZ@|+;T)v2` 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ǭNg8#C5&TsM?$hU4o1^tYer=Xnw!*dHC$-F| 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 z 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≪ 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

    k-+7^6;MzMBFIm&C@<(z5_j|4KS916bO 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 YaYXE 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່Z7tiS^9aKF5^`}C} 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 vcgeIvo4;puXy`#(iWOWWE0 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 z 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 (npm `serve-sim`, `@expo/serve-sim`) | +| Author | Evan Bacon | +| Commit vendored from | `6d009686ed08f7d8bd19bca9f9f67a9237931f44` (2026-10-11) | +| License | Apache-2.0, verbatim: [`LICENSES/serve-sim.APACHE-2.0.txt`](LICENSES/serve-sim.APACHE-2.0.txt) | +| Copyright, per the license appendix | `Copyright 2026 Evan Bacon` | + +Upstream ships no `NOTICE` file, so Apache §4(d) is vacuous here, and none of the +files we took carries a per-file license header, so §4(c) has no per-file text to +retain. The license text in `LICENSES/` is upstream's root `LICENSE`, byte for +byte; it is named for the project it covers rather than plain `LICENSE` because +this directory also holds code of ours that the file does not license. + +The four upstream files this directory still carries are 586 lines as of that +commit (`FrameCapture` 436, `FramebufferSurfaceSelector` 77, `DeviceDisplaySizes` +48, `SimFrameworks` 25) — a figure that cannot drift, because it is a property of an +immutable commit. Origin is per file, below, and the exact command to measure it is +under "The diff against upstream is the early warning". This notice deliberately +carries no count of our *own* lines: a stale count still reads as a fact. + +## Where every file came from + +Our target is one flat directory, `Sources/simlock-screen-capture/`; upstream +keeps the same files in two targets, `Sources/SimNative/` and +`Sources/SimNativeSupport/`, and their paths are given per row. + +| File in `Sources/simlock-screen-capture/` | Upstream path | Origin | +|---|---|---| +| `SimFrameworks.swift` | `Sources/SimNative/` | theirs, unchanged | +| `Xcode.swift` | `Sources/SimNative/` | theirs, unchanged | +| `FramebufferSurfaceSelector.swift` | `Sources/SimNativeSupport/` | theirs, unchanged | +| `DeviceDisplaySizes.swift` | `Sources/SimNativeSupport/` | theirs, unchanged | +| `FrameCapture.swift` | `Sources/SimNative/` | theirs, **one patch** (deviation 1) | +| `H264Encoder.swift` | `Sources/SimNative/` | **ours**, rewritten from upstream's | +| `VideoEncoder.swift` | `Sources/SimNative/` | **ours**, rewritten from upstream's | +| `PixelBufferUtils.swift` | `Sources/SimNative/` | **ours**, rewritten from upstream's | +| `Xcode.swift` | `Sources/SimNative/` | **ours**, rewritten from upstream's | +| `main.swift` | — | ours, entirely: the CLI, the frame envelope, the lifecycle | +| `scripts/unframe.mjs` | — | ours, entirely: a hand-run diagnostic | +| `Package.swift` | — | ours, entirely | + +Four files are ours because they were rewritten rather than copied. All four use +only public Apple API — VideoToolbox, CoreVideo, CoreMedia, ImageIO, Foundation — +which is what made rewriting them possible at all, and it is why they carry none of +the breakage risk this notice is mostly about. The rewrite kept the wire contract +byte-identical on purpose: the H.264 payload is still AVCC, the description frame is +still an `avcC` record, and on a live capture the `description` payload came out the +same bytes before and after. What a rewrite cannot survive is the diff below +reporting a small comparison for those four, so read that section for which files it +still means. + +`Xcode.swift` is the weakest provenance claim of the four, and saying so is the +honest thing: it wraps `xcode-select -p` in a `Process`, and any rewrite of that +converges on the same few calls. Apache-2.0 covers expression, not the idea, so a +rewrite that looks alike proves little. It is listed as ours because it was typed +here, not because it is clever. + +Three files were taken and later removed as dead weight, each still byte-identical +to upstream when removed, and nothing in our build reached any of them: +`Sources/SimNative/StreamFormat.swift` (our `main.swift` writes the same envelope +itself), `Sources/SimNativeSupport/DeviceHubKeyboardRouting.swift` and +`Sources/SimNativeSupport/DevicePose.swift`. + +## What upstream has that we did not take + +The whole of serve-sim's NodeAPI binding and everything behind it: the +node-swift module `sim-module.swift`, the HID injection and accessibility +bridges (`HIDInjector.swift`, `AccessibilityBridge.swift`, +`DeviceHubKeyboardBridge.swift`, `DuoHIDBridge.swift`), the capture orchestration +we replaced with `main.swift` (`CaptureEngine.swift`), the Duo hardware renderer +(`SimDuoRenderer/*`), the Objective-C camera, accessibility and HID helpers +(`SimCameraInjector/*`, `SimCameraHelper/*`, `SimAXSettings/*`, `SimDuoHID/*`), +and all of serve-sim's TypeScript server and preview UI. simlock needs one thing +from serve-sim: the framebuffer of a device nobody is looking at, encoded. + +## Deviations + +Two that change behaviour, both deliberate. Each is here because a later reader +must not "clean up" back to upstream's behaviour and silently break a simlock +feature. The mechanical consequences of the package layout — the files not taken, +the import that went away — are named where they show up, not counted here. + +**1. `FrameCapture.findSimDevice` searches the device set named by +`SIMLOCK_IOS_DEVICE_SET`.** serve-sim looks devices up in CoreSimulator's default +set only. simlock boots every simulator into its own `simctl --set` root +([ADR 0001](../../docs/internal/adr/0001-simlock-owned-device-roots.md)), so a +simlock device's UDID is not in the default set and unpatched serve-sim answers +`Device not found` for a simulator that is demonstrably booted. +CoreSimulator has no environment variable that moves its default set, so the set +has to be named in the lookup: with the variable set, the patch calls +`SimServiceContext deviceSetWithPath:error:` instead of +`defaultDeviceSetWithError:`. + +The patch's own comment in `FrameCapture.swift` says the same, and is the +authoritative wording for it. + +**2. There is no node-swift binding.** serve-sim exposes its capture as a +NodeAPI module through `sim-module.swift`, the only file of its package that +needs node-swift. We do not embed a Node module: the program writes frames to +stdout, so the daemon supervises a capture exactly as it supervises every other +tool it runs, and `sim-module.swift` was never taken. + +One mechanical consequence of flattening two upstream targets into one: +`FrameCapture.swift` no longer says `import SimNativeSupport`, because there is +no second target to import. Nothing else differs. + +## The maintenance risk, and the escape hatch + +The reason this file is longer than a license note: the program reaches into +private Apple frameworks, and an Xcode major version is what breaks that. + +`SimFrameworks.swift` `dlopen`s `CoreSimulator.framework` and +`SimulatorKit.framework` from candidate paths — SimulatorKit moved from +`Library/PrivateFrameworks` to `SharedFrameworks` in Xcode 27, which is why both +spellings are tried — and nothing is linked or imported. Every private member is +reached through the Objective-C runtime. The surface, measured off our own +sources: + +| Reached by | Member | Declared in `facebook/idb` at `66c6f7bb98324212dcf047dc7330048303b0163d` | +|---|---|---| +| selector | `SimServiceContext sharedServiceContextForDeveloperDir:error:` | `PrivateHeaders/CoreSimulator/SimServiceContext.h` | +| selector | `defaultDeviceSetWithError:` | same | +| selector | `deviceSetWithPath:error:` | same | +| selector | `SimDevice io` | `PrivateHeaders/CoreSimulator/SimDevice.h` | +| selector | `SimDevice deviceType` | same | +| selector | `updateIOPorts` | `PrivateHeaders/CoreSimulator/SimDeviceIOClient.h` | +| selector | `SimDeviceIOPort descriptor` | `PrivateHeaders/CoreSimDeviceIO/SimDeviceIOPortInterface-Protocol.h` | +| selector | `framebufferSurface` | `PrivateHeaders/CoreSimDeviceIO/SimDisplayIOSurfaceRenderable-Protocol.h`, `SimScreen-Protocol.h` | +| selector | `unregisterScreenCallbacksWithUUID:` | `PrivateHeaders/CoreSimDeviceIO/SimScreen-Protocol.h` | +| selector | `SimDeviceType mainScreenSize` | `PrivateHeaders/CoreSimulator/SimDeviceType.h` | +| selector | `portIdentifier` | **not declared there** | +| KVC | `SimDevice UDID`, `stateString` | `PrivateHeaders/CoreSimulator/SimDevice.h` | +| KVC | `SimDeviceSet devices` | `PrivateHeaders/CoreSimulator/SimDeviceSet.h` | +| KVC | `SimDeviceIOClient deviceIOPorts` | `PrivateHeaders/CoreSimulator/SimDeviceIOClient.h` | +| KVC | `SimDeviceType bundlePath`, `bundleURL` | **not declared there** (`idb` declares `bundle` only) | +| class | `NSClassFromString("SimServiceContext")` | `PrivateHeaders/CoreSimulator/SimServiceContext.h` | +| file schema | `Contents/Resources/capabilities.plist` inside a device type's bundle | **not declared there**, and undocumented by Apple | + +That is nineteen distinct private members: eleven selectors, six key-value-coding +keys, one class name and one plist schema. `facebook/idb` is MIT-licensed, and +fifteen of the nineteen are declared in its `PrivateHeaders/CoreSimulator/` and +`PrivateHeaders/CoreSimDeviceIO/`. +That cross-reference is the escape hatch: when Apple moves or removes one of +them, idb is an independently maintained MIT source to re-read the declaration +from, including the comments there that record which Xcode versions had which +shape (`SimScreen-Protocol.h` and `SimDisplayIOSurfaceRenderable-Protocol.h` in +particular carry that history). The headers are at the root of that repository, +so one fetch reads any of them: + +```sh +curl -sL https://raw.githubusercontent.com/facebook/idb/main/PrivateHeaders/CoreSimulator/SimServiceContext.h +curl -sL https://raw.githubusercontent.com/facebook/idb/main/PrivateHeaders/CoreSimDeviceIO/SimScreen-Protocol.h +``` + +What is *not* in idb is the other half of the story, and it is where a break will +land first: `portIdentifier`, the `bundlePath`/`bundleURL` pair on +`SimDeviceType`, and the `capabilities.plist` schema. Those have no second +source, which is why `DeviceDisplaySizes.swift` treats a missing or unexpected +plist as "fall back to `SimDeviceType.mainScreenSize`" rather than as an error. + +## The diff against upstream is the early warning + +A private API that Apple changed shows up as a line upstream no longer has, or a +line upstream changed under us. The only way to see that cheaply is to diff our +copy against pristine upstream at the commit above, so: **do not commit pristine +copies.** One source file with two homes is a file somebody will edit in the wrong +one. Fetch instead, and diff (from this directory, so the relative paths resolve): + +```sh +curl -sL https://raw.githubusercontent.com/EvanBacon/serve-sim/6d009686ed08f7d8bd19bca9f9f67a9237931f44/packages/serve-sim/Sources/SimNative/FrameCapture.swift \ + | diff -u - Sources/simlock-screen-capture/FrameCapture.swift +``` + +That prints three things and nothing else — the `MODIFIED FILE` header Apache §4(b) +asks for, the device-set patch, and the deleted `import SimNativeSupport` — which is +the whole claim of this notice in one screenful. If that diff ever grows past those +three, either someone edited upstream's capture logic or Apple moved something +upstream, and both deserve to be noticed rather than committed. For any other file +still listed as upstream's, substitute its upstream path in both places: + +```sh +UPSTREAM=packages/serve-sim/Sources/SimNative/SimFrameworks.swift # or SimNativeSupport/… +curl -sL "https://raw.githubusercontent.com/EvanBacon/serve-sim/6d009686ed08f7d8bd19bca9f9f67a9237931f44/$UPSTREAM" \ + | diff -u - "Sources/simlock-screen-capture/$(basename "$UPSTREAM")" +``` + +Run it before and after an Xcode upgrade, and again before taking anything new +from upstream. A file whose diff grows by lines nobody wrote is the notification +that something moved. + +This is the reason not to rewrite a private-API file to escape a license: for the +four files we did rewrite the comparison above stops being a detector, because a +whole-file diff can no longer tell you that Apple moved something. That is safe +here and only here — those four touch no private API, so they are not where an Xcode +major bites. For `FrameCapture.swift` and the other three upstream files the +detector still works, and losing it there would mean losing the only cheap warning +this program has. + +Upstream's license text, to compare with the copy in `LICENSES/`: + +```sh +curl -sL https://raw.githubusercontent.com/EvanBacon/serve-sim/6d009686ed08f7d8bd19bca9f9f67a9237931f44/LICENSE \ + | diff -u - LICENSES/serve-sim.APACHE-2.0.txt +``` + +Both fetches name the commit in the URL, so a stale upstream is impossible: the +URL either returns that commit's bytes or fails. + +## What is ours + +`main.swift`, `H264Encoder.swift`, `VideoEncoder.swift`, `PixelBufferUtils.swift`, +`Xcode.swift`, `scripts/unframe.mjs` and `Package.swift`, plus `NOTICE.md` and the +`LICENSES/` entry's file name. Everything else in `Sources/simlock-screen-capture/` +is serve-sim's, with the one patch above. + +Ours is under the project's MIT license, like the rest of this repository. That is +permitted rather than a relicensing: Apache-2.0 lets a licensee add its own terms to +its own modifications and to the derivative as a whole, provided the four §4 +conditions still travel — the license text in `LICENSES/`, the change notices in the +deviations above, and the attribution here. The Apache-2.0 text covers the serve-sim +code this directory carries and nothing else in it. Whether MIT and Apache-2.0 +should be stated as one combined license for this directory or kept as two explicit +layers is a legal judgement, and this notice deliberately does the second: it names +which file licenses which bytes, so nobody has to guess what shipping this directory +obliges us to. diff --git a/tools/screen-capture/Package.swift b/vendor/screen-capture/Package.swift similarity index 68% rename from tools/screen-capture/Package.swift rename to vendor/screen-capture/Package.swift index 5ca3d183..91b140de 100644 --- a/tools/screen-capture/Package.swift +++ b/vendor/screen-capture/Package.swift @@ -1,8 +1,11 @@ // swift-tools-version:6.0 // // simlock-screen-capture — a standalone Swift CLI that streams the framebuffer of a booted -// iOS Simulator to stdout. The capture and encode pipeline is serve-sim's (Apache-2.0), copied -// from Sources/SimNative and Sources/SimNativeSupport with two changes: +// iOS Simulator to stdout. +// +// Origin: see NOTICE.md for the per-file record, and LICENSES/ for the license text of the parts +// that are not ours. The private-API capture path is serve-sim's (Apache-2.0), copied from +// Sources/SimNative and Sources/SimNativeSupport with two changes: // // 1. `FrameCapture.findSimDevice` also searches the device set named by // SIMLOCK_IOS_DEVICE_SET. serve-sim searches only CoreSimulator's default set, and simlock @@ -13,6 +16,10 @@ // need it, and the daemon can then supervise the capture the same way it supervises every // other tool it runs. // +// The encoders are not serve-sim's: `H264Encoder.swift`, `VideoEncoder.swift`, +// `PixelBufferUtils.swift` and `Xcode.swift` were rewritten here against public VideoToolbox, +// CoreVideo, CoreMedia and ImageIO, and are under this project's MIT license. +// // One target, so the copied types keep their internal visibility and need no `public` edits. import PackageDescription diff --git a/tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift b/vendor/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift similarity index 100% rename from tools/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift rename to vendor/screen-capture/Sources/simlock-screen-capture/DeviceDisplaySizes.swift diff --git a/tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift b/vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift similarity index 97% rename from tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift rename to vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift index 123b1ef6..b85255b8 100644 --- a/tools/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift +++ b/vendor/screen-capture/Sources/simlock-screen-capture/FrameCapture.swift @@ -1,3 +1,9 @@ +// MODIFIED FILE — this file is copied from serve-sim (Copyright 2026 Evan Bacon, Apache-2.0; +// see NOTICE.md and LICENSES/ in this package) and it is NOT verbatim upstream. simlock's change +// is `deviceSetPath` and its use in `findSimDevice`, so that a simulator booted in a +// simlock-owned `simctl --set` root can be found at all; everything else here is upstream's. +// `NOTICE.md` prints the one command that diffs this file against pristine upstream at the commit +// we took it from. Read that notice before editing this file. import Foundation import CoreVideo import CoreMedia diff --git a/tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift b/vendor/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift similarity index 100% rename from tools/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift rename to vendor/screen-capture/Sources/simlock-screen-capture/FramebufferSurfaceSelector.swift diff --git a/vendor/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift b/vendor/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift new file mode 100644 index 00000000..3c9cec2b --- /dev/null +++ b/vendor/screen-capture/Sources/simlock-screen-capture/H264Encoder.swift @@ -0,0 +1,352 @@ +import CoreMedia +import CoreVideo +import Foundation +import VideoToolbox + +/// Turns captured frames into an H.264 bitstream, one frame per call. +/// +/// What comes back is AVCC: each NAL unit preceded by its four-byte length, which is +/// what a decoder consuming a container wants. Annex-B start codes — the `00 00 00 01` +/// spelling in a raw `.264` file — are never produced here. +/// +/// Everything is public VideoToolbox. Private API ends at the framebuffer; by the time a +/// frame reaches this file it is an ordinary `CVPixelBuffer`. +actor H264Encoder { + /// One encoded frame, ready to go on the wire. + struct Encoded { + let kind: Kind + /// Length-prefixed AVCC NAL bytes, with no Annex-B start codes anywhere in them. + let avcc: Data + /// An `avcC` record (ISO/IEC 14496-15 §5.2.4.1) carrying the SPS and PPS. + /// + /// Non-nil on exactly one frame per session — the first IDR — because that is the + /// only moment a viewer needs it. Nothing else carries the parameter sets, and a + /// decoder cannot start from an IDR without them, so a stream that never emits this + /// decodes to nothing. The frame it accompanies is the first one out of the session, + /// which is why the record always reaches a viewer ahead of the video it describes; + /// a rebuilt session emits a fresh pair, since new dimensions mean new sets. + let description: Data? + + enum Kind { case keyframe, delta } + } + + /// Submission is one-way and completion is a callback, so every encode waits on the + /// callback. `UnownedSerialExecutor` pins the actor to this queue, which is what makes + /// that wait cheap: the actor hops back onto the queue the callback already arrived on + /// instead of landing on a cooperative pool thread and then hopping again. + /// Unowned is sound because the queue is a stored property of the actor. + private let queue = DispatchSerialQueue(label: "h264-encoder", qos: .userInteractive) + nonisolated var unownedExecutor: UnownedSerialExecutor { queue.asUnownedSerialExecutor() } + + private var session: VTCompressionSession? + /// The size `session` was created for. + private var frameSize: Dimensions? + private let fps: Int32 + private var bitrate: Int + /// Whether the current session has already handed out its parameter sets. + private var descriptionSent = false + /// Encodes so far, never reset — see `encode`. + private var framesSent: Int64 = 0 + + init(fps: Int = 60, bitrate: Int = 6_000_000) { + self.fps = Int32(fps) + self.bitrate = bitrate + } + + deinit { + if let session { VTCompressionSessionInvalidate(session) } + } + + /// Encode one frame, or return nil if nothing came of it. + /// + /// A real-time session sheds load by discarding a frame rather than falling behind, + /// and that is a normal outcome of streaming: it comes back as nil, not as a thrown + /// error. Skipping a frame costs the stream one missing delta, which the next + /// P-frame covers; throwing would end the session over something the encoder does on + /// purpose. + /// + /// - Important: `source` must not be a buffer whose pixels can still change. + /// VideoToolbox keeps encoding after this call returns, and its contract is that the + /// client "should not modify the pixel data after making this call" — the session + /// retains the buffer rather than promising a copy of it, so the pixels have to stay + /// put. The capture path therefore deep-copies the live framebuffer before it gets + /// here. + func encode(_ source: CVPixelBuffer, forceKeyframe: Bool = false) async throws -> Encoded? { + // Retrying whenever there is no session is deliberate: if creating one fails, the + // next frame gets another attempt rather than a session that stays missing for the + // rest of the stream. + let size = source.dimensions + if session == nil || size != frameSize { + rebuildSession(for: size) + } + guard let session else { + throw Errors.couldNotCreateSession + } + + // A frame counter divided by the nominal frame rate, not the wall clock: VideoToolbox + // requires strictly increasing timestamps, and this satisfies that however erratically + // frames actually arrive. The counter is never reset — not even when a rotation + // rebuilds the session — so a size change can never move a timestamp backwards. + framesSent += 1 + let presentationTime = CMTime(value: framesSent, timescale: fps) + + let frameProperties: NSDictionary? = forceKeyframe + ? [kVTEncodeFrameOptionKey_ForceKeyFrame: kCFBooleanTrue!] + : nil + + let outcome: (status: OSStatus, flags: VTEncodeInfoFlags, sample: CMSampleBuffer?) = + await withCheckedContinuation { continuation in + let status = VTCompressionSessionEncodeFrame( + session, + imageBuffer: source, + presentationTimeStamp: presentationTime, + duration: .invalid, + frameProperties: frameProperties, + infoFlagsOut: nil + ) { @Sendable status, flags, sample in + continuation.resume(returning: (status, flags, sample)) + } + // A submission that fails outright never reaches the callback, so this is the + // only place that answer can be reported. + if status != noErr { + continuation.resume(returning: (status, [], nil)) + } + } + + if outcome.status == noErr, outcome.flags.contains(.frameDropped) { return nil } + guard outcome.status == noErr else { throw Errors.encodingFailed(outcome.status) } + guard let sample = outcome.sample else { throw Errors.missingSampleBuffer } + return try encoded(from: sample) + } + + /// Tear the session down early. Frames already submitted are not flushed — the last + /// couple of frames of a stream may never leave the encoder, which a live viewer + /// never notices. + func stop() { + if let session { + VTCompressionSessionInvalidate(session) + self.session = nil + } + } + + // MARK: - Session + + /// Replace the session with one sized for `size`, and allow the parameter sets to be + /// emitted again: a new size means new SPS and PPS, and a viewer that already took the + /// old ones needs the new pair. + private func rebuildSession(for size: Dimensions) { + invalidateSession() + guard let session = createSession(for: size) else { return } + VTCompressionSessionPrepareToEncodeFrames(session) + self.session = session + self.frameSize = size + self.descriptionSent = false + } + + private func invalidateSession() { + if let session { VTCompressionSessionInvalidate(session) } + self.session = nil + self.frameSize = nil + } + + /// A session configured for a live view: as little delay as the encoder will give up, + /// and nothing in the bitstream that makes a decoder sit on frames. + /// + /// Returns nil if the encoder could not be created, and reports failure the same way + /// for a session that came back without one, because VideoToolbox does both. A failed + /// rebuild leaves no session, so the next frame attempts it again. + private func createSession(for size: Dimensions) -> VTCompressionSession? { + let width = Int32(size.width) + let height = Int32(size.height) + // Asking for low-latency rate control is the part that actually buys the latency + // back. It selects the real-time encoder pipeline *and* writes a bitstream whose + // sequence parameters tell the decoder it may hold few reference pictures — + // `max_dec_frame_buffering`. That second half is the one that matters, and it is + // invisible here: without it a decoder fills a large decoded picture buffer before + // emitting anything, which measures out as roughly 300ms of delay on the viewer + // even though this encoder emits no B-frames at all. See + // VTCompressionProperties.h, "Ultra-low-latency conferencing and cloud gaming". + let lowLatency: CFDictionary = [ + kVTVideoEncoderSpecification_EnableLowLatencyRateControl: kCFBooleanTrue!, + ] as CFDictionary + + // The specification is a request, not a guarantee: an encoder that does not + // implement low-latency rate control fails the creation call. Falling back to a + // session with no specification keeps the stream working, merely further behind, so + // a machine that rejects it gets a stream rather than an error. + var candidate: VTCompressionSession? + var status = Self.create(width: width, height: height, specification: lowLatency, &candidate) + if status != noErr || candidate == nil { + candidate = nil + status = Self.create(width: width, height: height, specification: nil, &candidate) + } + guard status == noErr, let candidate else { return nil } + + let properties: [(key: CFString, value: Any)] = [ + // The encode is driven by frames arriving in real time, not by a file to chew + // through, so the encoder is told to spend for cadence rather than for quality. + (kVTCompressionPropertyKey_RealTime, kCFBooleanTrue!), + // High profile; the level is the encoder's call, since it is bounded by the + // resolution and rate we end up with and not by anything we know up front. + (kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_High_AutoLevel), + // No B-frames, so decode order and display order are the same order: a + // reordering decoder has to hold frames to put them back, and that is latency + // we are here to remove. + (kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse!), + (kVTCompressionPropertyKey_AverageBitRate, NSNumber(value: bitrate)), + (kVTCompressionPropertyKey_ExpectedFrameRate, NSNumber(value: fps)), + // An IDR is several times the size of a delta, so spacing them out keeps the + // average cost of a frame down where the motion is. A viewer arriving between + // two of them does not wait for the next one — the first frame of every session + // is forced to be an IDR. + (kVTCompressionPropertyKey_MaxKeyFrameInterval, NSNumber(value: Int(fps) * 5)), + ] + for property in properties { + VTSessionSetProperty(candidate, key: property.key, value: property.value as CFTypeRef) + } + return candidate + } + + private static func create( + width: Int32, + height: Int32, + specification: CFDictionary?, + _ sessionOut: inout VTCompressionSession? + ) -> OSStatus { + VTCompressionSessionCreate( + allocator: kCFAllocatorDefault, + width: width, + height: height, + codecType: kCMVideoCodecType_H264, + encoderSpecification: specification, + imageBufferAttributes: nil, + compressedDataAllocator: kCFAllocatorDefault, + outputCallback: nil, + refcon: nil, + compressionSessionOut: &sessionOut + ) + } + + // MARK: - Sample buffers + + /// The frame's bytes, and its parameter sets if this is the first IDR of the session. + private func encoded(from sample: CMSampleBuffer) throws -> Encoded { + let isKeyframe = !isNotSync(sample) + guard let block = CMSampleBufferGetDataBuffer(sample) else { + throw Errors.invalidSampleBuffer + } + var byteCount = 0 + var pointer: UnsafeMutablePointer? + guard CMBlockBufferGetDataPointer( + block, + atOffset: 0, + lengthAtOffsetOut: nil, + totalLengthOut: &byteCount, + dataPointerOut: &pointer + ) == noErr, let pointer + else { + throw Errors.invalidSampleBuffer + } + // VideoToolbox hands H.264 back in AVCC already — the block *is* a run of + // four-byte-length/NAL pairs, so the bytes go out as they stand. + let avcc = Data(bytes: pointer, count: byteCount) + + var description: Data? + if isKeyframe, let format = CMSampleBufferGetFormatDescription(sample) { + if let record = Self.avcC(from: format), !descriptionSent { + descriptionSent = true + description = record + } + } + return Encoded(kind: isKeyframe ? .keyframe : .delta, avcc: avcc, description: description) + } + + /// Whether a sample is marked as *not* a random-access point, which is how VideoToolbox + /// says "delta frame". + /// + /// It is the absence of the key that means sync — the encoder omits the attachment on a + /// keyframe rather than setting it false — hence the presence test rather than a value + /// comparison. + private func isNotSync(_ sample: CMSampleBuffer) -> Bool { + guard let attachments = CMSampleBufferGetSampleAttachmentsArray(sample, createIfNecessary: false), + CFArrayGetCount(attachments) > 0, + let entry = CFArrayGetValueAtIndex(attachments, 0) + else { return false } + // A CFArray hands back an untyped pointer; the attachment array's only documented + // contents are CFDictionary per sample, so this reads the one entry there is. + let attachment = unsafeBitCast(entry, to: CFDictionary.self) + return CFDictionaryContainsKey( + attachment, + Unmanaged.passUnretained(kCMSampleAttachmentKey_NotSync).toOpaque() + ) + } + + /// The AVC decoder configuration record for a format — the SPS and PPS in the one blob + /// a viewer needs before it can decode anything. + /// + /// The layout is fixed by ISO/IEC 14496-15 §5.2.4.1; the constants below are that + /// record's fields, in order. The first three bytes of the SPS after its header byte + /// *are* the profile, compatibility and level fields, which is why they are read out of + /// the SPS rather than asked of the encoder — one source of truth, no chance of the + /// record disagreeing with the stream. + private static func avcC(from format: CMFormatDescription) -> Data? { + // Only the SPS and PPS at indices 0 and 1 go into the record, so the count and the + // NAL header length are not asked for; the size is, and it has to be long enough to + // carry the profile/compat/level bytes read out of it below. + var spsPointer: UnsafePointer? + var spsSize = 0 + guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + format, + parameterSetIndex: 0, + parameterSetPointerOut: &spsPointer, + parameterSetSizeOut: &spsSize, + parameterSetCountOut: nil, + nalUnitHeaderLengthOut: nil + ) == noErr, let spsPointer, spsSize >= 4 else { return nil } + + var ppsPointer: UnsafePointer? + var ppsSize = 0 + guard CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + format, + parameterSetIndex: 1, + parameterSetPointerOut: &ppsPointer, + parameterSetSizeOut: &ppsSize, + parameterSetCountOut: nil, + nalUnitHeaderLengthOut: nil + ) == noErr, let ppsPointer else { return nil } + + let sps = UnsafeBufferPointer(start: spsPointer, count: spsSize) + let pps = UnsafeBufferPointer(start: ppsPointer, count: ppsSize) + + var record = Data() + record.append(0x01) // configurationVersion + record.append(sps[1]) // profileIdc + record.append(sps[2]) // profileCompatibility + record.append(sps[3]) // levelIdc + record.append(0xFF) // 0b111111: reserved, plus lengthSizeMinusOne == 3 — the + // four-byte NAL lengths this encoder's output is prefixed with + record.append(0xE1) // 0b11100001: reserved, plus one SPS + record.append(uint16BigEndian: spsSize) + record.append(contentsOf: sps) + record.append(0x01) // exactly one PPS, which is what we are holding + record.append(uint16BigEndian: ppsSize) + record.append(contentsOf: pps) + return record + } + + enum Errors: Error { + case couldNotCreateSession + case encodingFailed(OSStatus) + case missingSampleBuffer + case invalidSampleBuffer + } +} + +private extension Data { + /// Appends `value` as two big-endian bytes — the length field's width in an `avcC` + /// record's parameter-set entries. + mutating func append(uint16BigEndian value: Int) { + append(UInt8((value >> 8) & 0xFF)) + append(UInt8(value & 0xFF)) + } +} diff --git a/vendor/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift b/vendor/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift new file mode 100644 index 00000000..21b943ad --- /dev/null +++ b/vendor/screen-capture/Sources/simlock-screen-capture/PixelBufferUtils.swift @@ -0,0 +1,98 @@ +import CoreVideo + +/// The size of a frame, in pixels. +struct Dimensions: Hashable, Sendable { + var width: Int + var height: Int +} + +extension CVPixelBuffer { + /// The buffer's pixel size. + var dimensions: Dimensions { + Dimensions( + width: CVPixelBufferGetWidth(self), + height: CVPixelBufferGetHeight(self) + ) + } +} + +/// Copies each captured frame into a buffer of our own, so that it can outlive the +/// frame callback that produced it. +/// +/// What the capture path hands over is a `CVPixelBuffer` wrapped around SimulatorKit's +/// live framebuffer `IOSurface`, and the next frame the simulator draws lands in those +/// same pages, in place. So the pixels behind that buffer are only stable until the +/// simulator renders again — not long enough for a frame that is encoded after the +/// callback returns. Retaining the buffer would not help either: retention keeps the +/// object alive, not its contents. Only a copy does, which is why this exists at all — +/// it is a correctness requirement, not an optimisation. Skipping it shows up as torn +/// frames, not as an error. +/// +/// The copies come out of a `CVPixelBufferPool`, so a long session recycles the same +/// few allocations instead of creating one per frame. That is the only reason this +/// holds state; the pool is rebuilt whenever the frame size changes, which happens on +/// a rotation. +struct Photocopier { + private var pool: CVPixelBufferPool? + /// The frame size `pool` was built for, so a size change is noticed rather than + /// silently copied into a buffer of the wrong dimensions. + private var poolSize: Dimensions? + + init() {} + + /// The pool that fits `size`, rebuilding it when the one on hand does not. + private mutating func pool(for size: Dimensions) -> CVPixelBufferPool? { + if let pool, poolSize == size { + return pool + } + + // IOSurface-backed deliberately: what consumes these buffers downstream is a + // VideoToolbox compression session and a GPU pixel-transfer session, and both + // want a surface the hardware can map rather than a plain allocated block. + let format: [String: Any] = [ + kCVPixelBufferWidthKey as String: size.width, + kCVPixelBufferHeightKey as String: size.height, + kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA, + kCVPixelBufferIOSurfacePropertiesKey as String: [:], + ] + var rebuilt: CVPixelBufferPool? + CVPixelBufferPoolCreate(kCFAllocatorDefault, nil, format as CFDictionary, &rebuilt) + pool = rebuilt + poolSize = size + return rebuilt + } + + /// A buffer of our own holding the same pixels as `source`, or nil when no copy + /// could be made. + mutating func copy(_ source: CVPixelBuffer) -> CVPixelBuffer? { + guard let pool = pool(for: source.dimensions) else { return nil } + var fresh: CVPixelBuffer? + guard CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &fresh) == kCVReturnSuccess, + let target = fresh + else { return nil } + + // Locking is what hands out a base address at all — an IOSurface is not + // guaranteed to be CPU-mapped until someone asks to read it. + CVPixelBufferLockBaseAddress(source, .readOnly) + CVPixelBufferLockBaseAddress(target, []) + defer { + CVPixelBufferUnlockBaseAddress(source, .readOnly) + CVPixelBufferUnlockBaseAddress(target, []) + } + guard let from = CVPixelBufferGetBaseAddress(source), + let into = CVPixelBufferGetBaseAddress(target) + else { return nil } + + // Row by row, because a row of the copy and a row of the framebuffer are only + // the same length by accident: each allocator pads the end of a row to its own + // alignment, so the strides differ and the buffers cannot be copied as one + // block. The shorter stride is the one that is pixels in both. + let sourceStride = CVPixelBufferGetBytesPerRow(source) + let targetStride = CVPixelBufferGetBytesPerRow(target) + let rowBytes = min(sourceStride, targetStride) + for row in 0.. Data { + let image = try cgImage(from: pixelBuffer) + + let jpeg = NSMutableData() + guard let destination = CGImageDestinationCreateWithData( + jpeg as CFMutableData, + UTType.jpeg.identifier as CFString, + 1, + nil + ) else { + throw Errors.encodingFailed + } + CGImageDestinationAddImage( + destination, + image, + [kCGImageDestinationLossyCompressionQuality: quality] as CFDictionary + ) + guard CGImageDestinationFinalize(destination) else { + throw Errors.encodingFailed + } + return jpeg as Data + } + + /// A `CGImage` over the frame's pixels. + /// + /// Nothing is copied here: the bitmap context is handed the buffer's own base address, + /// and `makeImage()` produces an image that reads through it. Hence the lock, held for + /// the call — a buffer's base address is only meaningful while it is locked, and the + /// image has to have been created before the lock goes back. + private func cgImage(from pixelBuffer: CVPixelBuffer) throws -> CGImage { + CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) + defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } + + guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { + throw Errors.invalidPixelBuffer + } + guard let context = CGContext( + data: baseAddress, + width: CVPixelBufferGetWidth(pixelBuffer), + height: CVPixelBufferGetHeight(pixelBuffer), + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), + space: CGColorSpaceCreateDeviceRGB(), + // BGRA in memory, read as a pre-multiplied 32-bit little-endian pixel: what + // the capture path asks the framebuffer for (kCVPixelFormatType_32BGRA). The + // two flags are the same choice spelled in CoreGraphics' terms, and the + // pairing is the one CoreGraphics rejects any other combination of. + bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue + | CGImageAlphaInfo.premultipliedFirst.rawValue + ), let image = context.makeImage() else { + throw Errors.encodingFailed + } + return image + } + + enum Errors: Error { + case invalidPixelBuffer + case encodingFailed + } +} diff --git a/vendor/screen-capture/Sources/simlock-screen-capture/Xcode.swift b/vendor/screen-capture/Sources/simlock-screen-capture/Xcode.swift new file mode 100644 index 00000000..49801c59 --- /dev/null +++ b/vendor/screen-capture/Sources/simlock-screen-capture/Xcode.swift @@ -0,0 +1,42 @@ +import Foundation + +/// The active developer directory — where this tool looks for the simulator's +/// frameworks, and the one thing about the host machine it has to ask rather than know. +enum Xcode { + /// The directory `xcode-select` points at (`xcode-select -p`), e.g. + /// `/Applications/Xcode.app/Contents/Developer`. + /// + /// Asking the tool is the only honest way to read this: Apple ships several switchable + /// Xcodes and exposes no API for the active one, so any path written into this binary + /// is a guess about somebody's machine. When the question cannot be asked at all — + /// `xcode-select` is not where it is expected to be — the answer falls back to the + /// standard install location, which is the more likely answer than no answer. + static func developerDir() -> String { + guard let output = run("/usr/bin/xcode-select", ["-p"]), + let path = String(data: output, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + else { + return "/Applications/Xcode.app/Contents/Developer" + } + return path + } + + /// The standard output of a short-lived program, or nil if it could not be started. + /// + /// Reading the pipe is only reached once `run()` has succeeded: asking a `Process` that + /// never started to wait for its exit traps rather than throwing, so the two cannot be + /// ordered the other way round. + private static func run(_ launchPath: String, _ arguments: [String]) -> Data? { + let output = Pipe() + let program = Process() + program.executableURL = URL(fileURLWithPath: launchPath) + program.arguments = arguments + program.standardOutput = output + do { + try program.run() + } catch { + return nil + } + program.waitUntilExit() + return output.fileHandleForReading.readDataToEndOfFile() + } +} diff --git a/tools/screen-capture/Sources/simlock-screen-capture/main.swift b/vendor/screen-capture/Sources/simlock-screen-capture/main.swift similarity index 100% rename from tools/screen-capture/Sources/simlock-screen-capture/main.swift rename to vendor/screen-capture/Sources/simlock-screen-capture/main.swift diff --git a/tools/screen-capture/scripts/unframe.mjs b/vendor/screen-capture/scripts/unframe.mjs similarity index 100% rename from tools/screen-capture/scripts/unframe.mjs rename to vendor/screen-capture/scripts/unframe.mjs From bfc2e144fc3e5dca790e77a5380d0c0fcef87598 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 15 Oct 2026 11:59:25 +0200 Subject: [PATCH 04/29] feat(core): a driver may offer its devices' screens, in one shape A ScreenCapture on the Driver interface opens a device's screen and hands back the capture envelope as bytes plus a bounded close; DriverCatalog routes it by platform and refuses a platform whose driver offers none, so the stream module never names a platform. The envelope's tag table and encoder live in core, once, for every producer and reader. Two ports for what a capture needs that ProcessRunner and TcpProbe cannot give: a child with binary stdout and stdin as its liveness handle (BytePipeLauncher), and a long-lived loopback connection (TcpStreamConnector). --- src/core/AGENTS.md | 1 + src/core/driver-catalog.ts | 36 +++++++++ src/core/driver.ts | 4 + src/core/fake-driver.ts | 4 + src/core/index.ts | 13 +++ src/core/screen.test.ts | 80 +++++++++++++++++++ src/core/screen.ts | 74 ++++++++++++++++++ src/ports/AGENTS.md | 2 + src/ports/byte-pipe.test.ts | 64 +++++++++++++++ src/ports/byte-pipe.ts | 148 +++++++++++++++++++++++++++++++++++ src/ports/index.ts | 16 ++++ src/ports/tcp-stream.test.ts | 54 +++++++++++++ src/ports/tcp-stream.ts | 118 ++++++++++++++++++++++++++++ 13 files changed, 614 insertions(+) create mode 100644 src/core/screen.test.ts create mode 100644 src/core/screen.ts create mode 100644 src/ports/byte-pipe.test.ts create mode 100644 src/ports/byte-pipe.ts create mode 100644 src/ports/tcp-stream.test.ts create mode 100644 src/ports/tcp-stream.ts diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index 60a4400a..10ddcef1 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -32,6 +32,7 @@ See also: [leasing](../leasing/AGENTS.md) (lease flow, when reclaim runs), [warm | `startup-read.ts`, `startup-converger.ts` | `readStartup`; `StartupConverger`, the device half of startup | | `doctor.ts`, `host-facts.ts` | `Doctor` findings and fixes; `HostFactsReader` for status | | `driver.ts`, `driver-catalog.ts` | `Driver` interface, errors, `DiskSpaceGuard`; `DriverCatalog` | +| `screen.ts` | `ScreenCapture` (a driver's optional screen), its frame tags and encoder | | `catalog-match.ts` | Model name, class and paired runtimes against a catalog entry | | `component-installer.ts` | `ComponentInstaller`: install, list, remove | | `physical-enrollment.ts`, `physical-presence.ts` | `device add`/`remove`; every presence read, `latestPresence` | diff --git a/src/core/driver-catalog.ts b/src/core/driver-catalog.ts index 4129dc2d..0cc7df31 100644 --- a/src/core/driver-catalog.ts +++ b/src/core/driver-catalog.ts @@ -10,6 +10,7 @@ import type { PhysicalDriverDevice, VirtualDriverDevice, } from "./driver.js"; +import { type ScreenFeed, ScreenNotSupportedError, type ScreenSettings } from "./screen.js"; import { stableError } from "./stable-error.js"; /** Thrown when no installed driver can serve the requested platform. */ @@ -43,6 +44,11 @@ export interface PlatformCatalog extends DriverCatalogEntry { readonly classDefaults: Readonly>>; } +export interface ScreenAvailability { + readonly platform: Platform; + readonly reason?: string; +} + /** Immutable platform-to-driver lookup used by the lease path. */ export class DriverCatalog { readonly #drivers: ReadonlyMap; @@ -87,6 +93,36 @@ export class DriverCatalog { throw new UnknownPassthroughToolError(tool); } + async openScreen( + platform: Platform, + device: VirtualDriverDevice | PhysicalDriverDevice, + settings: ScreenSettings, + ): Promise { + const capture = this.#drivers.get(platform)?.screenCapture; + if (capture === undefined) { + throw new ScreenNotSupportedError(`A ${platform} device's screen cannot be streamed`); + } + return await capture.open(device, settings); + } + + async screenAvailability(): Promise { + return await Promise.all( + [...this.#drivers.values()].flatMap((driver) => + driver.screenCapture === undefined + ? [] + : [ + driver.screenCapture + .unavailable() + .then((reason) => + reason === undefined + ? { platform: driver.platform } + : { platform: driver.platform, reason }, + ), + ], + ), + ); + } + async resolveSpec(request: ExactDeviceRequest): Promise { return this.get(request.platform).resolveSpec(request); } diff --git a/src/core/driver.ts b/src/core/driver.ts index 035887da..82a622a9 100644 --- a/src/core/driver.ts +++ b/src/core/driver.ts @@ -1,6 +1,7 @@ import type { EventMap } from "../bus/index.js"; import type { Filesystem } from "../ports/index.js"; import type { RootRejectionReason } from "./device-root.js"; +import type { ScreenCapture } from "./screen.js"; import type { AbsentReason, DeviceClass, @@ -354,6 +355,9 @@ export interface Driver { dispose?(): Promise; /** Spelled `| undefined` so a driver that decides at construction can still say it has none. */ readonly passthroughTools?: readonly string[] | undefined; + // why: optional because a platform whose screen cannot be taken leaves it out, and the stream + // module learns that from its absence rather than from a platform name. + readonly screenCapture?: ScreenCapture | undefined; /** why: `device.exec` has no pseudo-terminal (ADR 0005 §19c), so a driver may refuse there what it allows locally. */ passthrough?( tool: string, diff --git a/src/core/fake-driver.ts b/src/core/fake-driver.ts index dcb9d55d..d291927a 100644 --- a/src/core/fake-driver.ts +++ b/src/core/fake-driver.ts @@ -38,6 +38,7 @@ import { UnknownModelError, uninstallPlan, } from "./driver.js"; +import type { ScreenCapture } from "./screen.js"; export type FakeDriverOperation = | "resolveSpec" @@ -126,6 +127,7 @@ export interface FakeDriverOptions { readonly leaseEnvironment?: Readonly>; /** why: absent by default, so two fakes in one catalog do not both answer to the same tool. */ readonly passthroughTools?: readonly string[]; + readonly screenCapture?: ScreenCapture; readonly passthrough?: ( tool: string, device: VirtualDriverDevice | PhysicalDriverDevice | undefined, @@ -188,6 +190,7 @@ export class FakeDriver implements Driver { readonly #latencyMs: FakeDriverOptions["latencyMs"]; readonly #leaseEnvironment: Readonly>; readonly passthroughTools: readonly string[] | undefined; + readonly screenCapture: ScreenCapture | undefined; readonly #passthrough: FakeDriverOptions["passthrough"]; #nextDeviceNumber = 1; readonly #pendingMakeReady: (() => void)[] = []; @@ -225,6 +228,7 @@ export class FakeDriver implements Driver { this.#leaseEnvironment = options.leaseEnvironment ?? {}; this.#legacyDevices = new Map(Object.entries(options.legacyDevices ?? {})); this.passthroughTools = options.passthroughTools; + this.screenCapture = options.screenCapture; this.#passthrough = options.passthrough; this.platform = options.platform; this.deviceRoot = options.deviceRoot ?? `/fake/${options.platform}`; diff --git a/src/core/index.ts b/src/core/index.ts index 0f1f9e16..2c30eec5 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -88,8 +88,21 @@ export { DriverCatalog, NoDriverError, type ModelPreferences, + type ScreenAvailability, UnknownPassthroughToolError, } from "./driver-catalog.js"; +export { + encodeScreenFrame, + encodeScreenMeta, + type ScreenCapture, + type ScreenFeed, + SCREEN_FRAME_HEADER_BYTES, + SCREEN_FRAME_TAG, + type ScreenMeta, + ScreenNotSupportedError, + type ScreenSettings, + ScreenUnavailableError, +} from "./screen.js"; // fallow-ignore-next-line unused-type -- wire-visible rejection vocabulary for Simlock's own adb server. export type { AdbServerRejectionReason, DriverRejectionReason } from "./driver.js"; export { diff --git a/src/core/screen.test.ts b/src/core/screen.test.ts new file mode 100644 index 00000000..5ce7dd92 --- /dev/null +++ b/src/core/screen.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; + +import { FakeClock } from "../ports/index.js"; +import { DriverCatalog } from "./driver-catalog.js"; +import { FakeDriver } from "./fake-driver.js"; +import { + encodeScreenFrame, + encodeScreenMeta, + type ScreenCapture, + ScreenNotSupportedError, + SCREEN_FRAME_TAG, +} from "./screen.js"; + +const settings = { bitrateKbps: 1500, fps: 30, maxDimension: 720 }; +const device = { address: "a", deviceId: "d", driverData: {}, physical: false } as const; + +describe("encodeScreenFrame", () => { + it("prefixes the payload with a big-endian length that counts the tag byte", () => { + const frame = encodeScreenFrame(SCREEN_FRAME_TAG.keyframe, new Uint8Array([9, 8, 7])); + expect([...frame]).toEqual([0, 0, 0, 4, SCREEN_FRAME_TAG.keyframe, 9, 8, 7]); + }); + + it("writes a meta frame whose payload is the meta as JSON", () => { + const frame = encodeScreenMeta({ codec: "h264", fps: 30, height: 720, width: 330 }); + expect(frame[4]).toBe(SCREEN_FRAME_TAG.meta); + expect(JSON.parse(new TextDecoder().decode(frame.subarray(5)))).toEqual({ + codec: "h264", + fps: 30, + height: 720, + width: 330, + }); + }); +}); + +describe("DriverCatalog screens", () => { + it("opens a screen through the driver of the device's platform", async () => { + const opened: string[] = []; + const capture: ScreenCapture = { + open: async (target) => { + opened.push(target.deviceId); + return { body: new ReadableStream(), close: async () => undefined }; + }, + unavailable: async () => undefined, + }; + const catalog = new DriverCatalog([ + new FakeDriver({ clock: new FakeClock(), platform: "ios", screenCapture: capture }), + new FakeDriver({ clock: new FakeClock(), platform: "android" }), + ]); + await catalog.openScreen("ios", device, settings); + expect(opened).toEqual(["d"]); + }); + + it("refuses a platform whose driver offers no screen as not supported", async () => { + const catalog = new DriverCatalog([ + new FakeDriver({ clock: new FakeClock(), platform: "android" }), + ]); + await expect(catalog.openScreen("android", device, settings)).rejects.toBeInstanceOf( + ScreenNotSupportedError, + ); + }); + + it("reports availability only for drivers that offer a screen, with the reason one cannot", async () => { + const catalog = new DriverCatalog([ + new FakeDriver({ + clock: new FakeClock(), + platform: "ios", + screenCapture: { + open: async () => { + throw new Error("unused"); + }, + unavailable: async () => "No capture program at /x", + }, + }), + new FakeDriver({ clock: new FakeClock(), platform: "android" }), + ]); + expect(await catalog.screenAvailability()).toEqual([ + { platform: "ios", reason: "No capture program at /x" }, + ]); + }); +}); diff --git a/src/core/screen.ts b/src/core/screen.ts new file mode 100644 index 00000000..335af749 --- /dev/null +++ b/src/core/screen.ts @@ -0,0 +1,74 @@ +import type { PhysicalDriverDevice, VirtualDriverDevice } from "./driver.js"; + +// why: one table for every producer and every reader of a capture's bytes. The iOS capture program +// writes it from Swift, the Android driver writes it from TypeScript, and the stream module reads +// it; two copies of the tags would drift silently and a viewer would decode garbage. +export const SCREEN_FRAME_TAG = { + description: 0x01, + keyframe: 0x02, + delta: 0x03, + seed: 0x04, + meta: 0x05, +} as const; + +export const SCREEN_FRAME_HEADER_BYTES = 5; + +export interface ScreenMeta { + readonly codec: "h264" | "mjpeg"; + readonly width: number; + readonly height: number; + readonly fps: number; +} + +export interface ScreenSettings { + readonly fps: number; + readonly maxDimension: number; + readonly bitrateKbps: number; +} + +// why: the one shape every platform's capture is handed out in, so the module that serves a stream +// never learns how a picture was taken. `body` carries frames in the table above, meta first; the +// stream module, not the driver, decides how long it may stay quiet. +export interface ScreenFeed { + readonly body: ReadableStream; + // why: resolves once the device holds nothing of this capture any more -- a server process, a + // forward, a pushed file -- so a lease's reclaim can wait for it rather than race it. Bounded by + // the driver and idempotent: a viewer leaving, the lease ending and the body ending all call it. + close(): Promise; +} + +export interface ScreenCapture { + // why: a reason when this driver cannot capture right now (its program is missing), so a + // status read can say why without opening a stream to find out. + unavailable(): Promise; + open( + device: VirtualDriverDevice | PhysicalDriverDevice, + settings: ScreenSettings, + ): Promise; +} + +export class ScreenNotSupportedError extends Error { + constructor(message: string) { + super(message); + this.name = "ScreenNotSupportedError"; + } +} + +export class ScreenUnavailableError extends Error { + constructor(message: string) { + super(message); + this.name = "ScreenUnavailableError"; + } +} + +export function encodeScreenFrame(tag: number, payload: Uint8Array): Uint8Array { + const frame = new Uint8Array(SCREEN_FRAME_HEADER_BYTES + payload.length); + new DataView(frame.buffer).setUint32(0, payload.length + 1); + frame[4] = tag; + frame.set(payload, SCREEN_FRAME_HEADER_BYTES); + return frame; +} + +export function encodeScreenMeta(meta: ScreenMeta): Uint8Array { + return encodeScreenFrame(SCREEN_FRAME_TAG.meta, new TextEncoder().encode(JSON.stringify(meta))); +} diff --git a/src/ports/AGENTS.md b/src/ports/AGENTS.md index 1f66d2d0..afab7c3c 100644 --- a/src/ports/AGENTS.md +++ b/src/ports/AGENTS.md @@ -23,6 +23,8 @@ Uses: nothing from other components. Imported by: every component that touches t | `HostInfo` | `host-info.ts` | `NodeHostInfo` | `FakeHostInfo` | OS, version (`sw_vers` on macOS), arch | | `HostLoadProbe` | `host-load.ts` | `DarwinHostLoadProbe`, `LinuxHostLoadProbe` | `FakeHostLoadProbe` | CPU, memory, disk counters | | `TcpProbe` | `tcp-probe.ts` | `NodeTcpProbe` | `FakeTcpProbe` | Is a loopback port listening; send bytes | +| `TcpStreamConnector` | `tcp-stream.ts` | `NodeTcpStreamConnector` | `FakeTcpStreamConnector` | A long-lived loopback connection, bytes both ways | +| `BytePipeLauncher` | `byte-pipe.ts` | `NodeBytePipeLauncher` | `FakeBytePipeLauncher` | A child with binary stdout and stdin as its liveness | | `IdGenerator` | `id-generator.ts` | `CryptoIdGenerator` | none; tests inline one | Random ids | | `TokenSecrets` | `token-secrets.ts` | `CryptoTokenSecrets` | none; tests inline one | New secret, its hash | | Paths | `paths.ts` | `resolveSimlockHome`, `resolveDaemonSocketPath` | — | `SIMLOCK_HOME` (default `~/.simlock`), socket path length | diff --git a/src/ports/byte-pipe.test.ts b/src/ports/byte-pipe.test.ts new file mode 100644 index 00000000..382109fb --- /dev/null +++ b/src/ports/byte-pipe.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from "vitest"; + +import { NodeBytePipeLauncher } from "./byte-pipe.js"; + +async function readAll(stream: ReadableStream): Promise { + const chunks: Uint8Array[] = []; + for await (const chunk of stream) chunks.push(chunk); + return Buffer.concat(chunks); +} + +describe("NodeBytePipeLauncher", () => { + it("hands stdout out as the exact bytes the child wrote, including ones that are not text", async () => { + const child = new NodeBytePipeLauncher().start( + process.execPath, + ["-e", "process.stdout.write(Buffer.from([0, 255, 10, 128, 1]))"], + { env: process.env }, + ); + expect([...(await readAll(child.stdout))]).toEqual([0, 255, 10, 128, 1]); + expect(await child.exited).toEqual({ code: 0, signal: null }); + }); + + it("closing stdin ends a child that waits on it", async () => { + const child = new NodeBytePipeLauncher().start( + process.execPath, + ["-e", "process.stdin.resume(); process.stdin.on('end', () => process.exit(7))"], + { env: process.env }, + ); + child.closeStdin(); + expect(await child.exited).toEqual({ code: 7, signal: null }); + }); + + it("reports each stderr line once it is complete", async () => { + const lines: string[] = []; + const child = new NodeBytePipeLauncher().start( + process.execPath, + [ + "-e", + "process.stderr.write('one\\ntw'); setTimeout(() => process.stderr.write('o\\n'), 20)", + ], + { env: process.env, onStderrLine: (line) => lines.push(line) }, + ); + await child.exited; + expect(lines).toEqual(["one", "two"]); + }); + + it("resolves a program that cannot be started with the spawn error instead of rejecting", async () => { + const child = new NodeBytePipeLauncher().start("/nonexistent/simlock-capture", [], { + env: process.env, + }); + const exit = await child.exited; + expect(exit.code).toBeNull(); + expect(exit.error).toMatch(/ENOENT/); + }); + + it("kill ends a child that ignores stdin", async () => { + const child = new NodeBytePipeLauncher().start( + process.execPath, + ["-e", "setInterval(() => undefined, 1000)"], + { env: process.env }, + ); + child.kill("SIGKILL"); + expect(await child.exited).toEqual({ code: null, signal: "SIGKILL" }); + }); +}); diff --git a/src/ports/byte-pipe.ts b/src/ports/byte-pipe.ts new file mode 100644 index 00000000..4fbdd44d --- /dev/null +++ b/src/ports/byte-pipe.ts @@ -0,0 +1,148 @@ +import { spawn } from "node:child_process"; +import { Readable } from "node:stream"; + +// why: `ProcessRunner` hands output out as text, which a video stream cannot survive, and closes or +// never offers the stdin a capture program treats as its liveness handle. +export interface BytePipeLauncher { + start(command: string, args: readonly string[], options: BytePipeOptions): BytePipeProcess; +} + +export interface BytePipeOptions { + readonly env: NodeJS.ProcessEnv; + readonly onStderrLine?: (line: string) => void; +} + +export interface BytePipeProcess { + readonly stdout: ReadableStream; + // why: never rejects -- a program the OS refused to start resolves with `error` set, so a caller + // has one place to learn how it ended. + readonly exited: Promise; + closeStdin(): void; + kill(signal: "SIGTERM" | "SIGKILL"): void; +} + +export interface BytePipeExit { + readonly code: number | null; + readonly signal: string | null; + readonly error?: string; +} + +export class NodeBytePipeLauncher implements BytePipeLauncher { + start(command: string, args: readonly string[], options: BytePipeOptions): BytePipeProcess { + const child = spawn(command, [...args], { env: options.env, stdio: ["pipe", "pipe", "pipe"] }); + // why: a child that exits while stdin is being closed raises EPIPE on the pipe, which is the + // stop having worked, not a failure anyone can act on. + child.stdin.on("error", () => undefined); + + let partial = ""; + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (chunk: string) => { + const lines = (partial + chunk).split("\n"); + partial = lines.pop() ?? ""; + for (const line of lines) if (line.trim() !== "") options.onStderrLine?.(line); + }); + + const exited = new Promise((resolve) => { + child.once("error", (error) => resolve({ code: null, error: error.message, signal: null })); + child.once("close", (code, signal) => resolve({ code, signal })); + }); + + return { + closeStdin: () => { + child.stdin.end(); + }, + exited, + kill: (signal) => { + if (child.exitCode === null && child.signalCode === null) child.kill(signal); + }, + stdout: Readable.toWeb(child.stdout) as ReadableStream, + }; + } +} + +export interface FakeBytePipeStart { + readonly command: string; + readonly args: readonly string[]; + readonly env: NodeJS.ProcessEnv; +} + +export class FakeBytePipeProcess implements BytePipeProcess { + readonly stdout: ReadableStream; + readonly exited: Promise; + stdinClosed = false; + readonly kills: string[] = []; + exitOnStdinClose = true; + readonly #onStderrLine: ((line: string) => void) | undefined; + #controller!: ReadableStreamDefaultController; + #resolveExit!: (exit: BytePipeExit) => void; + #ended = false; + + constructor(onStderrLine?: (line: string) => void) { + this.#onStderrLine = onStderrLine; + this.stdout = new ReadableStream({ + start: (controller) => { + this.#controller = controller; + }, + }); + this.exited = new Promise((resolve) => { + this.#resolveExit = resolve; + }); + } + + write(bytes: Uint8Array): void { + if (!this.#ended) this.#controller.enqueue(bytes); + } + + stderr(line: string): void { + this.#onStderrLine?.(line); + } + + exit(exit: BytePipeExit = { code: 0, signal: null }): void { + if (this.#ended) return; + this.#ended = true; + try { + this.#controller.close(); + } catch { + // why: a reader that already cancelled the stream has closed it; the exit still settles. + } + this.#resolveExit(exit); + } + + closeStdin(): void { + this.stdinClosed = true; + if (this.exitOnStdinClose) this.exit(); + } + + kill(signal: "SIGTERM" | "SIGKILL"): void { + this.kills.push(signal); + this.exit({ code: null, signal }); + } +} + +export class FakeBytePipeLauncher implements BytePipeLauncher { + readonly starts: FakeBytePipeStart[] = []; + readonly processes: FakeBytePipeProcess[] = []; + #failure: string | undefined; + + failNextStart(error: string): void { + this.#failure = error; + } + + start(command: string, args: readonly string[], options: BytePipeOptions): FakeBytePipeProcess { + this.starts.push({ args: [...args], command, env: options.env }); + const process = new FakeBytePipeProcess(options.onStderrLine); + this.processes.push(process); + if (this.#failure !== undefined) { + const error = this.#failure; + this.#failure = undefined; + process.exit({ code: null, error, signal: null }); + } + return process; + } + + get last(): FakeBytePipeProcess { + const process = this.processes.at(-1); + if (process === undefined) throw new Error("No byte-pipe process was started"); + return process; + } +} diff --git a/src/ports/index.ts b/src/ports/index.ts index 9b5d926a..b5d56c2b 100644 --- a/src/ports/index.ts +++ b/src/ports/index.ts @@ -66,6 +66,22 @@ export type { ProcessSupervisor } from "./process-supervisor.js"; // fallow-ignore-next-line unused-type -- public shape of FakeProcessSupervisor.signals. export type { SignalRecord } from "./process-supervisor.js"; export { FakeTcpProbe, NodeTcpProbe } from "./tcp-probe.js"; +export { + FakeTcpPeer, + FakeTcpStreamConnector, + NodeTcpStreamConnector, + type TcpStream, + type TcpStreamConnector, +} from "./tcp-stream.js"; +export { + type BytePipeExit, + type BytePipeLauncher, + type BytePipeOptions, + type BytePipeProcess, + FakeBytePipeLauncher, + FakeBytePipeProcess, + NodeBytePipeLauncher, +} from "./byte-pipe.js"; // fallow-ignore-next-line unused-type -- public shape of FakeTcpProbe.sends. export type { FakeSend } from "./tcp-probe.js"; export type { TcpProbe } from "./tcp-probe.js"; diff --git a/src/ports/tcp-stream.test.ts b/src/ports/tcp-stream.test.ts new file mode 100644 index 00000000..ec00af0a --- /dev/null +++ b/src/ports/tcp-stream.test.ts @@ -0,0 +1,54 @@ +import { createServer, type Server, type Socket } from "node:net"; + +import { afterEach, describe, expect, it } from "vitest"; + +import { NodeTcpStreamConnector } from "./tcp-stream.js"; + +let server: Server | undefined; + +afterEach(async () => { + await new Promise((resolve) => (server ? server.close(() => resolve()) : resolve())); + server = undefined; +}); + +async function listen(onConnection: (socket: Socket) => void): Promise { + server = createServer(onConnection); + await new Promise((resolve) => server?.listen(0, "127.0.0.1", resolve)); + const address = server.address(); + if (address === null || typeof address === "string") throw new Error("no port"); + return address.port; +} + +describe("NodeTcpStreamConnector", () => { + it("carries bytes both ways until the peer hangs up", async () => { + const port = await listen((socket) => { + socket.once("data", (request: Buffer) => { + socket.end(Buffer.concat([Buffer.from("echo:"), request])); + }); + }); + const stream = await new NodeTcpStreamConnector().connect(port, 1000); + await stream.write("ping"); + const chunks: Uint8Array[] = []; + for await (const chunk of stream.readable) chunks.push(chunk); + expect(Buffer.concat(chunks).toString()).toBe("echo:ping"); + }); + + it("rejects a port nothing listens on", async () => { + const port = await listen(() => undefined); + await new Promise((resolve) => server?.close(() => resolve())); + server = undefined; + await expect(new NodeTcpStreamConnector().connect(port, 1000)).rejects.toThrow(/ECONNREFUSED/); + }); + + it("close ends the connection the peer sees", async () => { + const peerClosed = new Promise((resolve) => { + void listen((socket) => { + socket.on("close", () => resolve()); + }).then(async (port) => { + const stream = await new NodeTcpStreamConnector().connect(port, 1000); + stream.close(); + }); + }); + await expect(peerClosed).resolves.toBeUndefined(); + }); +}); diff --git a/src/ports/tcp-stream.ts b/src/ports/tcp-stream.ts new file mode 100644 index 00000000..3ec348da --- /dev/null +++ b/src/ports/tcp-stream.ts @@ -0,0 +1,118 @@ +import { createConnection, type Socket } from "node:net"; +import { Readable } from "node:stream"; + +// why: `TcpProbe` answers one question and hangs up; a device's video socket reached through adb +// keeps talking for as long as someone watches. +export interface TcpStreamConnector { + connect(port: number, timeoutMs: number): Promise; +} + +export interface TcpStream { + readonly readable: ReadableStream; + write(bytes: Uint8Array | string): Promise; + close(): void; +} + +export class NodeTcpStreamConnector implements TcpStreamConnector { + connect(port: number, timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + let socket: Socket; + try { + socket = createConnection({ host: "127.0.0.1", port }); + } catch (error: unknown) { + reject(error instanceof Error ? error : new Error(String(error))); + return; + } + const fail = (error: Error) => { + socket.destroy(); + reject(error); + }; + socket.setTimeout(timeoutMs, () => + fail(new Error(`No connection to 127.0.0.1:${String(port)} within ${String(timeoutMs)}ms`)), + ); + socket.once("error", fail); + socket.once("connect", () => { + socket.setTimeout(0); + socket.off("error", fail); + // why: after connect an error ends the readable through `Readable.toWeb`; a listener must + // still exist or Node raises it as an uncaught exception and takes the daemon down. + socket.on("error", () => undefined); + resolve({ + close: () => { + socket.destroy(); + }, + readable: Readable.toWeb(socket) as ReadableStream, + write: (bytes) => + new Promise((done, failed) => { + socket.write(bytes, (error) => (error ? failed(error) : done())); + }), + }); + }); + }); + } +} + +export class FakeTcpPeer { + readonly written: Uint8Array[] = []; + closed = false; + #controller!: ReadableStreamDefaultController; + readonly stream: TcpStream; + + constructor() { + const readable = new ReadableStream({ + cancel: () => { + this.closed = true; + }, + start: (controller) => { + this.#controller = controller; + }, + }); + this.stream = { + close: () => { + if (this.closed) return; + this.closed = true; + try { + this.#controller.close(); + } catch { + // why: a cancelled readable is already closed; closing the connection is still done. + } + }, + readable, + write: async (bytes) => { + this.written.push(typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes); + }, + }; + } + + writtenText(): string { + return this.written.map((chunk) => new TextDecoder().decode(chunk)).join(""); + } + + send(bytes: Uint8Array | string): void { + if (this.closed) return; + this.#controller.enqueue(typeof bytes === "string" ? new TextEncoder().encode(bytes) : bytes); + } + + end(): void { + this.stream.close(); + } +} + +export class FakeTcpStreamConnector implements TcpStreamConnector { + readonly connections: { readonly port: number; readonly peer: FakeTcpPeer }[] = []; + #refuse = 0; + + refuseNext(count = 1): void { + this.#refuse += count; + } + + async connect(port: number): Promise { + if (this.#refuse > 0) { + this.#refuse -= 1; + throw new Error(`connect ECONNREFUSED 127.0.0.1:${String(port)}`); + } + const peer = new FakeTcpPeer(); + this.connections.push({ peer, port }); + return peer.stream; + } +} From 8a74000fe7f8fe1dd9a62b12a30097f8e95e7b65 Mon Sep 17 00:00:00 2001 From: Szymon Chmal Date: Thu, 15 Oct 2026 12:11:08 +0200 Subject: [PATCH 05/29] refactor(stream): the stream module names no platform; each driver captures its own screen The worker link opens a lease's screen through DriverCatalog.openScreen and the driver's ScreenCapture, so the `platform !== "ios"` gate, the device-blind merge of every driver's lease environment and the iOS tool's flags all leave the module and main.ts. The iOS driver now runs the capture program itself (stdin as liveness, 2 s grace, then SIGKILL); `stream.command` is gone and `drivers.ios.screenCaptureCommand` overrides the program pnpm build now puts in dist/bin. Fixes from the review of #576: - A capture is registered before it starts and its lease is re-read after its first frame, so a lease that ends while a capture starts can no longer leave the next holder's screen streaming to the old viewer. The lease-end participant waits for each capture's close. - The first-frame bound and the between-frames bound are separate and run on the Clock port, which removes the flaky silence test's race. - A gateway reads a worker's refusal to 16 KiB, forwards its code only when it is shaped like ours, and refuses a codec or size it cannot describe instead of calling it h264. - main.ts builds the module from two factories; the gateway path has tests. The module has its manual, and dependency-cruiser keeps it off fs, child process, the HTTP front, the gateway and the drivers. --- .dependency-cruiser.mjs | 16 +- docs/internal/ARCHITECTURE.md | 5 +- package.json | 3 +- scripts/build-capture.mjs | 58 ++++ src/cli/index.test.ts | 1 - src/contract/schemas.ts | 1 - src/core/AGENTS.md | 3 +- src/core/cleanup/idle-destroy.test.ts | 1 - src/core/cleanup/idle-shutdown.test.ts | 1 - src/core/config.ts | 21 +- src/core/doctor.test.ts | 1 - src/core/nuke.test.ts | 1 - src/core/reaper.test.ts | 1 - src/daemon/dispatcher.test.ts | 1 - src/daemon/main.ts | 248 +++------------- src/daemon/server.test.ts | 1 - src/drivers/AGENTS.md | 1 + src/drivers/ios/AGENTS.md | 7 + src/drivers/ios/index.ts | 34 ++- src/drivers/ios/screen-capture.test.ts | 123 ++++++++ src/drivers/ios/screen-capture.ts | 148 ++++++++++ src/http/test-fakes.ts | 1 - src/leasing/acquisition-planner.test.ts | 1 - src/leasing/create-leasing.test.ts | 1 - .../lease-acquisition-coordinator.test.ts | 1 - src/leasing/lease-end-participants.test.ts | 1 - src/leasing/lease-health-monitor.test.ts | 1 - src/leasing/physical-device-watcher.test.ts | 1 - src/leasing/physical-leasing.test.ts | 1 - src/ports/AGENTS.md | 2 +- src/stream/AGENTS.md | 77 +++++ src/stream/CLAUDE.md | 1 + src/stream/capture.test.ts | 269 ------------------ src/stream/capture.ts | 211 -------------- src/stream/captures.test.ts | 152 ++++------ src/stream/captures.ts | 111 +++++--- src/stream/create.test.ts | 77 +++++ src/stream/create.ts | 103 +++++++ src/stream/feed.test.ts | 96 +++++++ src/stream/feed.ts | 134 +++++++++ src/stream/framing.test.ts | 28 +- src/stream/framing.ts | 98 ++++--- src/stream/gateway-link.test.ts | 154 ++++++++++ src/stream/gateway-link.ts | 104 +++++-- src/stream/index.ts | 20 +- src/stream/mounts.ts | 9 +- src/stream/refusal.ts | 33 ++- src/stream/service.test.ts | 16 +- src/stream/service.ts | 8 +- src/stream/target.ts | 3 + src/stream/test-fixtures.ts | 92 ++++++ src/stream/worker-link.test.ts | 238 ++++++++++------ src/stream/worker-link.ts | 173 ++++++----- 53 files changed, 1768 insertions(+), 1125 deletions(-) create mode 100644 scripts/build-capture.mjs create mode 100644 src/drivers/ios/screen-capture.test.ts create mode 100644 src/drivers/ios/screen-capture.ts create mode 100644 src/stream/AGENTS.md create mode 100644 src/stream/CLAUDE.md delete mode 100644 src/stream/capture.test.ts delete mode 100644 src/stream/capture.ts create mode 100644 src/stream/create.test.ts create mode 100644 src/stream/create.ts create mode 100644 src/stream/feed.test.ts create mode 100644 src/stream/feed.ts create mode 100644 src/stream/gateway-link.test.ts create mode 100644 src/stream/target.ts create mode 100644 src/stream/test-fixtures.ts diff --git a/.dependency-cruiser.mjs b/.dependency-cruiser.mjs index 68ddc89e..918adf29 100644 --- a/.dependency-cruiser.mjs +++ b/.dependency-cruiser.mjs @@ -103,8 +103,8 @@ export default { ), direction( "no-fs-or-process", - "core, leasing, agent-device and host-load reach the filesystem and processes only through injected ports", - "^src/(core|leasing|agent-device|host-load)/", + "core, leasing, agent-device, host-load and stream reach the filesystem and processes only through injected ports", + "^src/(core|leasing|agent-device|host-load|stream)/", { dependencyTypes: ["core"], path: fsOrProcess }, ), direction( @@ -113,6 +113,18 @@ export default { "^src/(core|leasing|drivers)/", { path: "^src/agent-device/" }, ), + direction( + "not-stream", + "core, leasing, the drivers, the gateway and the contract import nothing from the stream module", + "^src/(core|leasing|drivers|gateway|contract)/", + { path: "^src/stream/" }, + ), + direction( + "stream-not-edges", + "the stream module is mounted into the HTTP front and the gateway's relay, and imports neither, nor a driver", + "^src/stream/", + { path: "^src/(http|gateway|drivers|daemon|cli|mcp)/" }, + ), direction( "host-load-imports-ports-only", "host-load reads the machine through the HostLoadProbe port and imports no other component", diff --git a/docs/internal/ARCHITECTURE.md b/docs/internal/ARCHITECTURE.md index c67975b5..f357b83b 100644 --- a/docs/internal/ARCHITECTURE.md +++ b/docs/internal/ARCHITECTURE.md @@ -44,6 +44,7 @@ worker daemon ── uplink (WebSocket to /v1/uplink, worker dials) ──┘ | gateway | Fleet queue, routing, forwarding to workers, relay | [gateway](../../src/gateway/AGENTS.md) · [routing](../../src/gateway/routing/AGENTS.md) | | link | Byte channels over the uplink | [link](../../src/link/AGENTS.md) | | agent-device | Optional edge: the agent-device daemon beside a worker, its HTTP front | [agent-device](../../src/agent-device/AGENTS.md) | +| stream | Optional edge: a leased device's live screen over HTTP, relayed by a gateway | [stream](../../src/stream/AGENTS.md) | | http | HTTP frontend: routes, tokens, SSE | [http](../../src/http/AGENTS.md) | | cli | Operator command surface | [cli](../../src/cli/AGENTS.md) | | mcp | stdio MCP server for one agent session | [mcp](../../src/mcp/AGENTS.md) | @@ -67,9 +68,10 @@ Arrows point from the optional to the required (architecture rule 15). | `past-index` | Outside a component, import only its index file (tests may also import its testing file) | | `core-not-leasing` | Core declares a port; the composition root connects leasing to it | | `core-not-drivers` | Core and leasing reach a platform only through the `Driver` interface | -| `no-fs-or-process` | Core, leasing, agent-device and host-load use ports, never `fs` or `child_process` | +| `no-fs-or-process` | Core, leasing, agent-device, host-load and stream use ports, never `fs` or `child_process` | | `not-agent-device` | Core, leasing and drivers import nothing from agent-device | | `host-load-imports-ports-only`, `not-host-load` | host-load stands alone | +| `not-stream`, `stream-not-edges` | Nothing required imports stream; stream imports no frontend, gateway or driver | | `gateway-not-edges`, `gateway-only-dispatch` | Gateway imports no driver or frontend, and from daemon only `src/daemon/dispatch.ts` | | `only-main-knows-gateway` | In daemon, only `src/daemon/main.ts` imports the gateway | | `link-is-portable`, `link-imports-port-types-only` | The link uses no Node module or package, and only the types of ports | @@ -88,6 +90,7 @@ never import each other, directly or through a third. | Startup and stop | [startup](flows/startup.md) | | `device.exec`, local and through a gateway | [device-exec](flows/device-exec.md) | | agent-device through simlock | [agent-device](../../src/agent-device/AGENTS.md) | +| A screen stream, local and through a gateway | [stream](../../src/stream/AGENTS.md) | ## Rules that cut across modules diff --git a/package.json b/package.json index c6a7573a..1d8f22ff 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,10 @@ } }, "scripts": { - "build": "pnpm run build:daemon && pnpm run build:ui", + "build": "pnpm run build:daemon && pnpm run build:ui && pnpm run build:capture", "build:daemon": "tsc -p tsconfig.build.json", "build:ui": "vite build ui", + "build:capture": "node scripts/build-capture.mjs", "build:e2e": "tsc -p e2e/fake-driver/tsconfig.json", "dev:ui": "vite ui", "test": "pnpm run test:unit && pnpm run test:timing && pnpm run test:e2e", diff --git a/scripts/build-capture.mjs b/scripts/build-capture.mjs new file mode 100644 index 00000000..93d5da03 --- /dev/null +++ b/scripts/build-capture.mjs @@ -0,0 +1,58 @@ +#!/usr/bin/env node +// `pnpm build:capture`: puts the screen-capture artifacts beside the compiled daemon, which looks for +// them there. The scrcpy server is copied into dist/vendor/scrcpy/ once its SHA-256 matches the pin in +// vendor/scrcpy/SHA256SUMS.txt; a mismatch fails the build. On macOS with a Swift toolchain the iOS +// capture program in vendor/screen-capture/ is built and copied to dist/bin/simlock-screen-capture. +// Anywhere else it is skipped with a note, and the daemon reports iOS streams as unavailable. +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { chmodSync, copyFileSync, mkdirSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const dist = join(root, "dist"); + +function copyScrcpyServer() { + const sums = readFileSync(join(root, "vendor/scrcpy/SHA256SUMS.txt"), "utf8").trim().split("\n"); + for (const line of sums) { + const [expected, name] = line.trim().split(/\s+/u); + const source = join(root, "vendor/scrcpy", name); + const actual = createHash("sha256").update(readFileSync(source)).digest("hex"); + if (actual !== expected) { + console.error(`build-capture: ${name} does not match its pinned SHA-256 (${actual})`); + process.exit(1); + } + const target = join(dist, "vendor/scrcpy", name); + mkdirSync(dirname(target), { recursive: true }); + copyFileSync(source, target); + } +} + +function buildIosCapture() { + if (process.platform !== "darwin") { + console.log("build-capture: not macOS, skipping the iOS screen-capture program"); + return; + } + const packagePath = join(root, "vendor/screen-capture"); + const scratch = join(packagePath, ".build/out"); + const built = spawnSync( + "swift", + ["build", "-c", "release", "--package-path", packagePath, "--scratch-path", scratch], + { stdio: "inherit" }, + ); + if (built.error !== undefined || built.status !== 0) { + console.warn( + "build-capture: could not build the iOS screen-capture program (is Xcode installed and selected?); " + + "iOS screen streams will be unavailable", + ); + return; + } + const target = join(dist, "bin/simlock-screen-capture"); + mkdirSync(dirname(target), { recursive: true }); + copyFileSync(join(scratch, "release/simlock-screen-capture"), target); + chmodSync(target, 0o755); +} + +copyScrcpyServer(); +buildIosCapture(); diff --git a/src/cli/index.test.ts b/src/cli/index.test.ts index 94cc2b89..ddaaa1f5 100644 --- a/src/cli/index.test.ts +++ b/src/cli/index.test.ts @@ -6190,7 +6190,6 @@ function testConfig(): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/contract/schemas.ts b/src/contract/schemas.ts index 6e560b8f..4c5f9a72 100644 --- a/src/contract/schemas.ts +++ b/src/contract/schemas.ts @@ -909,7 +909,6 @@ export const configSchema = z.object({ stream: z .object({ enabled: z.boolean(), - command: z.string(), fps: z.number(), maxDimension: z.number(), bitrateKbps: z.number(), diff --git a/src/core/AGENTS.md b/src/core/AGENTS.md index 10ddcef1..bd3214cf 100644 --- a/src/core/AGENTS.md +++ b/src/core/AGENTS.md @@ -31,8 +31,7 @@ See also: [leasing](../leasing/AGENTS.md) (lease flow, when reclaim runs), [warm | **Startup, health, drivers** | | | `startup-read.ts`, `startup-converger.ts` | `readStartup`; `StartupConverger`, the device half of startup | | `doctor.ts`, `host-facts.ts` | `Doctor` findings and fixes; `HostFactsReader` for status | -| `driver.ts`, `driver-catalog.ts` | `Driver` interface, errors, `DiskSpaceGuard`; `DriverCatalog` | -| `screen.ts` | `ScreenCapture` (a driver's optional screen), its frame tags and encoder | +| `driver.ts`, `driver-catalog.ts`, `screen.ts` | `Driver`, errors, `DiskSpaceGuard`; `DriverCatalog`; `ScreenCapture`, frame tags | | `catalog-match.ts` | Model name, class and paired runtimes against a catalog entry | | `component-installer.ts` | `ComponentInstaller`: install, list, remove | | `physical-enrollment.ts`, `physical-presence.ts` | `device add`/`remove`; every presence read, `latestPresence` | diff --git a/src/core/cleanup/idle-destroy.test.ts b/src/core/cleanup/idle-destroy.test.ts index 49d318d4..3352f347 100644 --- a/src/core/cleanup/idle-destroy.test.ts +++ b/src/core/cleanup/idle-destroy.test.ts @@ -10,7 +10,6 @@ const config: Config = { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/core/cleanup/idle-shutdown.test.ts b/src/core/cleanup/idle-shutdown.test.ts index d17198a4..ebed1c0a 100644 --- a/src/core/cleanup/idle-shutdown.test.ts +++ b/src/core/cleanup/idle-shutdown.test.ts @@ -8,7 +8,6 @@ const config: Config = { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/core/config.ts b/src/core/config.ts index 183b08b4..34b07d69 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -256,12 +256,10 @@ export interface Config { readonly leaseTtlCapMs?: number; }; readonly stream: { - // why: off by default, because a stream is a capture process pinned to a leased simulator and - // nothing should start one for a machine nobody is looking at. A gateway reads `enabled` alone: - // it answers a stream by relaying it, so the program and its picture settings belong to the - // worker that runs the capture. + // why: off by default, because a stream is a capture pinned to a leased device and nothing + // should start one for a machine nobody is looking at. A gateway reads `enabled` alone: it + // answers a stream by relaying it, so the picture settings belong to the worker that captures. readonly enabled: boolean; - readonly command: string; readonly fps: number; readonly maxDimension: number; readonly bitrateKbps: number; @@ -505,14 +503,9 @@ const WORKER_ONLY_GATEWAY_KEYS: readonly string[] = ["url", "token", "label"]; // why: `command` names the program a worker runs, and a gateway runs none. const WORKER_ONLY_AGENT_DEVICE_KEYS: readonly string[] = ["command"]; -// why: the capture program and its picture settings are what a worker runs and encodes with; a -// gateway relays a stream and never reads a video byte, so it has nothing to do with them. -const WORKER_ONLY_STREAM_KEYS: readonly string[] = [ - "bitrateKbps", - "command", - "fps", - "maxDimension", -]; +// why: the picture settings are what a worker encodes with; a gateway relays a stream and never +// reads a video byte, so it has nothing to do with them. +const WORKER_ONLY_STREAM_KEYS: readonly string[] = ["bitrateKbps", "fps", "maxDimension"]; /** * ADR 0005 §2: "worker-only keys in a gateway's config are ignored with a warning, as unknown @@ -793,7 +786,6 @@ function defaultConfig( agentDevice: { enabled: false, command: "agent-device" }, stream: { enabled: false, - command: "simlock-screen-capture", fps: 30, maxDimension: 720, bitrateKbps: 1_500, @@ -957,7 +949,6 @@ function configValidators(strategy: CapacityStrategyName): Record agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/core/nuke.test.ts b/src/core/nuke.test.ts index fa4b0729..bc0f97d1 100644 --- a/src/core/nuke.test.ts +++ b/src/core/nuke.test.ts @@ -160,7 +160,6 @@ function config(): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/core/reaper.test.ts b/src/core/reaper.test.ts index 7a4a06cb..f1ff5c3c 100644 --- a/src/core/reaper.test.ts +++ b/src/core/reaper.test.ts @@ -56,7 +56,6 @@ function config(): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/daemon/dispatcher.test.ts b/src/daemon/dispatcher.test.ts index 0b42ea53..17610538 100644 --- a/src/daemon/dispatcher.test.ts +++ b/src/daemon/dispatcher.test.ts @@ -3583,7 +3583,6 @@ function testConfig( agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/daemon/main.ts b/src/daemon/main.ts index b43eda8b..49361e43 100644 --- a/src/daemon/main.ts +++ b/src/daemon/main.ts @@ -1,6 +1,6 @@ import { homedir } from "node:os"; import { join, resolve } from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { agentDeviceCapabilitySource, @@ -46,18 +46,8 @@ import { Nuke, SerializedDecision, } from "../core/index.js"; -import { createLeasing, type LeaseEndParticipant } from "../leasing/index.js"; -import { - createCaptureRegistry, - createGatewayStreamLink, - createStreamLeaseEnd, - createStreamMount, - createStreamRelayTarget, - createWorkerStreamLink, - STREAM_TARGET, - type StreamLink, - type StreamRegistry, -} from "../stream/index.js"; +import { createLeasing } from "../leasing/index.js"; +import { createGatewayStream, createWorkerStream } from "../stream/index.js"; import { AdbServerUnavailableError, AndroidDriver, @@ -74,7 +64,6 @@ import { iosPrerequisites, } from "../drivers/ios/index.js"; import { createHttpApp } from "../http/app.js"; -import type { HttpMount } from "../http/app.js"; import type { HttpDispatch } from "../http/dispatcher-session.js"; import { HttpGateway } from "../http/server.js"; import { TokenStore } from "../http/token-store.js"; @@ -101,6 +90,7 @@ import { NodeProcessRunner, NodeProcessSupervisor, NodeSystemStats, + NodeBytePipeLauncher, NodeTcpProbe, resolveDaemonSocketPath, readPackageVersion, @@ -372,17 +362,16 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise driver.virtualLeaseEnvironment()), - ), + ? createWorkerStream({ + clock, logger: logger.child("stream"), registry, + screens: new DriverCatalog(drivers), + settings: { + bitrateKbps: config.stream.bitrateKbps, + fps: config.stream.fps, + maxDimension: config.stream.maxDimension, + }, }) : undefined; // Builds leasing on top of core (ADR 0018 §2). @@ -553,7 +542,7 @@ export async function startDaemon(options: StartDaemonOptions = {}): Promise leasing.corePorts.notifyAvailability()), @@ -871,10 +860,23 @@ async function startGatewayDaemon(options: GatewayDaemonOptions): Promise { + const entry = leaseIndex.resolve(leaseId); + return entry === undefined + ? undefined + : { + ownerId: entry.ownerId, + requesterId: entry.requesterId, + workerId: entry.workerId, + workerLeaseId: entry.workerLeaseId, + }; + }, + relay: (workerId, call) => gatewayRelay.relay(workerId, call), + requesterPrefix: leaseIndex.requesterPrefix, + }, logger: logger.child("stream"), - relay: gatewayRelay, }) : undefined; @@ -1059,188 +1061,16 @@ interface FleetPortParts { const AGENT_DEVICE = "agent-device"; -const SCREEN_STREAM_CAPABILITY = "screen-stream"; +// why: `pnpm build` puts the capture program beside the compiled daemon (scripts/build-capture.mjs), +// so a checkout that built finds it without configuration; `drivers.ios.screenCaptureCommand` +// names another one. +const PACKAGED_SCREEN_CAPTURE = fileURLToPath( + new URL("../bin/simlock-screen-capture", import.meta.url), +); type WorkerView = NonNullable>; -// why: all four sit on one link, so a stream answered over a worker's own HTTP surface and a stream -// answered for a gateway go through the same lease check and the same capture -- one place enforces a -// rule -- and the lease that ends has one registry to stop, whichever door the picture came in by. -interface StreamParts { - readonly capabilitySource: { - readonly name: string; - readonly read: () => Promise<{ - readonly reason?: string; - readonly state: "available" | "off" | "unavailable"; - }>; - }; - readonly leaseEnd: LeaseEndParticipant; - readonly link: StreamLink; - readonly mount: HttpMount; - readonly relayTarget: ( - request: Request, - tenant: string, - leaseId: string | undefined, - ) => Promise; -} - -function createStreamParts(options: { - readonly args: readonly string[]; - readonly command: string; - readonly drivers: readonly Driver[]; - readonly filesystem: Filesystem; - readonly leaseEnvironment: Readonly>; - readonly logger: Logger; - readonly registry: { - readonly snapshot: { - readonly devices: readonly { - readonly address?: string; - readonly id: string; - readonly physical: boolean; - readonly spec: { readonly platform: string }; - }[]; - readonly leases: readonly { - readonly deviceId: string; - readonly id: string; - readonly ownerId: string; - readonly requesterId: string; - }[]; - }; - }; -}): StreamParts { - // why: only a command that spells a path can be checked; a bare program name is resolved by the - // OS at spawn, and a failure there is reported with the spawn's own words rather than guessed at. - const namesAPath = options.command.includes("/"); - const available = namesAPath ? () => options.filesystem.exists(options.command) : undefined; - const captures = createCaptureRegistry(); - const link = createWorkerStreamLink({ - ...(available === undefined ? {} : { available }), - args: options.args, - captures, - command: options.command, - leaseEnvironment: options.leaseEnvironment, - onLog: (line) => options.logger.info("Capture", { line }), - registry: createStreamRegistry(options.registry), - }); - const service = { - link, - logger: { - info: (message: string, fields?: Record) => - options.logger.info(message, fields), - warn: (message: string, fields?: Record) => - options.logger.warn(message, fields), - }, - }; - return { - capabilitySource: { - name: SCREEN_STREAM_CAPABILITY, - read: async () => - available === undefined || (await available()) - ? { state: "available" } - : { reason: `No screen capture program at ${options.command}`, state: "unavailable" }, - }, - link, - leaseEnd: createStreamLeaseEnd(captures), - mount: createStreamMount(service), - relayTarget: createStreamRelayTarget(service), - }; -} - -// why: the snapshot is read at the moment of the read rather than held, because a stream is aimed at -// the device a lease holds right now -- a lease that ended a moment ago is not a stream, and a stale -// read would aim a capture at a simulator this worker has already begun to reclaim. -function createStreamRegistry(registry: { - readonly snapshot: { - readonly devices: readonly { - readonly address?: string; - readonly id: string; - readonly physical: boolean; - readonly spec: { readonly platform: string }; - }[]; - readonly leases: readonly { - readonly deviceId: string; - readonly id: string; - readonly ownerId: string; - readonly requesterId: string; - }[]; - }; -}): StreamRegistry { - return { - deviceOfLease: (leaseId) => { - const snapshot = registry.snapshot; - const lease = snapshot.leases.find((candidate) => candidate.id === leaseId); - if (lease === undefined) return undefined; - const device = snapshot.devices.find((candidate) => candidate.id === lease.deviceId); - if (device === undefined || device.address === undefined) return undefined; - return { - address: device.address, - physical: device.physical, - platform: device.spec.platform, - }; - }, - leaseOwner: (leaseId) => { - const lease = registry.snapshot.leases.find((candidate) => candidate.id === leaseId); - return lease === undefined - ? undefined - : { ownerId: lease.ownerId, requesterId: lease.requesterId }; - }, - }; -} - -function streamCaptureArgs(config: Config): string[] { - return [ - "--codec", - "avcc", - "--fps", - String(config.stream.fps), - "--bitrate", - String(config.stream.bitrateKbps * 1_000), - "--max-dimension", - String(config.stream.maxDimension), - ]; -} - -function createGatewayStreamParts(options: { - readonly leaseIndex: FleetLeaseIndex; - readonly logger: Logger; - readonly relay: GatewayRelay; -}): { readonly mount: HttpMount } { - const link = createGatewayStreamLink({ - fleet: { - holder: (leaseId) => { - const entry = options.leaseIndex.resolve(leaseId); - return entry === undefined - ? undefined - : { - ownerId: entry.ownerId, - requesterId: entry.requesterId, - workerLeaseId: entry.workerLeaseId, - workerId: entry.workerId, - }; - }, - requesterPrefix: options.leaseIndex.requesterPrefix, - relay: (workerId, request, tenant, leaseId) => - options.relay.relay(workerId, { - request, - target: STREAM_TARGET, - tenant, - ...(leaseId === undefined ? {} : { leaseId }), - }), - }, - }); - return { - mount: createStreamMount({ - link, - logger: { - info: (message: string, fields?: Record) => - options.logger.info(message, fields), - warn: (message: string, fields?: Record) => - options.logger.warn(message, fields), - }, - }), - }; -} - +// why: the agent-device edge imports nothing from the gateway, so this is the one place the gateway's parts become its port. function agentDeviceFleetPort({ instanceId, leaseIndex, @@ -1509,6 +1339,12 @@ async function discoverIosDriver( idGenerator: options.idGenerator, instanceId: options.instanceId, onSlimmed: emitSlimDiagnostic(options.eventBus), + screenCapture: { + baseEnvironment: process.env, + command: PACKAGED_SCREEN_CAPTURE, + launcher: new NodeBytePipeLauncher(), + onLog: (line) => logger.debug("Screen capture", { line }), + }, onSlimSkipped: (fact) => { logger.warn("Skipped iOS device slim", { deviceId: fact.deviceId, diff --git a/src/daemon/server.test.ts b/src/daemon/server.test.ts index 4d9a135c..b7f2071f 100644 --- a/src/daemon/server.test.ts +++ b/src/daemon/server.test.ts @@ -3573,7 +3573,6 @@ function testConfig( agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/drivers/AGENTS.md b/src/drivers/AGENTS.md index 3000a16d..3d53b499 100644 --- a/src/drivers/AGENTS.md +++ b/src/drivers/AGENTS.md @@ -16,6 +16,7 @@ Manuals: [Android](android/AGENTS.md), [iOS](ios/AGENTS.md). Flows: [device sele | `findComponent`, `installComponent`, `listComponents`, `removeComponent` | Runtimes / system images. Only core's `ComponentInstaller` installs or removes | | `inspectPhysical`, `listPresentPhysical` | Enrollment read and presence of physical devices | | `leaseEnvironment`, `virtualLeaseEnvironment`, `passthrough` | What a holder needs to reach its device | +| `screenCapture` (optional) | A device's screen as frames (`core/screen.ts`); absent = cannot stream | | `dispose` | Daemon stop. Android reaps its adb server here | ## Rules both drivers keep diff --git a/src/drivers/ios/AGENTS.md b/src/drivers/ios/AGENTS.md index 5c6648df..3b3cf134 100644 --- a/src/drivers/ios/AGENTS.md +++ b/src/drivers/ios/AGENTS.md @@ -14,6 +14,7 @@ Parent: [drivers](../AGENTS.md). Flows: [device selection](../../../docs/interna | `devicectl-exec.ts` | `devicectlExecCommand`: the reviewed `devicectl` list | | `slim-labels.ts` | `SLIM_CATEGORIES`, `resolveSlimCategories`, `labelsFor`, `slimSignature` | | `prerequisites.ts` | `iosPrerequisites`: Xcode selected, license accepted, first launch done | +| `screen-capture.ts` | `createIosScreenCapture`: runs `vendor/screen-capture/` on a simulator | ## How it works @@ -78,6 +79,12 @@ Passthrough — Lease environment: virtual `SIMLOCK_IOS_DEVICE_SET=`; physical `SIMLOCK_DEVICE_UDID=`. +Screen — `screenCapture` (only when `create` is given a launcher): `simlock-screen-capture +--codec avcc --fps --bitrate --max-dimension` with `SIMLOCK_IOS_DEVICE_SET=`; stdout is +the frames, stdin its liveness. Close: stdin, 2 s, SIGKILL. Physical → `ScreenNotSupportedError`; +program missing → `ScreenUnavailableError`. Default program: dist/bin, built by `pnpm build`; +`drivers.ios.screenCaptureCommand` names another. + Advisories — `runtime-cache-unreclaimable` (asset bundles no installed runtime mounts); `slim-runtime-unsupported` (installed runtimes below 18.5, only when `ios.defaultMode` is `slim`). diff --git a/src/drivers/ios/index.ts b/src/drivers/ios/index.ts index 54e00a44..3d16b560 100644 --- a/src/drivers/ios/index.ts +++ b/src/drivers/ios/index.ts @@ -43,8 +43,10 @@ import { type DeviceMode, type DeviceSpec, type VirtualSpec, + type ScreenCapture, } from "../../core/index.js"; import type { + BytePipeLauncher, Clock, Filesystem, IdGenerator, @@ -54,6 +56,7 @@ import type { import { runBoundedProcess, runInstallerProcess } from "../installer-process.js"; import { devicectlExecCommand } from "./devicectl-exec.js"; import { IosPhysicalHandler } from "./physical.js"; +import { createIosScreenCapture } from "./screen-capture.js"; import { labelsFor, resolveSlimCategories, slimSignature } from "./slim-labels.js"; const COMMAND_TIMEOUT_MS = 30_000; @@ -206,6 +209,14 @@ export interface IosSimctlDriverOptions { readonly bootTimeoutMs: number; }; readonly slimByDefault?: boolean; + // why: absent when this host has no way to run the capture program; the driver then offers no + // screen at all rather than one that fails on every open. + readonly screenCapture?: { + readonly command: string; + readonly launcher: BytePipeLauncher; + readonly baseEnvironment: NodeJS.ProcessEnv; + readonly onLog?: (line: string) => void; + }; } type SlimOptions = NonNullable; @@ -306,7 +317,23 @@ export class IosSimctlDriver implements Driver { this.#rootOptions = rootOptions; this.#slim = options.slim; this.#slimByDefault = options.slimByDefault === true; - } + this.screenCapture = + options.screenCapture === undefined + ? undefined + : createIosScreenCapture({ + baseEnvironment: options.screenCapture.baseEnvironment, + clock: options.clock, + command: configuredScreenCaptureCommand(options) ?? options.screenCapture.command, + deviceSet: deviceRoot, + filesystem: options.filesystem, + launcher: options.screenCapture.launcher, + ...(options.screenCapture.onLog === undefined + ? {} + : { onLog: options.screenCapture.onLog }), + }); + } + + readonly screenCapture: ScreenCapture | undefined; /** * why: Establishes the device set this driver owns before it can be asked to do anything, @@ -1633,6 +1660,11 @@ class IosRuntimeUnavailableForDownloadError extends RuntimeMissingError { } } +function configuredScreenCaptureCommand(options: IosSimctlDriverOptions): string | undefined { + const configured = options.driverConfig["screenCaptureCommand"]; + return typeof configured === "string" && configured !== "" ? configured : undefined; +} + function configuredDeviceRoot(options: IosSimctlDriverOptions): string { const configured = options.driverConfig["deviceRoot"]; diff --git a/src/drivers/ios/screen-capture.test.ts b/src/drivers/ios/screen-capture.test.ts new file mode 100644 index 00000000..ce0e44ef --- /dev/null +++ b/src/drivers/ios/screen-capture.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from "vitest"; + +import { ScreenNotSupportedError, ScreenUnavailableError } from "../../core/index.js"; +import { FakeBytePipeLauncher, FakeClock, MemoryFilesystem } from "../../ports/index.js"; +import { createIosScreenCapture } from "./screen-capture.js"; + +const settings = { bitrateKbps: 1500, fps: 30, maxDimension: 720 }; +const simulator = { + address: "UDID-1", + deviceId: "UDID-1", + driverData: {}, + physical: false, +} as const; +const COMMAND = "/opt/simlock/simlock-screen-capture"; + +async function setup() { + const filesystem = new MemoryFilesystem(); + await filesystem.mkdirp("/opt/simlock"); + await filesystem.writeFileAtomic(COMMAND, ""); + const launcher = new FakeBytePipeLauncher(); + const clock = new FakeClock(); + const capture = createIosScreenCapture({ + baseEnvironment: { PATH: "/usr/bin" }, + clock, + command: COMMAND, + deviceSet: "/home/.simlock/devices/ios", + filesystem, + launcher, + }); + return { capture, clock, filesystem, launcher }; +} + +async function readAll(stream: ReadableStream): Promise { + const bytes: number[] = []; + for await (const chunk of stream) bytes.push(...chunk); + return bytes; +} + +describe("iOS screen capture", () => { + it("runs the capture program on the device's UDID, inside Simlock's device set, with the stream's settings", async () => { + const { capture, launcher } = await setup(); + await capture.open(simulator, settings); + expect(launcher.starts).toEqual([ + { + args: [ + "UDID-1", + "--codec", + "avcc", + "--fps", + "30", + "--bitrate", + "1500000", + "--max-dimension", + "720", + ], + command: COMMAND, + env: { PATH: "/usr/bin", SIMLOCK_IOS_DEVICE_SET: "/home/.simlock/devices/ios" }, + }, + ]); + }); + + it("refuses a physical iPhone as not supported, without starting anything", async () => { + const { capture, launcher } = await setup(); + await expect( + capture.open({ ...simulator, enrolledApps: [], physical: true }, settings), + ).rejects.toBeInstanceOf(ScreenNotSupportedError); + expect(launcher.starts).toEqual([]); + }); + + it("refuses as unavailable, and says why, when the capture program is not installed", async () => { + const { capture, filesystem, launcher } = await setup(); + await filesystem.rm(COMMAND); + expect(await capture.unavailable()).toBe(`No screen capture program at ${COMMAND}`); + await expect(capture.open(simulator, settings)).rejects.toBeInstanceOf(ScreenUnavailableError); + expect(launcher.starts).toEqual([]); + }); + + it("hands out the program's stdout byte for byte and ends cleanly when it exits 0", async () => { + const { capture, launcher } = await setup(); + const feed = await capture.open(simulator, settings); + launcher.last.write(new Uint8Array([1, 2, 3])); + launcher.last.exit({ code: 0, signal: null }); + expect(await readAll(feed.body)).toEqual([1, 2, 3]); + }); + + it("fails the body with the program's last log line when it dies mid-picture", async () => { + const { capture, launcher } = await setup(); + const feed = await capture.open(simulator, settings); + launcher.last.stderr("Device UDID-1 shut down"); + launcher.last.exit({ code: 3, signal: null }); + await expect(readAll(feed.body)).rejects.toThrow( + "The screen capture program exited with code 3: Device UDID-1 shut down", + ); + }); + + it("close stops the program through stdin and resolves once it has exited", async () => { + const { capture, launcher } = await setup(); + const feed = await capture.open(simulator, settings); + await feed.close(); + expect(launcher.last.stdinClosed).toBe(true); + expect(launcher.last.kills).toEqual([]); + }); + + it("close kills a program that ignores its closed stdin once the grace runs out", async () => { + const { capture, clock, launcher } = await setup(); + const feed = await capture.open(simulator, settings); + launcher.last.exitOnStdinClose = false; + const closed = feed.close(); + await Promise.resolve(); + clock.advance(2_000); + await closed; + expect(launcher.last.kills).toEqual(["SIGKILL"]); + }); + + it("cancelling the body stops the program, and a second close does not stop it again", async () => { + const { capture, launcher } = await setup(); + const feed = await capture.open(simulator, settings); + await feed.body.cancel(); + await feed.close(); + expect(launcher.last.stdinClosed).toBe(true); + expect(launcher.processes).toHaveLength(1); + }); +}); diff --git a/src/drivers/ios/screen-capture.ts b/src/drivers/ios/screen-capture.ts new file mode 100644 index 00000000..b6183487 --- /dev/null +++ b/src/drivers/ios/screen-capture.ts @@ -0,0 +1,148 @@ +import { + isPhysicalDriverDevice, + type ScreenCapture, + type ScreenFeed, + ScreenNotSupportedError, + type ScreenSettings, + ScreenUnavailableError, +} from "../../core/index.js"; +import type { BytePipeLauncher, BytePipeProcess, Clock, Filesystem } from "../../ports/index.js"; + +export interface IosScreenCaptureOptions { + readonly command: string; + readonly launcher: BytePipeLauncher; + readonly clock: Clock; + readonly filesystem: Filesystem; + readonly baseEnvironment: NodeJS.ProcessEnv; + readonly deviceSet: string; + readonly onLog?: (line: string) => void; +} + +// why: closing stdin is the stop the capture program was built for, and it needs a moment to flush +// its last log line; a program that ignores its liveness handle is killed after this, so a stuck +// encoder cannot pin a simulator's framebuffer. +const STOP_GRACE_MS = 2_000; +// why: a SIGKILLed child is reaped by the kernel at once; the bound exists only so a close cannot +// wait forever on an exit that the OS never reports. +const KILL_WAIT_MS = 2_000; +const LOG_TAIL = 20; + +export function createIosScreenCapture(options: IosScreenCaptureOptions): ScreenCapture { + // why: only a command that spells a path can be checked; a bare program name is resolved by the + // OS at spawn, and a failure there is reported with the spawn's own words. + const missing = async (): Promise => + options.command.includes("/") && !(await options.filesystem.exists(options.command)) + ? `No screen capture program at ${options.command}` + : undefined; + + return { + open: async (device, settings) => { + if (isPhysicalDriverDevice(device)) { + throw new ScreenNotSupportedError( + "A physical iPhone's screen cannot be streamed; only a simulator's can", + ); + } + const reason = await missing(); + if (reason !== undefined) throw new ScreenUnavailableError(reason); + return startCapture(options, device.deviceId, settings); + }, + unavailable: missing, + }; +} + +function startCapture( + options: IosScreenCaptureOptions, + udid: string, + settings: ScreenSettings, +): ScreenFeed { + const tail: string[] = []; + const child = options.launcher.start( + options.command, + [ + udid, + "--codec", + "avcc", + "--fps", + String(settings.fps), + "--bitrate", + String(settings.bitrateKbps * 1_000), + "--max-dimension", + String(settings.maxDimension), + ], + { + env: { ...options.baseEnvironment, SIMLOCK_IOS_DEVICE_SET: options.deviceSet }, + onStderrLine: (line) => { + tail.push(line); + if (tail.length > LOG_TAIL) tail.shift(); + options.onLog?.(line); + }, + }, + ); + const close = once(() => stop(child, options.clock)); + return { body: bodyOf(child, tail, close), close }; +} + +// why: a program that dies mid-picture ends its stdout like one that finished, so the body asks how +// it exited and fails with its last log line rather than ending cleanly -- a viewer must be able to +// tell a simulator that shut down from a stream that was asked to stop. +function bodyOf( + child: BytePipeProcess, + tail: readonly string[], + close: () => Promise, +): ReadableStream { + const reader = child.stdout.getReader(); + return new ReadableStream({ + cancel: async (reason) => { + await reader.cancel(reason).catch(() => undefined); + await close(); + }, + pull: async (controller) => { + const step = await reader.read().catch((error: unknown) => ({ error })); + if ("error" in step) { + await close(); + controller.error(step.error); + return; + } + if (!step.done) { + controller.enqueue(step.value); + return; + } + const exit = await child.exited; + await close(); + if (exit.code === 0) controller.close(); + else { + const how = + exit.error ?? + (exit.signal === null + ? `exited with code ${String(exit.code)}` + : `was killed by ${exit.signal}`); + const last = tail.at(-1); + controller.error( + new Error(`The screen capture program ${how}${last === undefined ? "" : `: ${last}`}`), + ); + } + }, + }); +} + +async function stop(child: BytePipeProcess, clock: Clock): Promise { + child.closeStdin(); + if (await exitsWithin(child, clock, STOP_GRACE_MS)) return; + child.kill("SIGKILL"); + await exitsWithin(child, clock, KILL_WAIT_MS); +} + +function exitsWithin(child: BytePipeProcess, clock: Clock, ms: number): Promise { + return new Promise((resolve) => { + const timer = clock.setTimer(ms, () => resolve(false)); + void child.exited.then(() => { + clock.cancel(timer); + resolve(true); + }); + }); +} + +function once(fn: () => Promise): () => Promise { + let running: Promise | undefined; + return () => (running ??= fn()); +} diff --git a/src/http/test-fakes.ts b/src/http/test-fakes.ts index ad9076ec..151291a0 100644 --- a/src/http/test-fakes.ts +++ b/src/http/test-fakes.ts @@ -35,7 +35,6 @@ export function testConfig( agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/acquisition-planner.test.ts b/src/leasing/acquisition-planner.test.ts index 32254121..63a89ee1 100644 --- a/src/leasing/acquisition-planner.test.ts +++ b/src/leasing/acquisition-planner.test.ts @@ -19,7 +19,6 @@ const config: Config = { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/create-leasing.test.ts b/src/leasing/create-leasing.test.ts index 9e66c80b..161d70e0 100644 --- a/src/leasing/create-leasing.test.ts +++ b/src/leasing/create-leasing.test.ts @@ -48,7 +48,6 @@ function config( agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/lease-acquisition-coordinator.test.ts b/src/leasing/lease-acquisition-coordinator.test.ts index dd7b4bcc..6cba84ba 100644 --- a/src/leasing/lease-acquisition-coordinator.test.ts +++ b/src/leasing/lease-acquisition-coordinator.test.ts @@ -66,7 +66,6 @@ function config(maxDevices = 1, maxRunning = 1): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/lease-end-participants.test.ts b/src/leasing/lease-end-participants.test.ts index 2e8933c1..d50f5cff 100644 --- a/src/leasing/lease-end-participants.test.ts +++ b/src/leasing/lease-end-participants.test.ts @@ -115,7 +115,6 @@ function config(options: EngineOptions): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/lease-health-monitor.test.ts b/src/leasing/lease-health-monitor.test.ts index 7be60239..f2c38773 100644 --- a/src/leasing/lease-health-monitor.test.ts +++ b/src/leasing/lease-health-monitor.test.ts @@ -27,7 +27,6 @@ function config(overrides: Partial = {}): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/physical-device-watcher.test.ts b/src/leasing/physical-device-watcher.test.ts index 2a12f080..87574651 100644 --- a/src/leasing/physical-device-watcher.test.ts +++ b/src/leasing/physical-device-watcher.test.ts @@ -49,7 +49,6 @@ function config(options: HarnessOptions): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/leasing/physical-leasing.test.ts b/src/leasing/physical-leasing.test.ts index 3cb798b1..fcc78480 100644 --- a/src/leasing/physical-leasing.test.ts +++ b/src/leasing/physical-leasing.test.ts @@ -44,7 +44,6 @@ function config(leaseOverrides: object = {}): Config { agentDevice: { command: "agent-device", enabled: false }, stream: { bitrateKbps: 1500, - command: "simlock-screen-capture", enabled: false, fps: 30, maxDimension: 720, diff --git a/src/ports/AGENTS.md b/src/ports/AGENTS.md index afab7c3c..009769b1 100644 --- a/src/ports/AGENTS.md +++ b/src/ports/AGENTS.md @@ -35,7 +35,7 @@ Uses: nothing from other components. Imported by: every component that touches t ## Rules - Application code never imports `fs` or `child_process`, or calls `Date.now()` or - `setTimeout`: it takes a port. Lint fails core, leasing, agent-device and host-load on an + `setTimeout`: it takes a port. Lint fails core, leasing, agent-device, host-load and stream on an `fs` or `child_process` import (`no-fs-or-process` in `.dependency-cruiser.mjs`). - The real adapters are built once, in the composition root (`src/daemon/main.ts`, or the CLI and MCP environment builders), and injected. diff --git a/src/stream/AGENTS.md b/src/stream/AGENTS.md new file mode 100644 index 00000000..6eaed713 --- /dev/null +++ b/src/stream/AGENTS.md @@ -0,0 +1,77 @@ +# Stream + +Does: serves a leased device's live screen at `GET /v1/stream/leases/{leaseId}`, on a worker and through a gateway. +Entry: `index.ts` → `createWorkerStream` (worker), `createGatewayStream` (gateway). +Uses: core (`ScreenFeed`, frame tags, registry records), leasing (`LeaseEndParticipant` type), ports. +Never imports: http, gateway, drivers, daemon, `fs`, `child_process`. Imported by: `daemon/main.ts` only. +Off unless `stream.enabled` (false). Off: nothing mounted, no relay target, no capability row. + +## Files + +| File | Role | +| ------------------------ | ------------------------------------------------------------------------------- | +| `create.ts` | The two factories; the `stream` capability read from each driver's availability | +| `worker-link.ts` | Worker: lease check, open through the driver, register, re-check after frame 1 | +| `gateway-link.ts` | Gateway: fleet ownership, relay to the holding worker, refusal and header check | +| `feed.ts` | `readScreenFeed`: meta frame first, start and silence bounds, close on any end | +| `framing.ts` | `MetaFrameScanner`, `checkedMeta`: codec and size a picture may claim | +| `captures.ts` | Captures per lease; the lease-end participant that stops them | +| `service.ts` | One request either door: method, refusal body, response headers, log line | +| `mounts.ts` | The HTTP mount and the relay target | +| `refusal.ts` | `StreamRefusalError`, each refusal's status and code, a relayed refusal checked | +| `caller.ts`, `target.ts` | Who asks; `STREAM_TARGET` (`stream`) | +| `test-fixtures.ts` | `TestRegistry`, `ScriptedFeed` for tests | + +## How it works + +Worker — `worker-link.ts` `createWorkerStreamLink`: + +1. Lease from the registry snapshot. Agent: its requester (relayed) or principal must hold it. + Unknown and not yours both answer 404 `UNKNOWN_LEASE`; nothing is opened first. +2. `captures.begin(leaseId)` — registered before the capture starts. +3. `DriverCatalog.openScreen(platform, device, settings)` → the driver's `ScreenFeed`. + `ScreenNotSupportedError` → 415 `STREAM_NOT_SUPPORTED`; anything else → 503 `STREAM_UNAVAILABLE`. +4. `readScreenFeed`: the meta frame within the start bound, or 503 and close. +5. Lease re-read: gone, re-granted, or its slot stopped → close, 404. +6. 200; headers `simlock-stream-{codec,fps,width,height}`; body = the frames, meta first. + +Ends — each closes the capture once (`closedOnce`): the viewer cancels or aborts; the capture +ends or errors; no byte within the silence bound; the lease ends (`createStreamLeaseEnd` → +`captures.stop`, which waits for every close, bounded by the driver and by leasing's signal). + +Gateway — `gateway-link.ts`: the fleet lease index names owner, worker and the worker's lease +id. Not the owner (agent) → 404. Relays `GET /stream/leases/` as target `stream` +with tenant `` from the index, never from the caller. A refusal +body is read to 16 KiB; its code is forwarded only if it matches `^[A-Z][A-Z0-9_]{0,63}$`, +else 503 `STREAM_UNAVAILABLE`. A 2xx without a picture → 502. Headers go through `checkedMeta`. +The body is relayed unread; the gateway's relay bounds it by inactivity, not by a deadline. + +Capability `stream`: `available` when any driver can capture, with the others' reasons; +`unavailable` when none can. + +## State and timers + +Memory only: running captures per lease (`captures.ts`). + +| What | Default | Key | +| ---------------------------- | -------------------- | --------------------------------------------------------- | +| First frame | 30 s | `DEFAULT_START_MS` | +| Silence between frames | 30 s | `DEFAULT_SILENCE_MS` | +| Frame ceiling (envelope) | 8 MiB | `MAX_FRAME_BYTES` | +| fps / longest side / bitrate | 30 / 720 / 1500 kbps | `stream.fps`, `stream.maxDimension`, `stream.bitrateKbps` | + +## Rules + +- No platform name here: a driver offers `screenCapture` or not (`core/screen.ts`). +- Ownership before any work; unknown and not-yours answer the same. +- A capture is registered before it starts and the lease is re-read after its first frame, + so a capture never outlives its lease (`worker-link.test.ts`). +- A gateway never parses a video byte and never takes a caller's word for the requester. +- No business events: watching changes no lease or device. Each open and refusal is logged. + +## Known gaps + +- **View only.** No touch or key input. +- **Each viewer is its own capture.** Two tabs on one device run two captures. +- **A frame skipped by a slow viewer is not dropped, it is waited for.** A viewer that stops + reading slows the capture behind it to its pace. diff --git a/src/stream/CLAUDE.md b/src/stream/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/src/stream/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/src/stream/capture.test.ts b/src/stream/capture.test.ts deleted file mode 100644 index 19a6d085..00000000 --- a/src/stream/capture.test.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { afterAll, describe, expect, it } from "vitest"; - -import { captureStream } from "./capture.js"; - -// why: these tests spawn real programs rather than faking the spawn, because the whole point of this -// module is how it treats a child process -- its stdin, its stdout mid-chunk, its death -- and a fake -// child proves the arrangement of the fake. -const dirs: string[] = []; - -async function program(source: string): Promise { - const dir = await mkdtemp(join(tmpdir(), "simlock-capture-")); - dirs.push(dir); - const path = join(dir, "capture.mjs"); - await writeFile(path, `#!/usr/bin/env node\n${source}`); - await chmod(path, 0o755); - return path; -} - -afterAll(async () => { - await Promise.all(dirs.splice(0).map((dir) => rm(dir, { force: true, recursive: true }))); -}); - -function frame(tag: number, payload: Buffer): Buffer { - const head = Buffer.alloc(5); - head.writeUInt32BE(payload.length + 1, 0); - head[4] = tag; - return Buffer.concat([head, payload]); -} - -function metaFrame(): Buffer { - return frame(0x05, Buffer.from(JSON.stringify({ codec: "h264", fps: 30, height: 720, width: 330 }))); -} - -function collect(body: ReadableStream, bytes: number): Promise { - return (async () => { - const reader = body.getReader(); - const chunks: Uint8Array[] = []; - let have = 0; - while (have < bytes) { - const step = await reader.read(); - if (step.done) break; - chunks.push(step.value); - have += step.value.length; - } - await reader.cancel().catch(() => undefined); - return new Uint8Array(Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))); - })(); -} - -describe("captureStream", () => { - it("waits for the meta frame and hands the body back from its first byte", async () => { - const command = await program(` - const meta = ${JSON.stringify([...metaFrame()])}; - process.stdout.write(Buffer.from(meta)); - setInterval(() => process.stdout.write(Buffer.from([1,2,3])), 10); - process.stdin.resume(); - `); - const stream = await captureStream({ args: [], command, env: process.env }); - - expect(stream.meta).toEqual({ codec: "h264", fps: 30, height: 720, width: 330 }); - // why: the meta frame is the head's answer, and a decoder still needs it in the body -- a reader - // that got only [1,2,3] lost the frame that says how to read them. - const first = await collect(stream.body, metaFrame().length + 3); - expect(Buffer.from(first.subarray(0, metaFrame().length)).equals(metaFrame())).toBe(true); - expect([...first.subarray(metaFrame().length)]).toEqual([1, 2, 3]); - stream.close(); - }); - - it("keeps the bytes that arrived in the same read as the meta frame", async () => { - // why: one write of meta-and-frames-together is what a pipe does under load, and a scanner that - // returned only the meta frame would drop every frame that shared the read with it. - const whole = Buffer.concat([metaFrame(), frame(0x03, Buffer.from([9, 9, 9]))]); - const command = await program(` - process.stdout.write(Buffer.from(${JSON.stringify([...whole])})); - process.stdin.resume(); - `); - const stream = await captureStream({ args: [], command, env: process.env }); - expect([...(await collect(stream.body, whole.length))]).toEqual([...whole]); - stream.close(); - }); - - it("reads a meta frame that arrives a few bytes at a time", async () => { - const command = await program(` - const bytes = Buffer.from(${JSON.stringify([...metaFrame()])}); - let at = 0; - const timer = setInterval(() => { - process.stdout.write(bytes.subarray(at, at + 2)); - at += 2; - if (at >= bytes.length) clearInterval(timer); - }, 2); - process.stdin.resume(); - `); - const stream = await captureStream({ args: [], command, env: process.env }); - expect(stream.meta.width).toBe(330); - stream.close(); - }); - - it("stops the tool the way the tool was built to be stopped, and says so exactly once", async () => { - // why: the claim is that the child sees its stdin close -- the liveness handle the capture program - // is written against -- and not merely that this process forgot about it. A tool that ignored stdin - // would keep filming, and only reading what the child itself wrote proves it stopped. - const dir = await mkdtemp(join(tmpdir(), "simlock-capture-")); - dirs.push(dir); - const seen = join(dir, "stdin-ended"); - const command = await program(` - import { writeFileSync } from "node:fs"; - process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); - process.stdin.on("end", () => writeFileSync(${JSON.stringify(seen)}, "ended")); - process.stdin.resume(); - `); - let ended = 0; - const stream = await captureStream({ - args: [], - command, - env: process.env, - onEnd: () => { - ended += 1; - }, - }); - stream.close(); - stream.close(); - expect(ended).toBe(1); - - const { readFile } = await import("node:fs/promises"); - for (let at = 0; at < 100; at += 1) { - try { - expect(await readFile(seen, "utf8")).toBe("ended"); - return; - } catch { - await new Promise((resolve) => setTimeout(resolve, 20)); - } - } - throw new Error("the capture program never saw its stdin close"); - }); - - it("reports a tool that dies before its first frame, with its own words", async () => { - const command = await program('console.error("Device not found in device set"); process.exit(1);'); - await expect(captureStream({ args: [], command, env: process.env })).rejects.toThrow( - /not found in device set/u, - ); - }); - - it("tells whoever registered a tool that died on its own, without being asked to close it", async () => { - const command = await program(` - process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); - setTimeout(() => process.exit(0), 20); - `); - let ended = 0; - const stream = await captureStream({ - args: [], - command, - env: process.env, - onEnd: () => { - ended += 1; - }, - }); - const reader = stream.body.getReader(); - // why: a simulator that was shut down mid-picture ends this stream on its own; a registry still - // naming the capture after that is a lease that looks like it still has a picture running. - for (;;) { - const step = await reader.read(); - if (step.done) break; - } - await new Promise((resolve) => setTimeout(resolve, 60)); - expect(ended).toBe(1); - }); - - it("ends the capture when its body is dropped", async () => { - const command = await program(` - process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); - setInterval(() => process.stdout.write(Buffer.from([0])), 5); - process.stdin.resume(); - `); - let ended = 0; - const stream = await captureStream({ - args: [], - command, - env: process.env, - onEnd: () => { - ended += 1; - }, - }); - await stream.body.cancel(); - expect(ended).toBe(1); - }); - - it("gives up on a tool that starts and never writes a frame", async () => { - const command = await program("process.stdin.resume();"); - // why: this is the failure a request could otherwise wait on forever -- a program that exists, that - // spawned, and that has nothing to say. The bound is what turns it into an answer. - await expect( - captureStream({ args: [], command, env: process.env, silenceMs: 250 }), - ).rejects.toThrow(/wrote nothing for 250ms/u); - }); - - it("ends a stream whose tool goes quiet mid-picture", async () => { - const command = await program(` - process.stdout.write(Buffer.from(${JSON.stringify([...metaFrame()])})); - // why: nothing more arrives, which is the whole case under test - process.stdin.resume(); - `); - let ended = 0; - const stream = await captureStream({ - args: [], - command, - env: process.env, - onEnd: () => { - ended += 1; - }, - silenceMs: 250, - }); - const reader = stream.body.getReader(); - // why: an errored stream rejects `closed` too, and a test that lets that escape reports an - // unhandled rejection instead of the claim it is making. - reader.closed.catch(() => undefined); - // why: drained rather than read once, because the stream pulls ahead of the reader and so *which* - // read meets the silence is not knowable here -- the claim is that reading this body cannot go on - // forever, whichever step the bound is reported on. The race against a longer timer is what makes a - // bound that never fired fail with a named reason instead of hanging to vitest's own timeout, and - // the cancel inside it is what frees a read still waiting when that happens. - const drained: Promise = (async () => { - try { - for (;;) { - const step = await reader.read(); - if (step.done) return "the body ended cleanly"; - } - } catch (error) { - return error instanceof Error ? error.message : String(error); - } - })(); - const next: string = await Promise.race([ - drained, - new Promise((resolve) => { - const timer = setTimeout(() => { - void reader.cancel().catch(() => undefined); - resolve("never answered"); - }, 5_000); - timer.unref(); - }), - ]); - // why: a bound that held only before the first frame would leave a viewer watching a picture that - // froze, which is the failure mode this whole module is here to avoid. - expect(next).toMatch(/^The screen capture produced no stream \(the capture tool wrote nothing/u); - // why: the reader's failure and the capture's stop are two claims, and only the second keeps a - // simulator's framebuffer from being pinned by a stream nobody can see any more. - expect(ended).toBe(1); - }); - - it("refuses a tool that is not there rather than hanging", async () => { - await expect( - captureStream({ - args: [], - command: join(tmpdir(), "simlock-no-such-capture-program"), - env: process.env, - }), - ).rejects.toThrow(/simlock-no-such-capture-program/u); - }); - - it("refuses an output that is not a frame envelope", async () => { - const command = await program(`process.stdout.write("not a stream at all\\n"); process.exit(0);`); - await expect(captureStream({ args: [], command, env: process.env })).rejects.toThrow( - /not a frame envelope|ended before it wrote a frame/u, - ); - }); -}); diff --git a/src/stream/capture.ts b/src/stream/capture.ts deleted file mode 100644 index b2daf474..00000000 --- a/src/stream/capture.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { spawn } from "node:child_process"; -import { Readable } from "node:stream"; - -import { MetaFrameScanner, type CaptureMeta } from "./framing.js"; - -export interface CaptureOptions { - readonly command: string; - readonly args: readonly string[]; - readonly env: NodeJS.ProcessEnv; - onLog?(line: string): void; - onEnd?(): void; - // why: how long a capture may stay silent before its stream is called dead; see DEFAULT_SILENCE_MS - // for the default and why there is a bound at all. - readonly silenceMs?: number; -} - -export interface CapturedStream { - readonly meta: CaptureMeta; - readonly body: ReadableStream; - close(): void; -} - -const LOG_TAIL = 20; - -// why: a capture program that starts and never writes is the one failure a request cannot tell from a -// slow one, and an unbounded wait here would hold a viewer's connection, a worker's lease check and a -// simulator's framebuffer for a program that is never going to speak. Generous against a cold -// simulator; a bound rather than none because every other wait on another process has one. -const DEFAULT_SILENCE_MS = 30_000; - -export async function captureStream(options: CaptureOptions): Promise { - // why: stdin stays open on purpose. The capture tool treats it as a liveness handle, so closing it - // is the stop the tool was built for, and a daemon that is killed outright closes it by itself -- - // which is the only reason a capture cannot outlive a worker that was SIGKILLed. - const child = spawn(options.command, [...options.args], { - env: options.env, - stdio: ["pipe", "pipe", "pipe"], - }); - child.stdin.on("error", () => undefined); - - const tail: string[] = []; - child.stderr.setEncoding("utf8"); - child.stderr.on("data", (chunk: string) => { - for (const line of chunk.split("\n")) { - if (line.trim() === "") continue; - tail.push(line); - if (tail.length > LOG_TAIL) tail.shift(); - options.onLog?.(line); - } - }); - - // why: closing stdin is the polite stop and a signal is the backstop, because a tool that ignored - // its liveness handle would otherwise pin a simulator's framebuffer forever. The half second is - // only the grace it gets to write its own last log line. - const stopTool = () => { - if (child.exitCode !== null || child.signalCode !== null) return; - child.stdin.end(); - const deadline = setTimeout(() => { - if (child.exitCode === null && child.signalCode === null) child.kill("SIGKILL"); - }, 500); - deadline.unref(); - }; - - // why: `onEnd` unregisters the capture from the registry of running ones, and it has to fire for a - // tool that dies on its own as much as for a stop, or a lease would keep looking like it had a - // picture running. - const notifyEnd = once(() => options.onEnd?.()); - child.on("close", notifyEnd); - const close = () => { - stopTool(); - notifyEnd(); - }; - - // why: a child that dies is reported into the stream, so a viewer watching a simulator that was - // shut down mid-picture sees the stream fail rather than a frame that froze in place. - const died = new Promise((_, reject) => { - child.on("error", (error) => reject(withLog(error.message, tail))); - child.on("close", (code, signal) => - reject( - withLog( - signal !== null - ? `the capture tool was killed by ${signal}` - : `the capture tool exited with code ${String(code)}`, - tail, - ), - ), - ); - }); - died.catch(() => undefined); - // why: a process the OS refused to spawn never fires `close`, so that one path reports by hand. - child.once("error", notifyEnd); - - const reader = Readable.toWeb(child.stdout).getReader() as ReadableStreamDefaultReader; - const scanner = new MetaFrameScanner(); - const buffered: Uint8Array[] = []; - - // why: a timeout races every read, before and after the first frame, because "started and then went - // quiet" is something a capture program can do at any point. A wait bounded only before the first - // frame would still leave a viewer watching a frozen picture forever once the tool falls silent -- - // and a picture nobody can tell from a live one is the failure this module exists to avoid. - const silenceMs = options.silenceMs ?? DEFAULT_SILENCE_MS; - let silence: ReturnType | undefined; - const readFrame = (): Promise> => - new Promise>((resolve, reject) => { - const settle = (settleWith: () => void) => { - clearTimeout(silence); - settleWith(); - }; - silence = setTimeout( - () => - reject(withLog(`the capture tool wrote nothing for ${String(silenceMs)}ms`, tail)), - silenceMs, - ); - silence.unref(); - reader.read().then( - (step) => settle(() => resolve(step)), - (error) => settle(() => reject(error)), - ); - }); - - try { - for (;;) { - const step = await Promise.race([readFrame(), died]); - if (step.done) { - throw withLog("the capture tool ended before it wrote a frame", tail); - } - buffered.push(step.value); - const found = scanner.push(step.value); - if (found !== undefined) { - // why: everything read so far goes back on the front in order. The meta frame is what the - // response head was answered from but a decoder needs it in the body too, and the bytes that - // arrived in the same read as it are the first frames of the stream -- re-emitting the whole - // buffer is what keeps any of them from being dropped, and nothing here re-frames. - return { - body: followedBy(concat(buffered), readFrame, reader, close), - close, - meta: found.meta, - }; - } - } - } catch (error) { - clearTimeout(silence); - close(); - void reader.cancel().catch(() => undefined); - throw error; - } -} - -function followedBy( - prefix: Uint8Array, - read: () => Promise>, - source: ReadableStreamDefaultReader, - onClose: () => void, -): ReadableStream { - let handed = false; - return new ReadableStream({ - async pull(controller) { - if (!handed) { - handed = true; - controller.enqueue(prefix); - return; - } - try { - const step = await read(); - if (step.done) { - onClose(); - controller.close(); - } else controller.enqueue(step.value); - } catch (error) { - // why: a read that failed is the capture's end as much as a clean close is -- a body that - // errors while a registry still names its capture is a lease that looks like it is still - // being filmed, and the tool behind it has no idea its viewer is gone. - onClose(); - controller.error(error); - } - }, - // why: a body dropped without its request being aborted still ends the capture -- a stream nobody - // is reading has no claim on a simulator's framebuffer. - cancel: (reason) => { - onClose(); - return source.cancel(reason); - }, - }); -} - -function concat(chunks: readonly Uint8Array[]): Uint8Array { - const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0); - const out = new Uint8Array(total); - let offset = 0; - for (const chunk of chunks) { - out.set(chunk, offset); - offset += chunk.length; - } - return out; -} - -function once(fn: () => void): () => void { - let done = false; - return () => { - if (done) return; - done = true; - fn(); - }; -} - -function withLog(detail: string, tail: readonly string[]): Error { - return new Error( - `The screen capture produced no stream (${detail})` + - (tail.length === 0 ? "" : `: ${tail[tail.length - 1]}`), - ); -} diff --git a/src/stream/captures.test.ts b/src/stream/captures.test.ts index bcb641b0..0ce5bdd8 100644 --- a/src/stream/captures.test.ts +++ b/src/stream/captures.test.ts @@ -1,107 +1,81 @@ import { describe, expect, it } from "vitest"; -import { createCaptureRegistry, createStreamLeaseEnd, type RunningCapture } from "./captures.js"; +import { createCaptureRegistry, createStreamLeaseEnd } from "./captures.js"; -// why: a capture that remembers being stopped, so a test can say that this capture stopped and not -// another -- a registry that only cleared its map would pass a test that counted the map. -function spy(): RunningCapture & { stops(): number } { - let stopped = 0; - return { - close: () => { - stopped += 1; - }, - stops: () => stopped, - }; +function ended(leaseId: string) { + return { leaseId } as Parameters["leaseEnded"]>[0]; } -describe("createCaptureRegistry", () => { - it("stops the captures of one lease and none of another", () => { - const registry = createCaptureRegistry(); - const first = spy(); - const second = spy(); - registry.track("lse_a", first); - registry.track("lse_b", second); - - expect(registry.stop("lse_a")).toBe(1); - expect(first.stops()).toBe(1); - expect(second.stops()).toBe(0); - expect(registry.leases()).toEqual(["lse_b"]); +describe("the capture registry", () => { + it("a lease that ends while its capture is starting aborts the slot and closes the capture once it attaches", async () => { + const captures = createCaptureRegistry(); + const slot = captures.begin("lse_1"); + await captures.stop("lse_1"); + expect(slot.ended.aborted).toBe(true); + let closes = 0; + slot.attach(async () => { + closes += 1; + }); + await Promise.resolve(); + expect(closes).toBe(1); }); - it("stops every capture of a lease, because one device can be watched from more than one page", () => { - const registry = createCaptureRegistry(); - const viewers = [spy(), spy(), spy()]; - for (const viewer of viewers) registry.track("lse_a", viewer); - - expect(registry.stop("lse_a")).toBe(3); - expect(viewers.map((viewer) => viewer.stops())).toEqual([1, 1, 1]); - expect(registry.leases()).toEqual([]); + it("stops every capture of the lease that ended and leaves another lease's running", async () => { + const captures = createCaptureRegistry(); + const closed: string[] = []; + captures.begin("lse_1").attach(async () => void closed.push("a")); + captures.begin("lse_1").attach(async () => void closed.push("b")); + captures.begin("lse_2").attach(async () => void closed.push("c")); + expect(await captures.stop("lse_1")).toBe(2); + expect(closed).toEqual(["a", "b"]); + expect(captures.leases()).toEqual(["lse_2"]); }); - it("forgets a capture closed through its own handle, so a stop does not close it twice", () => { - const registry = createCaptureRegistry(); - const capture = spy(); - const handle = registry.track("lse_a", capture); - - handle.close(); - expect(registry.leases()).toEqual([]); - expect(registry.stop("lse_a")).toBe(0); - // why: one stop is the tool seeing its stdin close once; a second close here is the registry still - // naming a capture it should have let go of, aimed at a process that may be somebody else's by now. - expect(capture.stops()).toBe(1); + it("closes a capture once however many doors close it", async () => { + const captures = createCaptureRegistry(); + let closes = 0; + const slot = captures.begin("lse_1"); + slot.attach(async () => { + closes += 1; + }); + await slot.release(); + await slot.release(); + await captures.stop("lse_1"); + expect(closes).toBe(1); }); - it("keeps the lease's other captures when one viewer goes away", () => { - const registry = createCaptureRegistry(); - const staying = spy(); - const leaving = spy(); - const handle = registry.track("lse_a", leaving); - registry.track("lse_a", staying); - - handle.close(); - expect(leaving.stops()).toBe(1); - expect(staying.stops()).toBe(0); - expect(registry.leases()).toEqual(["lse_a"]); - }); - - it("stops nothing for a lease it never heard of", () => { - const registry = createCaptureRegistry(); - registry.track("lse_a", spy()); - expect(registry.stop("lse_nope")).toBe(0); - expect(registry.leases()).toEqual(["lse_a"]); + it("a released capture no longer counts as its lease's", async () => { + const captures = createCaptureRegistry(); + await captures.begin("lse_1").release(); + expect(captures.leases()).toEqual([]); + expect(await captures.stop("lse_1")).toBe(0); }); }); -describe("createStreamLeaseEnd", () => { - it("ends a picture the moment the lease ends, with the viewer still connected", () => { - const registry = createCaptureRegistry(); - const capture = spy(); - registry.track("lse_a", capture); - const leaseEnd = createStreamLeaseEnd(registry); - - // why: this is the case an aborted request cannot cover -- a lease ended somewhere else (released by - // a CLI, expired, its device lost) while a browser still holds the stream open. Without this the - // viewer keeps watching a simulator this worker has already handed back to the pool, and the next - // lease of that device is someone else's screen. - void leaseEnd.leaseEnded(ended("lse_a"), new AbortController().signal); - expect(capture.stops()).toBe(1); - expect(registry.leases()).toEqual([]); +describe("the stream's lease-end participant", () => { + it("returns only once the ended lease's captures have closed", async () => { + const captures = createCaptureRegistry(); + let finish!: () => void; + captures.begin("lse_1").attach(() => new Promise((resolve) => (finish = resolve))); + let returned = false; + const done = createStreamLeaseEnd(captures) + .leaseEnded(ended("lse_1"), new AbortController().signal) + .then(() => { + returned = true; + }); + await Promise.resolve(); + expect(returned).toBe(false); + finish(); + await done; + expect(returned).toBe(true); }); - it("is idle for a lease that never streamed anything", async () => { - const registry = createCaptureRegistry(); - await expect( - createStreamLeaseEnd(registry).leaseEnded(ended("lse_a"), new AbortController().signal), - ).resolves.toBeUndefined(); + it("stops waiting when leasing gives up on it", async () => { + const captures = createCaptureRegistry(); + captures.begin("lse_1").attach(() => new Promise(() => undefined)); + const abort = new AbortController(); + const done = createStreamLeaseEnd(captures).leaseEnded(ended("lse_1"), abort.signal); + abort.abort(); + await expect(done).resolves.toBeUndefined(); }); }); - -function ended(leaseId: string) { - return { - deviceId: "dev_1", - leaseId, - ownerId: "agent-1", - platform: "ios" as const, - reason: "explicit" as const, - }; -} diff --git a/src/stream/captures.ts b/src/stream/captures.ts index 96637e44..5a2abdbb 100644 --- a/src/stream/captures.ts +++ b/src/stream/captures.ts @@ -1,61 +1,96 @@ // why: a capture belongs to a lease, not to a connection -- the lease is the thing that says this -// machine still owns that simulator, and the types cannot say so. A viewer going away already stops a -// capture through its own request, which covers the ordinary case; this covers the case it cannot, a -// lease that ends somewhere else (released elsewhere, expired, the device lost) while somebody is -// still watching. Without it a stream pins a simulator's framebuffer on a device this worker has -// already handed back. +// machine still owns that device. A viewer going away stops a capture through its own request; this +// covers what that cannot, a lease that ends somewhere else (released elsewhere, expired, the device +// lost) while somebody is still watching, or while a capture is still starting. import type { LeaseEndParticipant } from "../leasing/index.js"; -export interface RunningCapture { - close(): void; +export interface CaptureSlot { + // why: aborted when the lease ends, so a capture still being opened learns it has no lease to + // film before it hands a picture out. + readonly ended: AbortSignal; + attach(close: () => Promise): void; + release(): Promise; } export interface CaptureRegistry { - track(leaseId: string, capture: RunningCapture): RunningCapture; - stop(leaseId: string): number; + begin(leaseId: string): CaptureSlot; + stop(leaseId: string): Promise; leases(): string[]; } +interface Slot { + readonly controller: AbortController; + close: (() => Promise) | undefined; + closing: Promise | undefined; +} + export function createCaptureRegistry(): CaptureRegistry { - const running = new Map>(); + const running = new Map>(); + + const closeSlot = (slot: Slot): Promise => { + // why: a slot stopped before its capture attached has nothing to close yet; `attach` closes it + // then, so the latch is set only once there is a close to run. + if (slot.close === undefined) return Promise.resolve(); + slot.closing ??= slot.close(); + return slot.closing; + }; + + const forget = (leaseId: string, slot: Slot) => { + const forLease = running.get(leaseId); + forLease?.delete(slot); + if (forLease?.size === 0) running.delete(leaseId); + }; return { - leases: () => [...running.keys()], - stop: (leaseId) => { - const captures = running.get(leaseId); - if (captures === undefined) return 0; - running.delete(leaseId); - for (const capture of captures) capture.close(); - return captures.size; - }, - track: (leaseId, capture) => { - const forLease = running.get(leaseId) ?? new Set(); - forLease.add(capture); + // why: registered before the capture starts, not after its first frame -- a lease that ends in + // between must find something to stop, or the capture would come up filming a device that is + // already on its way to the next holder. + begin: (leaseId) => { + const slot: Slot = { + close: undefined, + closing: undefined, + controller: new AbortController(), + }; + const forLease = running.get(leaseId) ?? new Set(); + forLease.add(slot); running.set(leaseId, forLease); - // why: `released` is the handle's own latch, not the capture's -- one capture can be closed - // through three doors (a stop, an aborted viewer, this registry) and the map must be corrected - // once, or a second close would remove another lease's capture that had reused the same set. - let released = false; - const close = () => { - if (!released) { - released = true; - forLease.delete(capture); - if (forLease.size === 0) running.delete(leaseId); - } - capture.close(); + return { + attach: (close) => { + slot.close = close; + if (slot.controller.signal.aborted) void closeSlot(slot); + }, + ended: slot.controller.signal, + release: async () => { + forget(leaseId, slot); + await closeSlot(slot); + }, }; - return { close }; + }, + leases: () => [...running.keys()], + stop: async (leaseId) => { + const forLease = running.get(leaseId); + if (forLease === undefined) return 0; + running.delete(leaseId); + for (const slot of forLease) slot.controller.abort(); + await Promise.allSettled([...forLease].map(closeSlot)); + return forLease.size; }, }; } export function createStreamLeaseEnd(captures: CaptureRegistry): LeaseEndParticipant { return { - // why: resolves without waiting for the capture process to be reaped. The stop is a closed pipe, - // not an operation whose outcome leasing needs, and a lease's reclaim must not queue behind a - // video tool's exit; the viewer learns the picture ended when the tool's stdout reaches its end. - leaseEnded: async (ended) => { - captures.stop(ended.leaseId); + // why: waits for each capture's close, which a driver bounds, so a reclaim does not wipe or + // reboot a device while its capture is still being torn down on it; leasing's own signal ends + // the wait if leasing gives up first. + leaseEnded: async (ended, signal) => { + await Promise.race([ + captures.stop(ended.leaseId), + new Promise((resolve) => { + if (signal.aborted) resolve(); + else signal.addEventListener("abort", () => resolve(), { once: true }); + }), + ]); }, }; } diff --git a/src/stream/create.test.ts b/src/stream/create.test.ts new file mode 100644 index 00000000..87d0ed4b --- /dev/null +++ b/src/stream/create.test.ts @@ -0,0 +1,77 @@ +import { describe, expect, it } from "vitest"; + +import type { ScreenAvailability } from "../core/index.js"; +import { FakeClock, NoopLogger } from "../ports/index.js"; +import { createWorkerStream } from "./create.js"; +import { ScriptedFeed, TestRegistry } from "./test-fixtures.js"; + +function worker(availability: readonly ScreenAvailability[] = []) { + const registry = new TestRegistry( + [{ deviceId: "dev_1", id: "lse_1", ownerId: "tok_agent", requesterId: "gw:inst:agent-1" }], + [{ id: "dev_1", platform: "ios" }], + ); + const feeds: ScriptedFeed[] = []; + const stream = createWorkerStream({ + clock: new FakeClock(), + logger: new NoopLogger(), + registry, + screens: { + openScreen: async () => { + const feed = new ScriptedFeed(); + feed.meta(); + feeds.push(feed); + return feed; + }, + screenAvailability: async () => availability, + }, + settings: { bitrateKbps: 1500, fps: 30, maxDimension: 720 }, + }); + return { feeds, stream }; +} + +describe("createWorkerStream", () => { + it("reports the stream available, naming the platforms that cannot capture and why", async () => { + const { stream } = worker([ + { platform: "android" }, + { platform: "ios", reason: "No capture program at /x" }, + ]); + expect(stream.capabilitySource.name).toBe("stream"); + expect(await stream.capabilitySource.read()).toEqual({ + reason: "Available on android; ios: No capture program at /x", + state: "available", + }); + }); + + it("reports the stream unavailable when no driver on the host can capture", async () => { + const { stream } = worker([]); + expect(await stream.capabilitySource.read()).toEqual({ + reason: "No driver on this host can capture a screen", + state: "unavailable", + }); + }); + + it("serves a relayed request for the requester the gateway names, checked against this worker's registry", async () => { + const { stream } = worker(); + const request = () => new Request("http://simlock-worker/stream/leases/lse_1"); + const served = await stream.relayTarget(request(), "gw:inst:agent-1", "lse_1"); + expect(served.status).toBe(200); + await served.body?.cancel(); + const refused = await stream.relayTarget(request(), "gw:inst:agent-2", "lse_1"); + expect(refused.status).toBe(404); + }); + + it("stops a relayed capture when its lease ends", async () => { + const { feeds, stream } = worker(); + const served = await stream.relayTarget( + new Request("http://simlock-worker/stream/leases/lse_1"), + "gw:inst:agent-1", + "lse_1", + ); + expect(served.status).toBe(200); + await stream.leaseEnd.leaseEnded( + { leaseId: "lse_1" } as Parameters[0], + new AbortController().signal, + ); + expect(feeds[0]?.closes).toBe(1); + }); +}); diff --git a/src/stream/create.ts b/src/stream/create.ts new file mode 100644 index 00000000..230ff3ab --- /dev/null +++ b/src/stream/create.ts @@ -0,0 +1,103 @@ +import type { Platform, ScreenAvailability, ScreenSettings } from "../core/index.js"; +import type { LeaseEndParticipant } from "../leasing/index.js"; +import type { Clock, Logger } from "../ports/index.js"; +import { createCaptureRegistry, createStreamLeaseEnd } from "./captures.js"; +import { DEFAULT_SILENCE_MS, DEFAULT_START_MS } from "./feed.js"; +import { createGatewayStreamLink, type FleetPort } from "./gateway-link.js"; +import { + createStreamMount, + createStreamRelayTarget, + STREAM_TARGET, + type StreamMount, + type StreamRelayTarget, +} from "./mounts.js"; +import type { StreamServiceOptions } from "./service.js"; +import { createWorkerStreamLink, type ScreenOpener, type StreamRegistry } from "./worker-link.js"; + +export interface WorkerStreamOptions { + readonly registry: StreamRegistry; + readonly screens: ScreenOpener & { + screenAvailability(): Promise; + }; + readonly clock: Clock; + readonly logger: Logger; + readonly settings: ScreenSettings; +} + +export interface StreamCapabilitySource { + readonly name: string; + read(): Promise<{ readonly state: "available" | "unavailable"; readonly reason?: string }>; +} + +export interface WorkerStream { + readonly mount: StreamMount; + readonly relayTarget: StreamRelayTarget; + readonly relayTargetName: string; + readonly leaseEnd: LeaseEndParticipant; + readonly capabilitySource: StreamCapabilitySource; +} + +// why: the mount, the relay target and the lease-end participant sit on one link and one registry, so +// a stream asked for on this host and one relayed by a gateway go through the same lease check, and a +// lease that ends has one place to stop its captures whichever door the picture came in by. +export function createWorkerStream(options: WorkerStreamOptions): WorkerStream { + const captures = createCaptureRegistry(); + const service: StreamServiceOptions = { + link: createWorkerStreamLink({ + bounds: { clock: options.clock, silenceMs: DEFAULT_SILENCE_MS, startMs: DEFAULT_START_MS }, + captures, + registry: options.registry, + screens: options.screens, + settings: options.settings, + }), + logger: options.logger, + }; + return { + capabilitySource: { + name: STREAM_TARGET, + read: async () => describeAvailability(await options.screens.screenAvailability()), + }, + leaseEnd: createStreamLeaseEnd(captures), + mount: createStreamMount(service), + relayTarget: createStreamRelayTarget(service), + relayTargetName: STREAM_TARGET, + }; +} + +export interface GatewayStreamOptions { + readonly fleet: FleetPort; + readonly logger: Logger; +} + +export function createGatewayStream(options: GatewayStreamOptions): { + readonly mount: StreamMount; + readonly relayTargetName: string; +} { + return { + mount: createStreamMount({ + link: createGatewayStreamLink({ fleet: options.fleet }), + logger: options.logger, + }), + relayTargetName: STREAM_TARGET, + }; +} + +function describeAvailability(platforms: readonly ScreenAvailability[]): { + readonly state: "available" | "unavailable"; + readonly reason?: string; +} { + const available: Platform[] = platforms + .filter((entry) => entry.reason === undefined) + .map((entry) => entry.platform); + const missing = platforms.filter((entry) => entry.reason !== undefined); + const why = missing.map((entry) => `${entry.platform}: ${entry.reason ?? ""}`).join("; "); + if (available.length === 0) { + return { + reason: why === "" ? "No driver on this host can capture a screen" : why, + state: "unavailable", + }; + } + return why === "" + ? { state: "available" } + : { reason: `Available on ${available.join(", ")}; ${why}`, state: "available" }; +} diff --git a/src/stream/feed.test.ts b/src/stream/feed.test.ts new file mode 100644 index 00000000..8365240d --- /dev/null +++ b/src/stream/feed.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from "vitest"; + +import { encodeScreenMeta } from "../core/index.js"; +import { FakeClock } from "../ports/index.js"; +import { readScreenFeed } from "./feed.js"; +import { ScriptedFeed } from "./test-fixtures.js"; + +const BOUNDS = { silenceMs: 1_000, startMs: 5_000 }; + +async function flush(): Promise { + for (let turn = 0; turn < 10; turn += 1) await Promise.resolve(); +} + +describe("readScreenFeed", () => { + it("answers with the meta frame and replays it, and the bytes that came with it, at the head of the body", async () => { + const feed = new ScriptedFeed(); + const meta = encodeScreenMeta({ codec: "h264", fps: 30, height: 720, width: 330 }); + feed.write(Uint8Array.from([...meta, 9, 9])); + const stream = await readScreenFeed(feed, { clock: new FakeClock(), ...BOUNDS }); + const reader = stream.body.getReader(); + expect([...((await reader.read()).value ?? [])]).toEqual([...meta, 9, 9]); + expect(stream.meta.width).toBe(330); + }); + + it("fails before the first frame only at the start bound, not the shorter silence bound", async () => { + const clock = new FakeClock(); + const feed = new ScriptedFeed(); + const outcome = readScreenFeed(feed, { clock, ...BOUNDS }).then( + () => "answered", + (error: unknown) => (error as Error).message, + ); + clock.advance(BOUNDS.silenceMs); + await flush(); + feed.meta(); + expect(await outcome).toBe("answered"); + }); + + it("refuses a capture that paints nothing within the start bound, and closes it", async () => { + const clock = new FakeClock(); + const feed = new ScriptedFeed(); + const outcome = readScreenFeed(feed, { clock, ...BOUNDS }); + clock.advance(BOUNDS.startMs); + await expect(outcome).rejects.toThrow( + /wrote nothing for 5000ms while waiting for its first frame/u, + ); + expect(feed.closes).toBe(1); + }); + + it("refuses a capture that ends before it writes a frame", async () => { + const feed = new ScriptedFeed(); + const outcome = readScreenFeed(feed, { clock: new FakeClock(), ...BOUNDS }); + feed.end(); + await expect(outcome).rejects.toThrow(/ended before it wrote a frame/u); + }); + + it("errors the body, and closes the capture, when it goes quiet mid-picture past the silence bound", async () => { + const clock = new FakeClock(); + const feed = new ScriptedFeed(); + feed.meta(); + const stream = await readScreenFeed(feed, { clock, ...BOUNDS }); + const reader = stream.body.getReader(); + await reader.read(); + const next = reader.read(); + await flush(); + clock.advance(BOUNDS.silenceMs); + await expect(next).rejects.toThrow( + /wrote nothing for 1000ms while waiting for the next frame/u, + ); + expect(feed.closes).toBe(1); + }); + + it("keeps a picture that keeps arriving within the silence bound", async () => { + const clock = new FakeClock(); + const feed = new ScriptedFeed(); + feed.meta(); + const stream = await readScreenFeed(feed, { clock, ...BOUNDS }); + const reader = stream.body.getReader(); + await reader.read(); + for (let frame = 0; frame < 3; frame += 1) { + const next = reader.read(); + await flush(); + clock.advance(BOUNDS.silenceMs - 1); + feed.keyframe(); + expect((await next).done).toBe(false); + } + expect(feed.closes).toBe(0); + }); + + it("closes the capture when the viewer cancels the body", async () => { + const feed = new ScriptedFeed(); + feed.meta(); + const stream = await readScreenFeed(feed, { clock: new FakeClock(), ...BOUNDS }); + await stream.body.cancel(); + expect(feed.closes).toBe(1); + }); +}); diff --git a/src/stream/feed.ts b/src/stream/feed.ts new file mode 100644 index 00000000..6d215050 --- /dev/null +++ b/src/stream/feed.ts @@ -0,0 +1,134 @@ +import type { ScreenFeed, ScreenMeta } from "../core/index.js"; +import type { Clock, TimerHandle } from "../ports/index.js"; +import { MetaFrameScanner } from "./framing.js"; + +export interface CapturedStream { + readonly meta: ScreenMeta; + readonly body: ReadableStream; + close(): Promise; +} + +export interface FeedBounds { + readonly clock: Clock; + readonly startMs: number; + readonly silenceMs: number; +} + +// why: a cold simulator or an emulator whose encoder is warming up can take seconds to paint its +// first frame, and a capture that never paints must still end; generous, but a bound. +export const DEFAULT_START_MS = 30_000; +// why: a screen that does not change still yields frames -- the iOS capture repaints at an idle floor +// and scrcpy repeats its last frame -- so this long without a byte is a capture that died without +// saying so, and a viewer must not be left looking at a frozen picture it cannot tell from a live one. +export const DEFAULT_SILENCE_MS = 30_000; + +export async function readScreenFeed( + feed: ScreenFeed, + bounds: FeedBounds, +): Promise { + const reader = feed.body.getReader(); + const scanner = new MetaFrameScanner(); + const buffered: Uint8Array[] = []; + const read = (ms: number, what: string) => readWithin(reader, bounds.clock, ms, what); + + try { + for (;;) { + const step = await read(bounds.startMs, "its first frame"); + if (step.done) throw new Error("The screen capture ended before it wrote a frame"); + buffered.push(step.value); + const found = scanner.push(step.value); + if (found !== undefined) { + // why: everything read so far goes back on the front in order. The meta frame is what the + // response head was answered from but a decoder needs it in the body too, and bytes that + // arrived in the same read as it are the stream's first frames. + return { + body: followedBy( + concat(buffered), + () => read(bounds.silenceMs, "the next frame"), + reader, + feed, + ), + close: () => feed.close(), + meta: found.meta, + }; + } + } + } catch (error) { + void reader.cancel().catch(() => undefined); + await feed.close(); + throw error; + } +} + +function readWithin( + reader: ReadableStreamDefaultReader, + clock: Clock, + ms: number, + what: string, +): Promise> { + return new Promise((resolve, reject) => { + let timer: TimerHandle | undefined = clock.setTimer(ms, () => { + timer = undefined; + reject( + new Error(`The screen capture wrote nothing for ${String(ms)}ms while waiting for ${what}`), + ); + }); + const settle = (outcome: () => void) => { + if (timer === undefined) return; + clock.cancel(timer); + timer = undefined; + outcome(); + }; + reader.read().then( + (step) => settle(() => resolve(step)), + (error: unknown) => + settle(() => reject(error instanceof Error ? error : new Error(String(error)))), + ); + }); +} + +function followedBy( + prefix: Uint8Array, + read: () => Promise>, + source: ReadableStreamDefaultReader, + feed: ScreenFeed, +): ReadableStream { + let handed = false; + return new ReadableStream({ + cancel: async (reason) => { + await source.cancel(reason).catch(() => undefined); + await feed.close(); + }, + async pull(controller) { + if (!handed) { + handed = true; + controller.enqueue(prefix); + return; + } + try { + const step = await read(); + if (step.done) { + await feed.close(); + controller.close(); + } else controller.enqueue(step.value); + } catch (error) { + // why: a read that failed is the capture's end as much as a clean close is, and the device + // must not go on being filmed for a viewer whose stream already errored. + void source.cancel().catch(() => undefined); + await feed.close(); + controller.error(error); + } + }, + }); +} + +function concat(chunks: readonly Uint8Array[]): Uint8Array { + const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const out = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + out.set(chunk, offset); + offset += chunk.length; + } + return out; +} diff --git a/src/stream/framing.test.ts b/src/stream/framing.test.ts index f61b7681..5a21f5a5 100644 --- a/src/stream/framing.test.ts +++ b/src/stream/framing.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; -import { CAPTURE_TAG, MAX_FRAME_BYTES, MetaFrameScanner } from "./framing.js"; +import { SCREEN_FRAME_TAG as CAPTURE_TAG } from "../core/index.js"; +import { checkedMeta, MAX_FRAME_BYTES, MetaFrameScanner } from "./framing.js"; const META = { codec: "h264", deviceSet: "/set", fps: 30, height: 720, udid: "UDID", width: 330 }; @@ -67,9 +68,28 @@ describe("MetaFrameScanner", () => { ); }); - it("reads an mjpeg meta as mjpeg and defaults a size it was not told to zero", () => { + it("reads an mjpeg meta as mjpeg", () => { const scanner = new MetaFrameScanner(); - const found = scanner.push(metaFrame({ codec: "mjpeg" })); - expect(found?.meta).toEqual({ codec: "mjpeg", fps: 0, height: 0, width: 0 }); + const found = scanner.push(metaFrame({ codec: "mjpeg", fps: 5, height: 720, width: 330 })); + expect(found?.meta).toEqual({ codec: "mjpeg", fps: 5, height: 720, width: 330 }); + }); + + it("refuses a meta frame that leaves out the size rather than reporting a stream with no size", () => { + expect(() => new MetaFrameScanner().push(metaFrame({ codec: "h264", fps: 30 }))).toThrow( + /is not a size a screen has/u, + ); + }); +}); + +describe("checkedMeta", () => { + it("refuses a codec the stream cannot carry instead of calling it h264", () => { + expect(() => checkedMeta("vp9", 330, 720, 30)).toThrow(/codec this stream cannot carry: vp9/u); + }); + + it("refuses a size no screen has", () => { + expect(() => checkedMeta("h264", 330, 1_000_000, 30)).toThrow(/height/u); + expect(() => checkedMeta("h264", 0, 720, 30)).toThrow(/width/u); + expect(() => checkedMeta("h264", 330.5, 720, 30)).toThrow(/width/u); + expect(() => checkedMeta("h264", 330, 720, -1)).toThrow(/fps/u); }); }); diff --git a/src/stream/framing.ts b/src/stream/framing.ts index e6b856f6..9ad70c93 100644 --- a/src/stream/framing.ts +++ b/src/stream/framing.ts @@ -1,24 +1,14 @@ -export const CAPTURE_TAG = { - description: 0x01, - keyframe: 0x02, - delta: 0x03, - seed: 0x04, - meta: 0x05, -} as const; - -const HEADER_BYTES = 5; +import { SCREEN_FRAME_HEADER_BYTES, SCREEN_FRAME_TAG, type ScreenMeta } from "../core/index.js"; export const MAX_FRAME_BYTES = 8 * 1024 * 1024; -export interface CaptureMeta { - readonly codec: "h264" | "mjpeg"; - readonly width: number; - readonly height: number; - readonly fps: number; -} +// why: a size is a claim from a program or a worker, and both end up in a browser's decoder +// configuration and a response header; anything past this is not a screen any device has. +const MAX_SIDE = 16_384; +const MAX_FPS = 240; export interface MetaFrame { - readonly meta: CaptureMeta; + readonly meta: ScreenMeta; readonly bytes: Uint8Array; } @@ -27,46 +17,64 @@ export class MetaFrameScanner { push(chunk: Uint8Array): MetaFrame | undefined { if (this.#buffer.length > MAX_FRAME_BYTES) { - throw new Error("The screen capture tool wrote no meta frame before an oversized frame"); + throw new Error("The screen capture wrote no meta frame before an oversized frame"); } const merged = new Uint8Array(this.#buffer.length + chunk.length); merged.set(this.#buffer, 0); merged.set(chunk, this.#buffer.length); this.#buffer = merged; - for (;;) { - if (this.#buffer.length < HEADER_BYTES) return undefined; - const length = new DataView(this.#buffer.buffer, this.#buffer.byteOffset).getUint32(0); - const total = HEADER_BYTES + length - 1; - if (length < 1 || total > MAX_FRAME_BYTES) { - throw new Error("The screen capture tool's output is not a frame envelope"); - } - if (this.#buffer.length < total) return undefined; - if (this.#buffer[4] === CAPTURE_TAG.meta) { - const meta = parseMeta(this.#buffer.subarray(HEADER_BYTES, total)); - if (meta === undefined) { - throw new Error("The screen capture tool's meta frame is not JSON"); - } - return { bytes: this.#buffer.slice(0, total), meta }; - } - // why: a frame ahead of the meta frame means a tool that broke the one promise the envelope - // makes -- write the meta first -- so the stream is refused rather than decoded blind. - throw new Error("The screen capture tool's stream does not begin with a meta frame"); + if (this.#buffer.length < SCREEN_FRAME_HEADER_BYTES) return undefined; + const length = new DataView(this.#buffer.buffer, this.#buffer.byteOffset).getUint32(0); + const total = SCREEN_FRAME_HEADER_BYTES + length - 1; + if (length < 1 || total > MAX_FRAME_BYTES) { + throw new Error("The screen capture's output is not a frame envelope"); + } + // why: a frame ahead of the meta frame means a capture that broke the one promise the envelope + // makes -- meta first -- so the stream is refused rather than decoded blind. + if (this.#buffer[4] !== SCREEN_FRAME_TAG.meta) { + throw new Error("The screen capture's stream does not begin with a meta frame"); } + if (this.#buffer.length < total) return undefined; + const meta = readScreenMeta(this.#buffer.subarray(SCREEN_FRAME_HEADER_BYTES, total)); + return { bytes: this.#buffer.slice(0, total), meta }; } } -function parseMeta(payload: Uint8Array): CaptureMeta | undefined { +export function readScreenMeta(payload: Uint8Array): ScreenMeta { + let parsed: unknown; try { - const parsed = JSON.parse(new TextDecoder().decode(payload)) as Record; - const number = (key: string) => (typeof parsed[key] === "number" ? parsed[key] : 0); - return { - codec: parsed["codec"] === "mjpeg" ? "mjpeg" : "h264", - fps: number("fps"), - height: number("height"), - width: number("width"), - }; + parsed = JSON.parse(new TextDecoder().decode(payload)); } catch { - return undefined; + throw new Error("The screen capture's meta frame is not JSON"); + } + const fields = + typeof parsed === "object" && parsed !== null ? (parsed as Record) : {}; + return checkedMeta(fields["codec"], fields["width"], fields["height"], fields["fps"]); +} + +// why: one check for a meta frame read from a capture and for the headers a gateway reads from a +// worker, so the two doors cannot disagree about what a picture may claim to be. +export function checkedMeta( + codec: unknown, + width: unknown, + height: unknown, + fps: unknown, +): ScreenMeta { + if (codec !== "h264" && codec !== "mjpeg") { + throw new Error(`The screen capture names a codec this stream cannot carry: ${String(codec)}`); + } + return { + codec, + fps: bounded(fps, 0, MAX_FPS, "fps"), + height: bounded(height, 1, MAX_SIDE, "height"), + width: bounded(width, 1, MAX_SIDE, "width"), + }; +} + +function bounded(value: unknown, min: number, max: number, name: string): number { + if (typeof value !== "number" || !Number.isInteger(value) || value < min || value > max) { + throw new Error(`The screen capture's ${name} is not a size a screen has: ${String(value)}`); } + return value; } diff --git a/src/stream/gateway-link.test.ts b/src/stream/gateway-link.test.ts new file mode 100644 index 00000000..19d9f04c --- /dev/null +++ b/src/stream/gateway-link.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it } from "vitest"; + +import type { StreamCaller } from "./caller.js"; +import { createGatewayStreamLink, type FleetPort } from "./gateway-link.js"; +import { StreamRefusalError } from "./refusal.js"; + +const AGENT: StreamCaller = { principal: "tok_agent", role: "agent" }; +const STRANGER: StreamCaller = { principal: "tok_other", role: "agent" }; + +interface Relayed { + readonly workerId: string; + readonly url: string; + readonly target: string; + readonly tenant: string; + readonly leaseId: string; +} + +function fleet(answer: () => Response): { port: FleetPort; relayed: Relayed[] } { + const relayed: Relayed[] = []; + return { + port: { + holder: (leaseId) => + leaseId === "w1.lse_9" + ? { ownerId: "tok_agent", requesterId: "agent-1", workerId: "w1", workerLeaseId: "lse_9" } + : undefined, + relay: async (workerId, call) => { + relayed.push({ + leaseId: call.leaseId, + target: call.target, + tenant: call.tenant, + url: call.request.url, + workerId, + }); + return answer(); + }, + requesterPrefix: "gw:inst:", + }, + relayed, + }; +} + +function streamHeaders(overrides: Record = {}): Record { + return { + "simlock-stream-codec": "h264", + "simlock-stream-fps": "30", + "simlock-stream-height": "720", + "simlock-stream-width": "330", + ...overrides, + }; +} + +async function refusalOf(served: Promise): Promise { + const outcome = await served.then( + () => undefined, + (error: unknown) => error, + ); + expect(outcome, "the stream was expected to be refused").toBeInstanceOf(StreamRefusalError); + return outcome as StreamRefusalError; +} + +const signal = () => new AbortController().signal; + +describe("the gateway's stream link", () => { + it("relays to the worker holding the lease, by the worker's own lease id, as the lease's namespaced requester", async () => { + const { port, relayed } = fleet(() => new Response("bytes", { headers: streamHeaders() })); + const stream = await createGatewayStreamLink({ fleet: port }).serve( + "w1.lse_9", + AGENT, + signal(), + ); + expect(relayed).toEqual([ + { + leaseId: "lse_9", + target: "stream", + tenant: "gw:inst:agent-1", + url: "http://simlock-worker/stream/leases/lse_9", + workerId: "w1", + }, + ]); + expect(stream.meta).toEqual({ codec: "h264", fps: 30, height: 720, width: 330 }); + }); + + it("answers a caller who does not own the lease exactly as an unknown lease, relaying nothing", async () => { + const { port, relayed } = fleet(() => new Response("bytes", { headers: streamHeaders() })); + const refusal = await refusalOf( + createGatewayStreamLink({ fleet: port }).serve("w1.lse_9", STRANGER, signal()), + ); + expect([refusal.status, refusal.code]).toEqual([404, "UNKNOWN_LEASE"]); + expect(relayed).toEqual([]); + }); + + it("forwards the worker's own refusal code and status", async () => { + const { port } = fleet(() => + Response.json( + { error: { code: "STREAM_NOT_SUPPORTED", message: "A physical phone cannot" } }, + { status: 415 }, + ), + ); + const refusal = await refusalOf( + createGatewayStreamLink({ fleet: port }).serve("w1.lse_9", AGENT, signal()), + ); + expect([refusal.status, refusal.code, refusal.message]).toEqual([ + 415, + "STREAM_NOT_SUPPORTED", + "A physical phone cannot", + ]); + }); + + it("does not forward a refusal code that is not shaped like one of ours", async () => { + const { port } = fleet(() => + Response.json({ error: { code: "