bashalarmistalt/decimen-optical-transfer

GitHub: bashalarmistalt/decimen-optical-transfer

仅通过屏幕显示动态二维码、摄像头扫描解码的方式,在无网络连接的设备间实现文件传输的浏览器端概念验证。

Stars: 2152 | Forks: 250

# Decimen Optical Transfer:基于 fountain code 的二维码文件传输 仅通过**屏幕和摄像头**即可在两台设备之间发送文件。 一个页面将文件显示为源源不断的动态二维码流;另一台 设备将摄像头对准它并重建文件。**设备之间无需网络连接,无需应用,无需配对,除了摄像头外无需任何权限。** 数据以光的形式传输。 这是从一个更大型的实验中提取的最小概念验证(PoC),原实验通过更密集的帧、 多二维码网格和纠错颜色通道,实现了手机到手机间 **128 KB/s** 的传输速度。此 PoC 仅保留了 核心技术,以适宜的速率传输 512 KB(或 2 MB,可在发送方设置中选择)的图像。

Phone receiving a 2 MB image over light: 129.2 KB/s goodput, decoding the sender's animated QR code

Mid-transfer: a phone pulling a 2 MB image out of the air at 129 KB/s.

## 试用 ``` npm install npm run dev ``` - 在**发送**设备上(理想情况下为笔记本电脑):打开 `https://localhost:5173/send/`,它会立即开始流式传输。将 屏幕亮度调到最高会有所帮助。 - 在**接收**设备上(手机):打开 Vite 打印的 `Network` URL (`https://:5173/receive/`),接受一次证书警告, 点击 **Start camera**,然后将摄像头对准二维码。 - 几秒钟后:显示 *Transfer Complete!* 以及通过 哈希校验的接收图像。 **为什么开发服务器仅支持 https:**接收方使用了 `getUserMedia`, 而浏览器在不安全的来源上会彻底移除该 API:手机通过纯 http 访问你的开发服务器将完全无法使用摄像头(`localhost` 豁免, 但你的手机不是 localhost)。这是 Web 平台的规则,别无选择。因此,开发服务器自带了 自签名证书 (`@vitejs/plugin-basic-ssl`);首次访问时浏览器会发出警告。点击 “Show Details”然后点击“visit this website”(iOS),或者点击“Advanced”然后点击“Proceed” (Android/桌面端),此时页面仍处于安全上下文中,因此摄像头 可以正常工作。Vite 打印的那个看起来很奇怪的 `lvh.me` 主机是一个公共便捷 域名,它解析到 127.0.0.1(同一台机器,没有运行额外的服务)。 拿稳手机,或者最好是把它靠在固定物体上。手持 抖动导致摄像头不断自动对焦是吞吐量的头号杀手。 ## 工作原理 **单向通道问题。** 屏幕到摄像头的链路没有反向通道: 接收方无法请求重传,并且不可避免地会丢失帧 (模糊、刷新跨越、自动对焦)。循环播放帧并抱有侥幸心理是 极其痛苦的:错过一帧,你就得等上一整个周期才能再次看到它。 **Fountain code 彻底解决了这个问题。** 发送方从不直接发送文件的 数据块。每一帧都是数据块的伪随机*子集*的 XOR(异或); 该子集由帧的序列号确定性推导得出, 子集大小则基于鲁棒孤子分布([Luby transform coding](https://en.wikipedia.org/wiki/Luby_transform_code))。接收方 按任意顺序收集**任意** ~K·1.15 个不同的帧,并从中剥离出 文件。丢失的帧只会消耗一点时间,绝不会影响正确性。发送方 和接收方的帧率完全不需要匹配。 **每一帧都是自我描述的。** 一个 20 字节的头部携带了 session id、 序列号、块数/大小、文件长度和一个哈希值。没有 握手过程:接收方可以在传输过程中锁定数据流,并且重启 发送方(新的 session id)会自动重置接收方。 **解码。** Safari 从未提供过 `BarcodeDetector`(WebKit bug 281848), 因此解码使用的是编译为 WASM 的 [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp), 在由 `requestVideoFrameCallback` 驱动的 worker 中运行。繁忙的 worker 意味着会丢帧,而 fountain code 可以轻松吸收这些丢帧。 ## 内置于本 PoC 的来之不易的细节 - **JS 引擎在 `Math.log` 上存在分歧**(它是实现近似值)。 发送方和接收方必须构建位级完全相同的孤子分布,因此 `fountain.ts` 包含了一个由精确定义的 IEEE-754 操作构建的确定性对数。V8 与 JavaScriptCore 的不同步是一种隐蔽且彻底的失败模式。 - **iOS 在摄像头帧率上撒谎。** `frameRate: {ideal: 60}` 会静默地 提供 30 帧;你必须要求 `{exact: 60}`(适用于 1280 宽的捕获) 并提供后备方案。始终要读取 `getSettings()` 的返回值。 - **`requestVideoFrameCallback` 链在其流结束后依然存活**,并在 下一个流上恢复;如果没有代计数器,每次停止/启动都会泄漏一个 僵尸捕获循环。 - **进度条必须跟踪已收集的帧,而不是已解决的块。** LT 剥离会延后其解决级联:块计数的进度在大部分 传输过程中看起来停滞不前,然后瞬间跃升至 100%。 - **二维码的纠错级别设置为最低 (L)。** 帧内 ECC 和 fountain 层解决的是不同的问题(损坏与擦除),但在 这些帧大小下,级别 L 加上帧丢弃是更好的权衡。 ## 调优 两个页面都有一个折叠的 **Settings** 面板。在发送方:payload 大小 (512 KB 或 2 MB)、tx fps、每帧字节数、纠错级别和 显示大小。更改任何内容都会重启流,接收方会 根据新的 session id 自动重置。在接收方:捕获宽度、 捕获 fps 和解码 worker 数量,这些在摄像头启动时应用。 | 设置 | 默认值 | 备注 | |---|---|---| | tx fps | 24 | 每一帧必须至少占据显示屏的 2 个刷新周期 | | bytes / frame | 1465 (QR v27) | 如果接收方仍能解码,更密集的帧会更快;2953 (v40) 在近距离手机对手机传输时有效 | 在完全相同的架构加上更密集的帧、120 fps ProMotion 发送方和 堆叠代码的情况下,父实验测得的传输上限:手持约为 128 KB/s,固定状态约为 186 KB/s。 ## 类似项目 此处的概念是独立得出的。事实证明 有几个人也有过类似的想法,他们的作品都 值得一看: - [mohankumarelec/airgapped-qr-code-transfer](https://github.com/mohankumarelec/airgapped-qr-code-transfer): 基于浏览器的二维码文件传输,带有压缩和顺序分块。 在公开演示此项目后发现的;这是趋同进化 的实际案例。 - [divan/txqr](https://github.com/divan/txqr)(2018 年):动态二维码加上 Go 中的 fountain code,并配有两篇优秀的文章,阐述了为什么 fountain coding 胜过顺序循环。 - [sz3/libcimbar](https://github.com/sz3/libcimbar):完全放弃了二维码,转而使用 专为该通道构建的自定义高密度彩色代码。 使用 [node-qrcode](https://github.com/soldair/node-qrcode) 和 [zxing-wasm](https://github.com/Sec-ant/zxing-wasm) 构建。 ## License MIT
标签:AI工具, 二维码, 光学通信, 喷泉码, 文件传输, 自动化攻击, 音视频处理