From 79d90ea7039b6934d3dcb7d6288ff2f1e2b82be0 Mon Sep 17 00:00:00 2001 From: mofeng-git Date: Fri, 20 Feb 2026 21:51:17 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7=E5=88=B0=20v0.1.5=EF=BC=8C=E6=9B=B4=E6=96=B0=20readme?= =?UTF-8?q?=EF=BC=8C=E5=88=A0=E9=99=A4=E8=90=BD=E5=90=8E=E7=9A=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Cargo.toml | 2 +- README.md | 19 +- docs/README.md | 130 -- docs/modules/atx.md | 484 ------ docs/modules/audio.md | 463 ------ docs/modules/auth.md | 340 ---- docs/modules/config.md | 755 --------- docs/modules/events.md | 353 ---- docs/modules/hid.md | 1153 ------------- docs/modules/msd.md | 617 ------- docs/modules/otg.md | 667 -------- docs/modules/rustdesk.md | 1258 -------------- docs/modules/video.md | 1446 ----------------- docs/modules/web.md | 428 ----- docs/modules/webrtc.md | 789 --------- docs/report/hwcodec/00-architecture.md | 477 ------ docs/report/hwcodec/01-api-reference.md | 481 ------ .../hwcodec/02-hardware-acceleration.md | 561 ------- docs/report/hwcodec/03-build-integration.md | 477 ------ docs/report/rustdesk/00-overview.md | 69 - docs/report/rustdesk/01-architecture.md | 218 --- .../report/rustdesk/02-rendezvous-protocol.md | 438 ----- docs/report/rustdesk/03-relay-protocol.md | 318 ---- docs/report/rustdesk/04-p2p-connection.md | 424 ----- docs/report/rustdesk/05-message-format.md | 574 ------- docs/report/rustdesk/06-encryption.md | 342 ---- docs/report/rustdesk/07-nat-traversal.md | 410 ----- docs/report/rustdesk/08-onekvm-comparison.md | 401 ----- docs/system-architecture.md | 920 ----------- docs/tech-stack.md | 1007 ------------ web/package-lock.json | 4 +- web/package.json | 2 +- 33 files changed, 13 insertions(+), 16015 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/modules/atx.md delete mode 100644 docs/modules/audio.md delete mode 100644 docs/modules/auth.md delete mode 100644 docs/modules/config.md delete mode 100644 docs/modules/events.md delete mode 100644 docs/modules/hid.md delete mode 100644 docs/modules/msd.md delete mode 100644 docs/modules/otg.md delete mode 100644 docs/modules/rustdesk.md delete mode 100644 docs/modules/video.md delete mode 100644 docs/modules/web.md delete mode 100644 docs/modules/webrtc.md delete mode 100644 docs/report/hwcodec/00-architecture.md delete mode 100644 docs/report/hwcodec/01-api-reference.md delete mode 100644 docs/report/hwcodec/02-hardware-acceleration.md delete mode 100644 docs/report/hwcodec/03-build-integration.md delete mode 100644 docs/report/rustdesk/00-overview.md delete mode 100644 docs/report/rustdesk/01-architecture.md delete mode 100644 docs/report/rustdesk/02-rendezvous-protocol.md delete mode 100644 docs/report/rustdesk/03-relay-protocol.md delete mode 100644 docs/report/rustdesk/04-p2p-connection.md delete mode 100644 docs/report/rustdesk/05-message-format.md delete mode 100644 docs/report/rustdesk/06-encryption.md delete mode 100644 docs/report/rustdesk/07-nat-traversal.md delete mode 100644 docs/report/rustdesk/08-onekvm-comparison.md delete mode 100644 docs/system-architecture.md delete mode 100644 docs/tech-stack.md diff --git a/.gitignore b/.gitignore index 725f1869..d04ac415 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ CLAUDE.md # Secrets (compile-time configuration) secrets.toml .env +/docs/ diff --git a/Cargo.toml b/Cargo.toml index c246fdd3..899b0b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "one-kvm" -version = "0.1.4" +version = "0.1.5" edition = "2021" authors = ["SilentWind"] description = "A open and lightweight IP-KVM solution written in Rust" diff --git a/README.md b/README.md index e20c6845..7798cb72 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,6 @@ [![GitHub stars](https://img.shields.io/github/stars/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/stargazers) [![GitHub forks](https://img.shields.io/github/forks/mofeng-git/One-KVM?style=social)](https://github.com/mofeng-git/One-KVM/network/members) [![GitHub issues](https://img.shields.io/github/issues/mofeng-git/One-KVM)](https://github.com/mofeng-git/One-KVM/issues) - -

- ๐Ÿ“– ๆŠ€ๆœฏๆ–‡ๆกฃ โ€ข - โšก ๅฟซ้€Ÿๅผ€ๅง‹ โ€ข - ๐Ÿ“Š ๅŠŸ่ƒฝไป‹็ป โ€ข - ๐Ÿ” ่ฟ็งป่ฏดๆ˜Ž -

--- @@ -26,7 +19,7 @@ - **ๅผ€ๆ”พ**๏ผšไธ็ป‘ๅฎš็‰นๅฎš็กฌไปถ้…็ฝฎ๏ผŒๅฐฝ้‡้€‚้…ๅธธ่ง Linux ่ฎพๅค‡ - **่ฝป้‡**๏ผšๅ•ไบŒ่ฟ›ๅˆถๅˆ†ๅ‘๏ผŒ้ƒจ็ฝฒ่ฟ‡็จ‹ๆ›ด็ฎ€ๅ• -- **ๆ˜“็”จ**๏ผš็ฝ‘้กต็•Œ้ขๅฎŒๆˆ่ฎพๅค‡ไธŽๅ‚ๆ•ฐ้…็ฝฎ๏ผŒๅฐฝ้‡ๅ‡ๅฐ‘ๆ‰‹ๅŠจๆ”น้…็ฝฎๆ–‡ไปถ +- **ๆ˜“็”จ**๏ผš็ฝ‘้กต็•Œ้ขๅฎŒๆˆ่ฎพๅค‡ไธŽๅ‚ๆ•ฐ้…็ฝฎ๏ผŒๆ— ้œ€ๆ‰‹ๅŠจๆ”น้…็ฝฎๆ–‡ไปถ > **ๆณจๆ„๏ผš** One-KVM Rust ็›ฎๅ‰ไปๅค„ไบŽๅผ€ๅ‘ๆ—ฉๆœŸ้˜ถๆฎต๏ผŒๅŠŸ่ƒฝไธŽ็ป†่Š‚ไผšๅฟซ้€Ÿ่ฟญไปฃ๏ผŒๆฌข่ฟŽไฝ“้ชŒไธŽๅ้ฆˆใ€‚ @@ -35,7 +28,7 @@ ๅผ€ๅ‘้‡ๅฟƒๆญฃๅœจไปŽ **One-KVM Python** ้€ๆญฅ่ฝฌๅ‘ **One-KVM Rust**ใ€‚ - ๅฆ‚ๆžœไฝ ๅœจไฝฟ็”จ **One-KVM Python๏ผˆๅŸบไบŽ PiKVM๏ผ‰**๏ผŒ่ฏทๆŸฅ็œ‹ [One-KVM Python ๆ–‡ๆกฃ](https://docs.one-kvm.cn/python/) -- One-KVM Rust ็›ธ่พƒไบŽ One-KVM Python๏ผš**ๅฐšๆœช้€‚้… CSI HDMI ้‡‡้›†ๅก**ใ€**ไธๆ”ฏๆŒ VNC ่ฎฟ้—ฎ**๏ผŒไปๅค„ไบŽๅผ€ๅ‘ๆ—ฉๆœŸ้˜ถๆฎต +- One-KVM Rust ็›ธ่พƒไบŽ One-KVM Python๏ผš**ๅฐšๆœชๅฎŒๅ…จ้€‚้… CSI HDMI ้‡‡้›†ๅก**ใ€**ไธๆ”ฏๆŒ VNC ่ฎฟ้—ฎ**๏ผŒไปๅค„ไบŽๅผ€ๅ‘ๆ—ฉๆœŸ้˜ถๆฎต ## ๐Ÿ“Š ๅŠŸ่ƒฝไป‹็ป @@ -55,13 +48,13 @@ - **VAAPI**๏ผšIntel/AMD GPU - **RKMPP**๏ผšRockchip SoC -- **V4L2 M2M**๏ผš้€š็”จ็กฌไปถ็ผ–็ ๅ™จ๏ผˆๅฐšๆœชๅฎž็Žฐ๏ผ‰ +- **V4L2 M2M**๏ผš้€š็”จ็กฌไปถ็ผ–็ ๅ™จ - **่ฝฏไปถ็ผ–็ **๏ผšCPU ็ผ–็  ### ๆ‰ฉๅฑ•่ƒฝๅŠ› - Web UI ้…็ฝฎ๏ผŒๅคš่ฏญ่จ€ๆ”ฏๆŒ๏ผˆไธญๆ–‡/่‹ฑๆ–‡๏ผ‰ -- ๅ†…็ฝฎ Web ็ปˆ็ซฏ๏ผˆttyd๏ผ‰ๅ†…็ฝ‘็ฉฟ้€ๆ”ฏๆŒ๏ผˆgostc๏ผ‰ใ€P2P ็ป„็ฝ‘ๆ”ฏๆŒ๏ผˆEasyTier๏ผ‰ใ€RustDesk ๅ่ฎฎ้›†ๆˆ๏ผˆ็”จไบŽ่ทจๅนณๅฐ่ฟœ็จ‹่ฎฟ้—ฎ่ƒฝๅŠ›ๆ‰ฉๅฑ•๏ผ‰ +- ๅ†…็ฝฎ Web ็ปˆ็ซฏ๏ผˆttyd๏ผ‰ใ€ๅ†…็ฝ‘็ฉฟ้€ๆ”ฏๆŒ๏ผˆgostc๏ผ‰ใ€P2P ็ป„็ฝ‘ๆ”ฏๆŒ๏ผˆEasyTier๏ผ‰ใ€RustDesk ๅ่ฎฎ้›†ๆˆ๏ผˆ็”จไบŽ่ทจๅนณๅฐ่ฟœ็จ‹่ฎฟ้—ฎ่ƒฝๅŠ›ๆ‰ฉๅฑ•๏ผ‰ๅ’Œ RTSP ่ง†้ข‘ๆต๏ผˆ็”จไบŽ่ง†้ข‘ๆŽจๆต๏ผ‰ ## โšก ๅฎ‰่ฃ…ไฝฟ็”จ @@ -195,6 +188,10 @@ - ็ˆฑๅ‘็”ต็”จๆˆท_JT6c +- MaxZ + +- ็ˆฑๅ‘็”ต็”จๆˆท_d3d9c + - ...... diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 24545f82..00000000 --- a/docs/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# One-KVM ๆŠ€ๆœฏๆ–‡ๆกฃ - -ๆœฌ็›ฎๅฝ•ๅŒ…ๅซ One-KVM ้กน็›ฎ็š„ๅฎŒๆ•ดๆŠ€ๆœฏๆ–‡ๆกฃใ€‚ - -## ๆ–‡ๆกฃ็ป“ๆž„ - -``` -docs/ -โ”œโ”€โ”€ README.md # ๆœฌๆ–‡ไปถ - ๆ–‡ๆกฃ็ดขๅผ• -โ”œโ”€โ”€ system-architecture.md # ็ณป็ปŸๆžถๆž„ๆ–‡ๆกฃ -โ”œโ”€โ”€ tech-stack.md # ๆŠ€ๆœฏๆ ˆๆ–‡ๆกฃ -โ””โ”€โ”€ modules/ # ๆจกๅ—ๆ–‡ๆกฃ - โ”œโ”€โ”€ video.md # ่ง†้ข‘ๆจกๅ— - โ”œโ”€โ”€ hid.md # HID ๆจกๅ— - โ”œโ”€โ”€ otg.md # OTG ๆจกๅ— - โ”œโ”€โ”€ msd.md # MSD ๆจกๅ— - โ”œโ”€โ”€ atx.md # ATX ๆจกๅ— - โ”œโ”€โ”€ audio.md # ้Ÿณ้ข‘ๆจกๅ— - โ”œโ”€โ”€ webrtc.md # WebRTC ๆจกๅ— - โ”œโ”€โ”€ rustdesk.md # RustDesk ๆจกๅ— - โ”œโ”€โ”€ auth.md # ่ฎค่ฏๆจกๅ— - โ”œโ”€โ”€ config.md # ้…็ฝฎๆจกๅ— - โ”œโ”€โ”€ events.md # ไบ‹ไปถๆจกๅ— - โ””โ”€โ”€ web.md # Web ๆจกๅ— -``` - -## ๅฟซ้€Ÿๅฏผ่ˆช - -### ๆ ธๅฟƒๆ–‡ๆกฃ - -| ๆ–‡ๆกฃ | ๆ่ฟฐ | -|------|------| -| [็ณป็ปŸๆžถๆž„](./system-architecture.md) | ๆ•ดไฝ“ๆžถๆž„่ฎพ่ฎกใ€ๆ•ฐๆฎๆตใ€ๆจกๅ—ไพ่ต– | -| [ๆŠ€ๆœฏๆ ˆ](./tech-stack.md) | ไฝฟ็”จ็š„ๆŠ€ๆœฏใ€ๅบ“ๅ’Œๅผ€ๅ‘่ง„่Œƒ | - -### ๅŠŸ่ƒฝๆจกๅ— - -| ๆจกๅ— | ๆ่ฟฐ | ๅ…ณ้”ฎๆ–‡ไปถ | -|------|------|---------| -| [Video](./modules/video.md) | ่ง†้ข‘้‡‡้›†ๅ’Œ็ผ–็  | `src/video/` | -| [HID](./modules/hid.md) | ้”ฎ็›˜้ผ ๆ ‡ๆŽงๅˆถ | `src/hid/` | -| [OTG](./modules/otg.md) | USB Gadget ็ฎก็† | `src/otg/` | -| [MSD](./modules/msd.md) | ่™šๆ‹Ÿๅญ˜ๅ‚จ่ฎพๅค‡ | `src/msd/` | -| [ATX](./modules/atx.md) | ็”ตๆบๆŽงๅˆถ | `src/atx/` | -| [Audio](./modules/audio.md) | ้Ÿณ้ข‘้‡‡้›†็ผ–็  | `src/audio/` | -| [WebRTC](./modules/webrtc.md) | WebRTC ๆตๅช’ไฝ“ | `src/webrtc/` | -| [RustDesk](./modules/rustdesk.md) | RustDesk ๅ่ฎฎ้›†ๆˆ | `src/rustdesk/` | - -### ๅŸบ็ก€่ฎพๆ–ฝ - -| ๆจกๅ— | ๆ่ฟฐ | ๅ…ณ้”ฎๆ–‡ไปถ | -|------|------|---------| -| [Auth](./modules/auth.md) | ่ฎค่ฏๅ’Œไผš่ฏ | `src/auth/` | -| [Config](./modules/config.md) | ้…็ฝฎ็ฎก็† | `src/config/` | -| [Events](./modules/events.md) | ไบ‹ไปถ็ณป็ปŸ | `src/events/` | -| [Web](./modules/web.md) | HTTP API | `src/web/` | - -## ๆžถๆž„ๆฆ‚่งˆ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ One-KVM System โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Web Frontend (Vue3) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Axum Web Server โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ AppState โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Video โ”‚ โ”‚ HID โ”‚ โ”‚ MSD โ”‚ โ”‚ ATX โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Audio โ”‚ โ”‚ WebRTC โ”‚ โ”‚RustDeskโ”‚ โ”‚ Events โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Bus โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Hardware Layer โ”‚ โ”‚ -โ”‚ โ”‚ V4L2 โ”‚ USB OTG โ”‚ GPIO โ”‚ ALSA โ”‚ Network โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๅ…ณ้”ฎ็‰นๆ€ง - -- **ๅ•ไธ€ไบŒ่ฟ›ๅˆถ**: Web UI + ๅŽ็ซฏไธ€ไฝ“ๅŒ–้ƒจ็ฝฒ -- **ๅŒๆตๆจกๅผ**: WebRTC (H264/H265/VP8/VP9) + MJPEG -- **USB OTG**: ่™šๆ‹Ÿ้”ฎ้ผ ใ€่™šๆ‹Ÿๅญ˜ๅ‚จ -- **็กฌไปถๅŠ ้€Ÿ**: VAAPI/RKMPP/V4L2 M2M -- **RustDesk**: ่ทจๅนณๅฐ่ฟœ็จ‹่ฎฟ้—ฎ -- **ๆ— ้…็ฝฎๆ–‡ไปถ**: SQLite ้…็ฝฎๅญ˜ๅ‚จ - -## ็›ฎๆ ‡ๅนณๅฐ - -| ๅนณๅฐ | ๆžถๆž„ | ็”จ้€” | -|------|------|------| -| aarch64-unknown-linux-gnu | ARM64 | ไธป่ฆ็›ฎๆ ‡ | -| armv7-unknown-linux-gnueabihf | ARMv7 | ๅค‡้€‰ | -| x86_64-unknown-linux-gnu | x86-64 | ๅผ€ๅ‘/ๆต‹่ฏ• | - -## ๅฟซ้€Ÿๅผ€ๅง‹ - -```bash -# ๆž„ๅปบๅ‰็ซฏ -cd web && npm install && npm run build && cd .. - -# ๆž„ๅปบๅŽ็ซฏ -cargo build --release - -# ่ฟ่กŒ -./target/release/one-kvm --enable-https -``` - -## ็›ธๅ…ณ้“พๆŽฅ - -- [้กน็›ฎไป“ๅบ“](https://github.com/mofeng-git/One-KVM) -- [ๅผ€ๅ‘่ฎกๅˆ’](./DEVELOPMENT_PLAN.md) -- [้กน็›ฎ็›ฎๆ ‡](./PROJECT_GOALS.md) diff --git a/docs/modules/atx.md b/docs/modules/atx.md deleted file mode 100644 index 316d4a34..00000000 --- a/docs/modules/atx.md +++ /dev/null @@ -1,484 +0,0 @@ -# ATX ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -ATX ๆจกๅ—ๆไพ›็”ตๆบๆŽงๅˆถๅŠŸ่ƒฝ๏ผŒ้€š่ฟ‡ GPIO ๆˆ– USB ็ปง็”ตๅ™จๆŽงๅˆถ็›ฎๆ ‡่ฎก็ฎ—ๆœบ็š„็”ตๆบๅ’Œ้‡็ฝฎๆŒ‰้’ฎใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ็”ตๆบๆŒ‰้’ฎๆŽงๅˆถ -- ้‡็ฝฎๆŒ‰้’ฎๆŽงๅˆถ -- ็”ตๆบ LED ็Šถๆ€็›‘่ง† -- Wake-on-LAN ๆ”ฏๆŒ -- ๅคšๅŽ็ซฏๆ”ฏๆŒ (GPIO/USB ็ปง็”ตๅ™จ) - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/atx/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ controller.rs # AtxController (11KB) -โ”œโ”€โ”€ executor.rs # ๅŠจไฝœๆ‰ง่กŒๅ™จ (10KB) -โ”œโ”€โ”€ types.rs # ็ฑปๅž‹ๅฎšไน‰ (7KB) -โ”œโ”€โ”€ led.rs # LED ็›‘่ง† (5KB) -โ””โ”€โ”€ wol.rs # Wake-on-LAN (5KB) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ATX Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - Web API - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ AtxController โ”‚ - โ”‚ (controller.rs) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Power โ”‚ โ”‚ Reset โ”‚ โ”‚ LED โ”‚ -โ”‚Executorโ”‚ โ”‚Executorโ”‚ โ”‚Monitor โ”‚ -โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ GPIO โ”‚ โ”‚ GPIO โ”‚ โ”‚ GPIO โ”‚ -โ”‚ or USB โ”‚ โ”‚ or USB โ”‚ โ”‚ Input โ”‚ -โ”‚ Relay โ”‚ โ”‚ Relay โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Target PC โ”‚ - โ”‚ (ATX Header) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 AtxController (controller.rs) - -```rust -pub struct AtxController { - /// ็”ตๆบๆŒ‰้’ฎ้…็ฝฎ - power: Arc, - - /// ้‡็ฝฎๆŒ‰้’ฎ้…็ฝฎ - reset: Arc, - - /// LED ็›‘่ง†ๅ™จ - led_monitor: Arc>>, - - /// WoL ๆŽงๅˆถๅ™จ - wol: Arc>>, - - /// ๅฝ“ๅ‰็Šถๆ€ - state: Arc>, - - /// ไบ‹ไปถๆ€ป็บฟ - events: Arc, -} - -impl AtxController { - /// ๅˆ›ๅปบๆŽงๅˆถๅ™จ - pub fn new(config: &AtxConfig, events: Arc) -> Result; - - /// ็ŸญๆŒ‰็”ตๆบๆŒ‰้’ฎ (ๅผ€ๆœบ/ๆญฃๅธธๅ…ณๆœบ) - pub async fn power_short_press(&self) -> Result<()>; - - /// ้•ฟๆŒ‰็”ตๆบๆŒ‰้’ฎ (ๅผบๅˆถๅ…ณๆœบ) - pub async fn power_long_press(&self) -> Result<()>; - - /// ๆŒ‰้‡็ฝฎๆŒ‰้’ฎ - pub async fn reset_press(&self) -> Result<()>; - - /// ่Žทๅ–็”ตๆบ็Šถๆ€ - pub fn power_state(&self) -> PowerState; - - /// ๅ‘้€ WoL ้ญ”ๆœฏๅŒ… - pub async fn wake_on_lan(&self, mac: &str) -> Result<()>; - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> AtxState; - - /// ้‡ๆ–ฐๅŠ ่ฝฝ้…็ฝฎ - pub async fn reload(&self, config: &AtxConfig) -> Result<()>; -} - -pub struct AtxState { - /// ๆ˜ฏๅฆๅฏ็”จ - pub available: bool, - - /// ็”ตๆบๆ˜ฏๅฆๅผ€ๅฏ - pub power_on: bool, - - /// ๆœ€ๅŽๆ“ไฝœๆ—ถ้—ด - pub last_action: Option>, - - /// ้”™่ฏฏไฟกๆฏ - pub error: Option, -} - -pub enum PowerState { - On, - Off, - Unknown, -} -``` - -### 3.2 AtxButton (executor.rs) - -```rust -pub struct AtxButton { - /// ๆŒ‰้’ฎๅ็งฐ - name: String, - - /// ้ฉฑๅŠจ็ฑปๅž‹ - driver: AtxDriverType, - - /// GPIO ๅฅๆŸ„ - gpio: Option, - - /// USB ็ปง็”ตๅ™จๅฅๆŸ„ - relay: Option, - - /// ้…็ฝฎ - config: AtxKeyConfig, -} - -impl AtxButton { - /// ๅˆ›ๅปบๆŒ‰้’ฎ - pub fn new(name: &str, config: &AtxKeyConfig) -> Result; - - /// ็ŸญๆŒ‰ (100ms) - pub async fn short_press(&self) -> Result<()>; - - /// ้•ฟๆŒ‰ (3000ms) - pub async fn long_press(&self) -> Result<()>; - - /// ่‡ชๅฎšไน‰ๆŒ‰ๅŽ‹ๆ—ถ้—ด - pub async fn press(&self, duration: Duration) -> Result<()>; - - /// ่ฎพ็ฝฎ่พ“ๅ‡บ็Šถๆ€ - fn set_output(&self, high: bool) -> Result<()>; -} - -pub enum AtxDriverType { - /// GPIO ็›ด่ฟž - Gpio, - - /// USB ็ปง็”ตๅ™จ - UsbRelay, - - /// ็ฆ็”จ - None, -} -``` - -### 3.3 LedMonitor (led.rs) - -```rust -pub struct LedMonitor { - /// GPIO ๅผ•่„š - pin: u32, - - /// GPIO ๅฅๆŸ„ - line: LineHandle, - - /// ๅฝ“ๅ‰็Šถๆ€ - state: Arc, - - /// ็›‘่ง†ไปปๅŠก - monitor_task: Option>, -} - -impl LedMonitor { - /// ๅˆ›ๅปบ็›‘่ง†ๅ™จ - pub fn new(config: &AtxLedConfig) -> Result; - - /// ๅฏๅŠจ็›‘่ง† - pub fn start(&mut self, events: Arc) -> Result<()>; - - /// ๅœๆญข็›‘่ง† - pub fn stop(&mut self); - - /// ่Žทๅ–ๅฝ“ๅ‰็Šถๆ€ - pub fn state(&self) -> bool; -} -``` - -### 3.4 WolController (wol.rs) - -```rust -pub struct WolController { - /// ็ฝ‘็ปœๆŽฅๅฃ - interface: String, - - /// ๅนฟๆ’ญๅœฐๅ€ - broadcast_addr: SocketAddr, -} - -impl WolController { - /// ๅˆ›ๅปบๆŽงๅˆถๅ™จ - pub fn new(interface: Option<&str>) -> Result; - - /// ๅ‘้€ WoL ้ญ”ๆœฏๅŒ… - pub async fn wake(&self, mac: &str) -> Result<()>; - - /// ๆž„ๅปบ้ญ”ๆœฏๅŒ… - fn build_magic_packet(mac: &[u8; 6]) -> [u8; 102]; - - /// ่งฃๆž MAC ๅœฐๅ€ - fn parse_mac(mac: &str) -> Result<[u8; 6]>; -} -``` - ---- - -## 4. ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct AtxConfig { - /// ๆ˜ฏๅฆๅฏ็”จ - pub enabled: bool, - - /// ็”ตๆบๆŒ‰้’ฎ้…็ฝฎ - pub power: AtxKeyConfig, - - /// ้‡็ฝฎๆŒ‰้’ฎ้…็ฝฎ - pub reset: AtxKeyConfig, - - /// LED ็›‘่ง†้…็ฝฎ - pub led: AtxLedConfig, - - /// WoL ้…็ฝฎ - pub wol: WolConfig, -} - -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct AtxKeyConfig { - /// ้ฉฑๅŠจ็ฑปๅž‹ - pub driver: AtxDriverType, - - /// GPIO ่Šฏ็‰‡ (ๅฆ‚ /dev/gpiochip0) - pub gpio_chip: Option, - - /// GPIO ๅผ•่„šๅท - pub gpio_pin: Option, - - /// USB ็ปง็”ตๅ™จ่ฎพๅค‡ - pub relay_device: Option, - - /// ็ปง็”ตๅ™จ้€š้“ - pub relay_channel: Option, - - /// ๆฟ€ๆดป็”ตๅนณ - pub active_level: ActiveLevel, -} - -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct AtxLedConfig { - /// ๆ˜ฏๅฆๅฏ็”จ - pub enabled: bool, - - /// GPIO ่Šฏ็‰‡ - pub gpio_chip: Option, - - /// GPIO ๅผ•่„šๅท - pub gpio_pin: Option, - - /// ๆฟ€ๆดป็”ตๅนณ - pub active_level: ActiveLevel, -} - -pub enum ActiveLevel { - High, - Low, -} - -impl Default for AtxConfig { - fn default() -> Self { - Self { - enabled: false, - power: AtxKeyConfig::default(), - reset: AtxKeyConfig::default(), - led: AtxLedConfig::default(), - wol: WolConfig::default(), - } - } -} -``` - ---- - -## 5. API ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/api/atx/status` | GET | ่Žทๅ– ATX ็Šถๆ€ | -| `/api/atx/power/short` | POST | ็ŸญๆŒ‰็”ตๆบ | -| `/api/atx/power/long` | POST | ้•ฟๆŒ‰็”ตๆบ | -| `/api/atx/reset` | POST | ๆŒ‰้‡็ฝฎ | -| `/api/atx/wol` | POST | ๅ‘้€ WoL | - -### ๅ“ๅบ”ๆ ผๅผ - -```json -// GET /api/atx/status -{ - "available": true, - "power_on": true, - "last_action": "2024-01-15T10:30:00Z", - "error": null -} - -// POST /api/atx/wol -// Request: { "mac": "00:11:22:33:44:55" } -{ - "success": true -} -``` - ---- - -## 6. ็กฌไปถ่ฟžๆŽฅ - -### 6.1 GPIO ็›ด่ฟž - -``` -One-KVM Device Target PC -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ GPIO Pin โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ Power SW โ”‚ -โ”‚ (Output) โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -ๆŽฅ็บฟ่ฏดๆ˜Ž: -- GPIO ๅผ•่„š่ฟžๆŽฅๅˆฐ ATX ็”ตๆบๆŒ‰้’ฎ -- ไฝฟ็”จๅ…‰่€ฆๆˆ–็ปง็”ตๅ™จ้š”็ฆป (ๆŽจ่) -- ๆณจๆ„็”ตๅนณๅŒน้… -``` - -### 6.2 USB ็ปง็”ตๅ™จ - -``` -One-KVM Device USB Relay Target PC -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ USB โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ Relay โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ Power SW โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -ไผ˜็‚น: -- ๅฎŒๅ…จ้š”็ฆป -- ๆ— ้œ€ๆ‹…ๅฟƒ็”ตๅนณ้—ฎ้ข˜ -- ๆ›ดๅฎ‰ๅ…จ -``` - ---- - -## 7. ไบ‹ไปถ - -```rust -pub enum SystemEvent { - AtxStateChanged { - power_on: bool, - last_action: Option, - error: Option, - }, - - AtxActionPerformed { - action: String, // "power_short" | "power_long" | "reset" | "wol" - success: bool, - }, -} -``` - ---- - -## 8. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum AtxError { - #[error("ATX not available")] - NotAvailable, - - #[error("GPIO error: {0}")] - GpioError(String), - - #[error("Relay error: {0}")] - RelayError(String), - - #[error("WoL error: {0}")] - WolError(String), - - #[error("Invalid MAC address: {0}")] - InvalidMac(String), - - #[error("Operation in progress")] - Busy, -} -``` - ---- - -## 9. ไฝฟ็”จ็คบไพ‹ - -```rust -let atx = AtxController::new(&config, events)?; - -// ๅผ€ๆœบ -atx.power_short_press().await?; - -// ๆฃ€ๆŸฅ็Šถๆ€ -tokio::time::sleep(Duration::from_secs(5)).await; -if atx.power_state() == PowerState::On { - println!("PC is now on"); -} - -// ๅผบๅˆถๅ…ณๆœบ -atx.power_long_press().await?; - -// ้‡็ฝฎ -atx.reset_press().await?; - -// Wake-on-LAN -atx.wake_on_lan("00:11:22:33:44:55").await?; -``` - ---- - -## 10. ๅธธ่ง้—ฎ้ข˜ - -### Q: GPIO ๆ— ๆณ•ๆŽงๅˆถ? - -1. ๆฃ€ๆŸฅๅผ•่„š้…็ฝฎ -2. ๆฃ€ๆŸฅๆƒ้™ (`/dev/gpiochip*`) -3. ๆฃ€ๆŸฅๆŽฅ็บฟ - -### Q: LED ็Šถๆ€ไธๆญฃ็กฎ? - -1. ๆฃ€ๆŸฅ active_level ้…็ฝฎ -2. ๆฃ€ๆŸฅ GPIO ่พ“ๅ…ฅๆจกๅผ - -### Q: WoL ไธๅทฅไฝœ? - -1. ๆฃ€ๆŸฅ็›ฎๆ ‡ PC BIOS ่ฎพ็ฝฎ -2. ๆฃ€ๆŸฅ็ฝ‘ๅกๆ”ฏๆŒ -3. ๆฃ€ๆŸฅ็ฝ‘็ปœๅนฟๆ’ญ diff --git a/docs/modules/audio.md b/docs/modules/audio.md deleted file mode 100644 index 670f4469..00000000 --- a/docs/modules/audio.md +++ /dev/null @@ -1,463 +0,0 @@ -# Audio ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Audio ๆจกๅ—่ดŸ่ดฃ้Ÿณ้ข‘้‡‡้›†ๅ’Œ็ผ–็ ๏ผŒๆ”ฏๆŒ ALSA ้‡‡้›†ๅ’Œ Opus ็ผ–็ ใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ALSA ้Ÿณ้ข‘้‡‡้›† -- Opus ็ผ–็  -- ๅคš่ดจ้‡้…็ฝฎ -- WebSocket/WebRTC ไผ ่พ“ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/audio/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ controller.rs # AudioController (15KB) -โ”œโ”€โ”€ capture.rs # ALSA ้‡‡้›† (12KB) -โ”œโ”€โ”€ encoder.rs # Opus ็ผ–็  (8KB) -โ”œโ”€โ”€ shared_pipeline.rs # ๅ…ฑไบซ็ฎก้“ (15KB) -โ”œโ”€โ”€ monitor.rs # ๅฅๅบท็›‘่ง† (11KB) -โ””โ”€โ”€ device.rs # ่ฎพๅค‡ๆžšไธพ (8KB) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Audio Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -ALSA Device (hw:0,0) - โ”‚ - โ”‚ PCM 48kHz/16bit/Stereo - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ AudioCapturer โ”‚ -โ”‚ (capture.rs) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ SharedAudioPipeline โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Opus Encoder โ”‚ โ”‚ -โ”‚ โ”‚ 48kHz โ†’ 24-96 kbps โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ WebSocket โ”‚ โ”‚ WebRTC โ”‚ -โ”‚ Stream โ”‚ โ”‚ Audio Track โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 AudioController (controller.rs) - -```rust -pub struct AudioController { - /// ้‡‡้›†ๅ™จ - capturer: Arc>>, - - /// ๅ…ฑไบซ็ฎก้“ - pipeline: Arc, - - /// ้…็ฝฎ - config: Arc>, - - /// ็Šถๆ€ - state: Arc>, - - /// ไบ‹ไปถๆ€ป็บฟ - events: Arc, -} - -impl AudioController { - /// ๅˆ›ๅปบๆŽงๅˆถๅ™จ - pub fn new(config: &AudioConfig, events: Arc) -> Result; - - /// ๅฏๅŠจ้Ÿณ้ข‘ - pub async fn start(&self) -> Result<()>; - - /// ๅœๆญข้Ÿณ้ข‘ - pub async fn stop(&self) -> Result<()>; - - /// ่ฎข้˜…้Ÿณ้ข‘ๅธง - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ่Žทๅ–็Šถๆ€ - pub fn status(&self) -> AudioStatus; - - /// ่ฎพ็ฝฎ่ดจ้‡ - pub fn set_quality(&self, quality: AudioQuality) -> Result<()>; - - /// ๅˆ—ๅ‡บ่ฎพๅค‡ - pub fn list_devices(&self) -> Vec; - - /// ้‡ๆ–ฐๅŠ ่ฝฝ้…็ฝฎ - pub async fn reload(&self, config: &AudioConfig) -> Result<()>; -} - -pub struct AudioStatus { - pub enabled: bool, - pub streaming: bool, - pub device: Option, - pub sample_rate: u32, - pub channels: u16, - pub bitrate: u32, - pub error: Option, -} -``` - -### 3.2 AudioCapturer (capture.rs) - -```rust -pub struct AudioCapturer { - /// PCM ๅฅๆŸ„ - pcm: PCM, - - /// ่ฎพๅค‡ๅ - device: String, - - /// ้‡‡ๆ ท็އ - sample_rate: u32, - - /// ้€š้“ๆ•ฐ - channels: u16, - - /// ๅธงๅคงๅฐ - frame_size: usize, - - /// ่ฟ่กŒ็Šถๆ€ - running: AtomicBool, -} - -impl AudioCapturer { - /// ๆ‰“ๅผ€่ฎพๅค‡ - pub fn open(device: &str, config: &CaptureConfig) -> Result; - - /// ่ฏปๅ–้Ÿณ้ข‘ๅธง - pub fn read_frame(&self) -> Result>; - - /// ๅฏๅŠจ้‡‡้›† - pub fn start(&self) -> Result<()>; - - /// ๅœๆญข้‡‡้›† - pub fn stop(&self); - - /// ๆ˜ฏๅฆ่ฟ่กŒไธญ - pub fn is_running(&self) -> bool; -} - -pub struct CaptureConfig { - pub sample_rate: u32, // 48000 - pub channels: u16, // 2 - pub frame_size: usize, // 960 (20ms) - pub buffer_size: usize, // 4800 -} -``` - -### 3.3 OpusEncoder (encoder.rs) - -```rust -pub struct OpusEncoder { - /// Opus ็ผ–็ ๅ™จ - encoder: audiopus::Encoder, - - /// ้‡‡ๆ ท็އ - sample_rate: u32, - - /// ้€š้“ๆ•ฐ - channels: u16, - - /// ๅธงๅคงๅฐ - frame_size: usize, - - /// ็ ็އ - bitrate: u32, -} - -impl OpusEncoder { - /// ๅˆ›ๅปบ็ผ–็ ๅ™จ - pub fn new(quality: AudioQuality) -> Result; - - /// ็ผ–็  PCM ๆ•ฐๆฎ - pub fn encode(&mut self, pcm: &[i16]) -> Result>; - - /// ่ฎพ็ฝฎ็ ็އ - pub fn set_bitrate(&mut self, bitrate: u32) -> Result<()>; - - /// ่Žทๅ–็ ็އ - pub fn bitrate(&self) -> u32; - - /// ้‡็ฝฎ็ผ–็ ๅ™จ - pub fn reset(&mut self) -> Result<()>; -} -``` - -### 3.4 SharedAudioPipeline (shared_pipeline.rs) - -```rust -pub struct SharedAudioPipeline { - /// ้‡‡้›†ๅ™จ - capturer: Arc>>, - - /// ็ผ–็ ๅ™จ - encoder: Arc>, - - /// ๅนฟๆ’ญ้€š้“ - tx: broadcast::Sender, - - /// ้‡‡้›†ไปปๅŠก - capture_task: Arc>>>, - - /// ้…็ฝฎ - config: Arc>, -} - -impl SharedAudioPipeline { - /// ๅˆ›ๅปบ็ฎก้“ - pub fn new(config: &AudioConfig) -> Result; - - /// ๅฏๅŠจ็ฎก้“ - pub async fn start(&self) -> Result<()>; - - /// ๅœๆญข็ฎก้“ - pub async fn stop(&self) -> Result<()>; - - /// ่ฎข้˜…้Ÿณ้ข‘ๅธง - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ่Žทๅ–็ปŸ่ฎก - pub fn stats(&self) -> PipelineStats; -} - -pub struct AudioFrame { - /// Opus ๆ•ฐๆฎ - pub data: Bytes, - - /// ๆ—ถ้—ดๆˆณ - pub timestamp: u64, - - /// ๅธงๅบๅท - pub sequence: u64, -} -``` - ---- - -## 4. ้Ÿณ้ข‘่ดจ้‡ - -```rust -pub enum AudioQuality { - /// 24 kbps - ๆœ€ไฝŽๅธฆๅฎฝ - VeryLow, - - /// 48 kbps - ไฝŽๅธฆๅฎฝ - Low, - - /// 64 kbps - ไธญ็ญ‰ - Medium, - - /// 96 kbps - ้ซ˜่ดจ้‡ - High, -} - -impl AudioQuality { - pub fn bitrate(&self) -> u32 { - match self { - Self::VeryLow => 24000, - Self::Low => 48000, - Self::Medium => 64000, - Self::High => 96000, - } - } -} -``` - ---- - -## 5. ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct AudioConfig { - /// ๆ˜ฏๅฆๅฏ็”จ - pub enabled: bool, - - /// ่ฎพๅค‡ๅ - pub device: Option, - - /// ้Ÿณ้ข‘่ดจ้‡ - pub quality: AudioQuality, - - /// ่‡ชๅŠจๅฏๅŠจ - pub auto_start: bool, -} - -impl Default for AudioConfig { - fn default() -> Self { - Self { - enabled: true, - device: None, // ไฝฟ็”จ้ป˜่ฎค่ฎพๅค‡ - quality: AudioQuality::Medium, - auto_start: false, - } - } -} -``` - ---- - -## 6. API ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/api/audio/status` | GET | ่Žทๅ–้Ÿณ้ข‘็Šถๆ€ | -| `/api/audio/start` | POST | ๅฏๅŠจ้Ÿณ้ข‘ | -| `/api/audio/stop` | POST | ๅœๆญข้Ÿณ้ข‘ | -| `/api/audio/devices` | GET | ๅˆ—ๅ‡บ่ฎพๅค‡ | -| `/api/audio/quality` | GET | ่Žทๅ–่ดจ้‡ | -| `/api/audio/quality` | POST | ่ฎพ็ฝฎ่ดจ้‡ | -| `/api/ws/audio` | WS | ้Ÿณ้ข‘ๆต | - -### ๅ“ๅบ”ๆ ผๅผ - -```json -// GET /api/audio/status -{ - "enabled": true, - "streaming": true, - "device": "hw:0,0", - "sample_rate": 48000, - "channels": 2, - "bitrate": 64000, - "error": null -} - -// GET /api/audio/devices -{ - "devices": [ - { - "name": "hw:0,0", - "description": "USB Audio Device", - "is_default": true - } - ] -} -``` - ---- - -## 7. WebSocket ้Ÿณ้ข‘ๆต - -```javascript -// ่ฟžๆŽฅ WebSocket -const ws = new WebSocket('/api/ws/audio'); -ws.binaryType = 'arraybuffer'; - -// ๅˆๅง‹ๅŒ– Opus ่งฃ็ ๅ™จ -const decoder = new OpusDecoder(); - -// ๆŽฅๆ”ถ้Ÿณ้ข‘ๅธง -ws.onmessage = (event) => { - const frame = new Uint8Array(event.data); - const pcm = decoder.decode(frame); - audioContext.play(pcm); -}; -``` - ---- - -## 8. ไบ‹ไปถ - -```rust -pub enum SystemEvent { - AudioStateChanged { - enabled: bool, - streaming: bool, - device: Option, - error: Option, - }, -} -``` - ---- - -## 9. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum AudioError { - #[error("Device not found: {0}")] - DeviceNotFound(String), - - #[error("Device busy: {0}")] - DeviceBusy(String), - - #[error("ALSA error: {0}")] - AlsaError(String), - - #[error("Encoder error: {0}")] - EncoderError(String), - - #[error("Not streaming")] - NotStreaming, -} -``` - ---- - -## 10. ไฝฟ็”จ็คบไพ‹ - -```rust -let controller = AudioController::new(&config, events)?; - -// ๅฏๅŠจ้Ÿณ้ข‘ -controller.start().await?; - -// ่ฎข้˜…้Ÿณ้ข‘ๅธง -let mut rx = controller.subscribe(); -while let Ok(frame) = rx.recv().await { - // ๅค„็† Opus ๆ•ฐๆฎ - send_to_client(frame.data); -} - -// ๅœๆญข -controller.stop().await?; -``` - ---- - -## 11. ๅธธ่ง้—ฎ้ข˜ - -### Q: ๆ‰พไธๅˆฐ้Ÿณ้ข‘่ฎพๅค‡? - -1. ๆฃ€ๆŸฅ ALSA ้…็ฝฎ -2. ่ฟ่กŒ `arecord -l` -3. ๆฃ€ๆŸฅๆƒ้™ - -### Q: ้Ÿณ้ข‘ๅปถ่ฟŸ้ซ˜? - -1. ๅ‡ๅฐๅธงๅคงๅฐ -2. ้™ไฝŽ่ดจ้‡ -3. ๆฃ€ๆŸฅ็ฝ‘็ปœ - -### Q: ้Ÿณ้ข‘ๆ–ญๆ–ญ็ปญ็ปญ? - -1. ๅขžๅคง็ผ“ๅ†ฒๅŒบ -2. ๆฃ€ๆŸฅ CPU ่ดŸ่ฝฝ -3. ไฝฟ็”จๆ›ดไฝŽ่ดจ้‡ diff --git a/docs/modules/auth.md b/docs/modules/auth.md deleted file mode 100644 index a1e4a4b6..00000000 --- a/docs/modules/auth.md +++ /dev/null @@ -1,340 +0,0 @@ -# Auth ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Auth ๆจกๅ—ๆไพ›็”จๆˆท่ฎค่ฏๅ’Œไผš่ฏ็ฎก็†ๅŠŸ่ƒฝใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ็”จๆˆท็ฎก็† -- ๅฏ†็ ๅ“ˆๅธŒ (Argon2) -- ไผš่ฏ็ฎก็† -- ่ฎค่ฏไธญ้—ดไปถ -- ๆƒ้™ๆŽงๅˆถ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/auth/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ user.rs # ็”จๆˆท็ฎก็† (5KB) -โ”œโ”€โ”€ session.rs # ไผš่ฏ็ฎก็† (4KB) -โ”œโ”€โ”€ password.rs # ๅฏ†็ ๅ“ˆๅธŒ (1KB) -โ””โ”€โ”€ middleware.rs # ไธญ้—ดไปถ (4KB) -``` - ---- - -## 2. ๆ ธๅฟƒ็ป„ไปถ - -### 2.1 UserStore (user.rs) - -```rust -pub struct UserStore { - db: Pool, -} - -impl UserStore { - /// ๅˆ›ๅปบๅญ˜ๅ‚จ - pub async fn new(db: Pool) -> Result; - - /// ๅˆ›ๅปบ็”จๆˆท - pub async fn create_user(&self, user: &CreateUser) -> Result; - - /// ่Žทๅ–็”จๆˆท - pub async fn get_user(&self, id: &str) -> Result>; - - /// ๆŒ‰็”จๆˆทๅ่Žทๅ– - pub async fn get_by_username(&self, username: &str) -> Result>; - - /// ๆ›ดๆ–ฐ็”จๆˆท - pub async fn update_user(&self, id: &str, update: &UpdateUser) -> Result<()>; - - /// ๅˆ ้™ค็”จๆˆท - pub async fn delete_user(&self, id: &str) -> Result<()>; - - /// ๅˆ—ๅ‡บ็”จๆˆท - pub async fn list_users(&self) -> Result>; - - /// ้ชŒ่ฏๅฏ†็  - pub async fn verify_password(&self, username: &str, password: &str) -> Result>; - - /// ๆ›ดๆ–ฐๅฏ†็  - pub async fn update_password(&self, id: &str, new_password: &str) -> Result<()>; - - /// ๆฃ€ๆŸฅๆ˜ฏๅฆ้œ€่ฆๅˆๅง‹ๅŒ– - pub async fn needs_setup(&self) -> Result; -} - -pub struct User { - pub id: String, - pub username: String, - pub role: UserRole, - pub created_at: DateTime, -} - -pub enum UserRole { - Admin, - User, -} - -pub struct CreateUser { - pub username: String, - pub password: String, - pub role: UserRole, -} -``` - -### 2.2 SessionStore (session.rs) - -```rust -pub struct SessionStore { - /// ไผš่ฏๆ˜ ๅฐ„ - sessions: RwLock>, - - /// ไผš่ฏ่ถ…ๆ—ถ - timeout: Duration, -} - -impl SessionStore { - /// ๅˆ›ๅปบๅญ˜ๅ‚จ - pub fn new(timeout: Duration) -> Self; - - /// ๅˆ›ๅปบไผš่ฏ - pub fn create_session(&self, user: &User) -> String; - - /// ่Žทๅ–ไผš่ฏ - pub fn get_session(&self, token: &str) -> Option; - - /// ๅˆ ้™คไผš่ฏ - pub fn delete_session(&self, token: &str); - - /// ๆธ…็†่ฟ‡ๆœŸไผš่ฏ - pub fn cleanup_expired(&self); - - /// ๅˆทๆ–ฐไผš่ฏ - pub fn refresh_session(&self, token: &str) -> bool; -} - -pub struct Session { - pub token: String, - pub user_id: String, - pub username: String, - pub role: UserRole, - pub created_at: Instant, - pub last_active: Instant, -} -``` - -### 2.3 ๅฏ†็ ๅ“ˆๅธŒ (password.rs) - -```rust -/// ๅ“ˆๅธŒๅฏ†็  -pub fn hash_password(password: &str) -> Result { - let salt = SaltString::generate(&mut OsRng); - let argon2 = Argon2::default(); - let hash = argon2 - .hash_password(password.as_bytes(), &salt)? - .to_string(); - Ok(hash) -} - -/// ้ชŒ่ฏๅฏ†็  -pub fn verify_password(password: &str, hash: &str) -> Result { - let parsed_hash = PasswordHash::new(hash)?; - Ok(Argon2::default() - .verify_password(password.as_bytes(), &parsed_hash) - .is_ok()) -} -``` - -### 2.4 ่ฎค่ฏไธญ้—ดไปถ (middleware.rs) - -```rust -pub async fn auth_middleware( - State(state): State>, - cookies: Cookies, - request: Request, - next: Next, -) -> Response { - // ่Žทๅ– session token - let token = cookies - .get("session_id") - .map(|c| c.value().to_string()); - - // ้ชŒ่ฏไผš่ฏ - let session = token - .and_then(|t| state.sessions.get_session(&t)); - - if let Some(session) = session { - // ๅฐ†็”จๆˆทไฟกๆฏๆณจๅ…ฅ่ฏทๆฑ‚ - let mut request = request; - request.extensions_mut().insert(session); - next.run(request).await - } else { - StatusCode::UNAUTHORIZED.into_response() - } -} - -pub async fn admin_middleware( - session: Extension, - request: Request, - next: Next, -) -> Response { - if session.role == UserRole::Admin { - next.run(request).await - } else { - StatusCode::FORBIDDEN.into_response() - } -} -``` - ---- - -## 3. API ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆƒ้™ | ๆ่ฟฐ | -|------|------|------|------| -| `/api/auth/login` | POST | Public | ็™ปๅฝ• | -| `/api/auth/logout` | POST | User | ็™ปๅ‡บ | -| `/api/auth/check` | GET | User | ๆฃ€ๆŸฅ่ฎค่ฏ | -| `/api/auth/password` | POST | User | ไฟฎๆ”นๅฏ†็  | -| `/api/users` | GET | Admin | ๅˆ—ๅ‡บ็”จๆˆท | -| `/api/users` | POST | Admin | ๅˆ›ๅปบ็”จๆˆท | -| `/api/users/:id` | DELETE | Admin | ๅˆ ้™ค็”จๆˆท | -| `/api/setup/init` | POST | Public | ๅˆๅง‹ๅŒ–่ฎพ็ฝฎ | - -### ่ฏทๆฑ‚/ๅ“ๅบ”ๆ ผๅผ - -```json -// POST /api/auth/login -// Request: -{ - "username": "admin", - "password": "password123" -} - -// Response: -{ - "user": { - "id": "uuid", - "username": "admin", - "role": "admin" - } -} - -// GET /api/auth/check -{ - "authenticated": true, - "user": { - "id": "uuid", - "username": "admin", - "role": "admin" - } -} -``` - ---- - -## 4. ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct AuthConfig { - /// ไผš่ฏ่ถ…ๆ—ถ (็ง’) - pub session_timeout_secs: u64, - - /// ๆ˜ฏๅฆๅฏ็”จ่ฎค่ฏ - pub enabled: bool, -} - -impl Default for AuthConfig { - fn default() -> Self { - Self { - session_timeout_secs: 86400, // 24 ๅฐๆ—ถ - enabled: true, - } - } -} -``` - ---- - -## 5. ๅฎ‰ๅ…จ็‰นๆ€ง - -### 5.1 ๅฏ†็ ๅญ˜ๅ‚จ - -- Argon2id ๅ“ˆๅธŒ -- ้šๆœบ็›ๅ€ผ -- ไธๅฏ้€† - -### 5.2 ไผš่ฏๅฎ‰ๅ…จ - -- ้šๆœบ token (UUID v4) -- HTTPOnly Cookie -- ไผš่ฏ่ถ…ๆ—ถ -- ่‡ชๅŠจๆธ…็† - -### 5.3 ๆƒ้™ๆŽงๅˆถ - -- ไธค็บงๆƒ้™: Admin / User -- ไธญ้—ดไปถๆฃ€ๆŸฅ -- ๆ•ๆ„Ÿๆ“ไฝœ้œ€ Admin - ---- - -## 6. ไฝฟ็”จ็คบไพ‹ - -```rust -// ๅˆ›ๅปบ็”จๆˆท -let user = users.create_user(&CreateUser { - username: "admin".to_string(), - password: "password123".to_string(), - role: UserRole::Admin, -}).await?; - -// ้ชŒ่ฏๅฏ†็  -if let Some(user) = users.verify_password("admin", "password123").await? { - // ๅˆ›ๅปบไผš่ฏ - let token = sessions.create_session(&user); - - // ่ฎพ็ฝฎ Cookie - cookies.add(Cookie::build("session_id", token) - .http_only(true) - .path("/") - .finish()); -} - -// ่Žทๅ–ไผš่ฏ -if let Some(session) = sessions.get_session(&token) { - println!("User: {}", session.username); -} -``` - ---- - -## 7. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum AuthError { - #[error("Invalid credentials")] - InvalidCredentials, - - #[error("User not found")] - UserNotFound, - - #[error("User already exists")] - UserExists, - - #[error("Session expired")] - SessionExpired, - - #[error("Permission denied")] - PermissionDenied, - - #[error("Setup required")] - SetupRequired, -} -``` diff --git a/docs/modules/config.md b/docs/modules/config.md deleted file mode 100644 index 9e1cf34d..00000000 --- a/docs/modules/config.md +++ /dev/null @@ -1,755 +0,0 @@ -# Config ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Config ๆจกๅ—ๆไพ›้…็ฝฎ็ฎก็†ๅŠŸ่ƒฝ๏ผŒๆ‰€ๆœ‰้…็ฝฎๅญ˜ๅ‚จๅœจ SQLite ๆ•ฐๆฎๅบ“ไธญ๏ผŒไฝฟ็”จ ArcSwap ๅฎž็Žฐๆ— ้”่ฏปๅ–๏ผŒๆไพ›้ซ˜ๆ€ง่ƒฝ้…็ฝฎ่ฎฟ้—ฎใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- SQLite ้…็ฝฎๅญ˜ๅ‚จ๏ผˆๆŒไน…ๅŒ–๏ผ‰ -- ๆ— ้”้…็ฝฎ่ฏปๅ–๏ผˆArcSwap๏ผ‰ -- ็ฑปๅž‹ๅฎ‰ๅ…จ็š„้…็ฝฎ็ป“ๆž„ -- ้…็ฝฎๅ˜ๆ›ด้€š็Ÿฅ๏ผˆbroadcast channel๏ผ‰ -- TypeScript ็ฑปๅž‹็”Ÿๆˆ๏ผˆtypeshare๏ผ‰ -- RESTful API๏ผˆๆŒ‰ๅŠŸ่ƒฝๅŸŸๅˆ†็ฆป๏ผ‰ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/config/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ schema.rs # ้…็ฝฎ็ป“ๆž„ๅฎšไน‰๏ผˆๅŒ…ๅซๆ‰€ๆœ‰ๅญ้…็ฝฎ๏ผ‰ -โ””โ”€โ”€ store.rs # SQLite ๅญ˜ๅ‚จไธŽๆ— ้”็ผ“ๅญ˜ -``` - ---- - -## 2. ๆ ธๅฟƒ็ป„ไปถ - -### 2.1 ConfigStore (store.rs) - -้…็ฝฎๅญ˜ๅ‚จไฝฟ็”จ **ArcSwap** ๅฎž็Žฐๆ— ้”่ฏปๅ–๏ผŒๆไพ›ๆŽฅ่ฟ‘้›ถๆˆๆœฌ็š„้…็ฝฎ่ฎฟ้—ฎๆ€ง่ƒฝ๏ผš - -```rust -pub struct ConfigStore { - pool: Pool, - /// ๆ— ้”็ผ“ๅญ˜๏ผŒไฝฟ็”จ ArcSwap ๅฎž็Žฐ้›ถๆˆๆœฌ่ฏปๅ– - cache: Arc>, - /// ้…็ฝฎๅ˜ๆ›ด้€š็Ÿฅ้€š้“ - change_tx: broadcast::Sender, -} - -impl ConfigStore { - /// ๅˆ›ๅปบๅญ˜ๅ‚จ - pub async fn new(db_path: &Path) -> Result; - - /// ่Žทๅ–ๅฝ“ๅ‰้…็ฝฎ๏ผˆๆ— ้”๏ผŒ้›ถๆ‹ท่ด๏ผ‰ - /// - /// ่ฟ”ๅ›ž Arc๏ผŒ้ซ˜ๆ•ˆๅ…ฑไบซๆ— ้œ€ๅ…‹้š† - /// ่ฟ™ๆ˜ฏไธ€ไธชๆ— ้”ๆ“ไฝœ๏ผŒๅผ€้”€ๆžๅฐ - pub fn get(&self) -> Arc; - - /// ่ฎพ็ฝฎๅฎŒๆ•ด้…็ฝฎ - pub async fn set(&self, config: AppConfig) -> Result<()>; - - /// ไฝฟ็”จ้—ญๅŒ…ๆ›ดๆ–ฐ้…็ฝฎ - /// - /// ่ฏป-ไฟฎๆ”น-ๅ†™ๆจกๅผใ€‚ๅนถๅ‘ๆ›ดๆ–ฐๆ—ถ๏ผŒๆœ€ๅŽ็š„ๅ†™ๅ…ฅ่Žท่ƒœใ€‚ - /// ๅฏนไบŽไธ้ข‘็น็š„็”จๆˆท่งฆๅ‘้…็ฝฎๆ›ดๆ”นๆฅ่ฏดๆ˜ฏๅฏๆŽฅๅ—็š„ใ€‚ - pub async fn update(&self, f: F) -> Result<()> - where - F: FnOnce(&mut AppConfig); - - /// ่ฎข้˜…้…็ฝฎๅ˜ๆ›ดไบ‹ไปถ - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ๆฃ€ๆŸฅ็ณป็ปŸๆ˜ฏๅฆๅทฒๅˆๅง‹ๅŒ–๏ผˆๆ— ้”๏ผ‰ - pub fn is_initialized(&self) -> bool; - - /// ่Žทๅ–ๆ•ฐๆฎๅบ“่ฟžๆŽฅๆฑ ๏ผˆ็”จไบŽไผš่ฏ็ฎก็†๏ผ‰ - pub fn pool(&self) -> &Pool; -} -``` - -**ๆ€ง่ƒฝ็‰น็‚น**๏ผš -- `get()` ๆ˜ฏๆ— ้”่ฏปๅ–ๆ“ไฝœ๏ผŒ่ฟ”ๅ›ž `Arc`๏ผŒๆ— ้œ€ๅ…‹้š† -- ้…็ฝฎ่ฏปๅ–้ข‘็އ่ฟœ้ซ˜ไบŽๅ†™ๅ…ฅ๏ผŒArcSwap ไผ˜ๅŒ–ไบ†่ฏปๅ–่ทฏๅพ„ -- ๅ†™ๅ…ฅๆ“ไฝœๅ…ˆๆŒไน…ๅŒ–ๅˆฐๆ•ฐๆฎๅบ“๏ผŒๅ†ๅŽŸๅญๆ€งๆ›ดๆ–ฐๅ†…ๅญ˜็ผ“ๅญ˜ -- ไฝฟ็”จ broadcast channel ้€š็Ÿฅ้…็ฝฎๅ˜ๆ›ด๏ผŒๆ”ฏๆŒๅคš่ฎข้˜…่€… - -**ๆ•ฐๆฎๅบ“่ฟžๆŽฅๆฑ ้…็ฝฎ**๏ผš -```rust -SqlitePoolOptions::new() - .max_connections(2) // SQLite ๅ•ๅ†™ๆจกๅผ๏ผŒ2 ไธช่ฟžๆŽฅ่ถณๅคŸ - .acquire_timeout(Duration::from_secs(5)) - .idle_timeout(Duration::from_secs(300)) -``` - -### 2.2 AppConfig (schema.rs) - -ไธปๅบ”็”จ้…็ฝฎ็ป“ๆž„๏ผŒๅŒ…ๅซๆ‰€ๆœ‰ๅญ็ณป็ปŸ็š„้…็ฝฎ๏ผš - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct AppConfig { - /// ๅˆๅง‹่ฎพ็ฝฎๆ˜ฏๅฆๅฎŒๆˆ - pub initialized: bool, - - /// ่ฎค่ฏ้…็ฝฎ - pub auth: AuthConfig, - - /// ่ง†้ข‘้‡‡้›†้…็ฝฎ - pub video: VideoConfig, - - /// HID๏ผˆ้”ฎ็›˜/้ผ ๆ ‡๏ผ‰้…็ฝฎ - pub hid: HidConfig, - - /// MSD๏ผˆๅคงๅฎน้‡ๅญ˜ๅ‚จ๏ผ‰้…็ฝฎ - pub msd: MsdConfig, - - /// ATX ็”ตๆบๆŽงๅˆถ้…็ฝฎ - pub atx: AtxConfig, - - /// ้Ÿณ้ข‘้…็ฝฎ - pub audio: AudioConfig, - - /// ๆตๅช’ไฝ“้…็ฝฎ - pub stream: StreamConfig, - - /// Web ๆœๅŠกๅ™จ้…็ฝฎ - pub web: WebConfig, - - /// ๆ‰ฉๅฑ•้…็ฝฎ๏ผˆttyd, gostc, easytier๏ผ‰ - pub extensions: ExtensionsConfig, - - /// RustDesk ่ฟœ็จ‹่ฎฟ้—ฎ้…็ฝฎ - pub rustdesk: RustDeskConfig, -} -``` - -### 2.3 ไธป่ฆๅญ้…็ฝฎ็ป“ๆž„ - -#### AuthConfig - ่ฎค่ฏ้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct AuthConfig { - /// ไผš่ฏ่ถ…ๆ—ถๆ—ถ้—ด๏ผˆ็ง’๏ผ‰ - pub session_timeout_secs: u32, // ้ป˜่ฎค 86400๏ผˆ24ๅฐๆ—ถ๏ผ‰ - /// ๅฏ็”จๅŒๅ› ็ด ่ฎค่ฏ - pub totp_enabled: bool, - /// TOTP ๅฏ†้’ฅ๏ผˆๅŠ ๅฏ†ๅญ˜ๅ‚จ๏ผ‰ - pub totp_secret: Option, -} -``` - -#### VideoConfig - ่ง†้ข‘้‡‡้›†้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(default)] -#[typeshare] -pub struct VideoConfig { - /// ่ง†้ข‘่ฎพๅค‡่ทฏๅพ„๏ผˆๅฆ‚ /dev/video0๏ผ‰ - pub device: Option, - /// ๅƒ็ด ๆ ผๅผ๏ผˆๅฆ‚ "MJPEG", "YUYV", "NV12"๏ผ‰ - pub format: Option, - /// ๅˆ†่พจ็އๅฎฝๅบฆ - pub width: u32, // ้ป˜่ฎค 1920 - /// ๅˆ†่พจ็އ้ซ˜ๅบฆ - pub height: u32, // ้ป˜่ฎค 1080 - /// ๅธง็އ - pub fps: u32, // ้ป˜่ฎค 30 - /// JPEG ่ดจ้‡๏ผˆ1-100๏ผ‰ - pub quality: u32, // ้ป˜่ฎค 80 -} -``` - -#### HidConfig - HID ้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(default)] -#[typeshare] -pub struct HidConfig { - /// HID ๅŽ็ซฏ็ฑปๅž‹ - pub backend: HidBackend, // Otg | Ch9329 | None - /// OTG ้”ฎ็›˜่ฎพๅค‡่ทฏๅพ„ - pub otg_keyboard: String, // ้ป˜่ฎค "/dev/hidg0" - /// OTG ้ผ ๆ ‡่ฎพๅค‡่ทฏๅพ„ - pub otg_mouse: String, // ้ป˜่ฎค "/dev/hidg1" - /// OTG UDC๏ผˆUSB ่ฎพๅค‡ๆŽงๅˆถๅ™จ๏ผ‰ๅ็งฐ - pub otg_udc: Option, - /// OTG USB ่ฎพๅค‡ๆ่ฟฐ็ฌฆ้…็ฝฎ - pub otg_descriptor: OtgDescriptorConfig, - /// CH9329 ไธฒๅฃ่ทฏๅพ„ - pub ch9329_port: String, // ้ป˜่ฎค "/dev/ttyUSB0" - /// CH9329 ๆณข็‰น็އ - pub ch9329_baudrate: u32, // ้ป˜่ฎค 9600 - /// ้ผ ๆ ‡ๆจกๅผ๏ผš็ปๅฏนๅฎšไฝๆˆ–็›ธๅฏนๅฎšไฝ - pub mouse_absolute: bool, // ้ป˜่ฎค true -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[typeshare] -pub struct OtgDescriptorConfig { - pub vendor_id: u16, // ้ป˜่ฎค 0x1d6b๏ผˆLinux Foundation๏ผ‰ - pub product_id: u16, // ้ป˜่ฎค 0x0104 - pub manufacturer: String, // ้ป˜่ฎค "One-KVM" - pub product: String, // ้ป˜่ฎค "One-KVM USB Device" - pub serial_number: Option, -} -``` - -#### StreamConfig - ๆตๅช’ไฝ“้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct StreamConfig { - /// ๆตๆจกๅผ๏ผˆWebRTC | Mjpeg๏ผ‰ - pub mode: StreamMode, - /// ็ผ–็ ๅ™จ็ฑปๅž‹ - pub encoder: EncoderType, // Auto | Software | Vaapi | Nvenc | Qsv | Amf | Rkmpp | V4l2m2m - /// ็ ็އ้ข„่ฎพ๏ผˆSpeed | Balanced | Quality๏ผ‰ - pub bitrate_preset: BitratePreset, - /// ่‡ชๅฎšไน‰ STUN ๆœๅŠกๅ™จ - pub stun_server: Option, // ้ป˜่ฎค "stun:stun.l.google.com:19302" - /// ่‡ชๅฎšไน‰ TURN ๆœๅŠกๅ™จ - pub turn_server: Option, - /// TURN ็”จๆˆทๅ - pub turn_username: Option, - /// TURN ๅฏ†็ ๏ผˆๅŠ ๅฏ†ๅญ˜ๅ‚จ๏ผŒไธ้€š่ฟ‡ API ๆšด้œฒ๏ผ‰ - pub turn_password: Option, - /// ๆ— ๅฎขๆˆท็ซฏๆ—ถ่‡ชๅŠจๆš‚ๅœ - #[typeshare(skip)] - pub auto_pause_enabled: bool, - /// ่‡ชๅŠจๆš‚ๅœๅปถ่ฟŸ๏ผˆ็ง’๏ผ‰ - #[typeshare(skip)] - pub auto_pause_delay_secs: u64, - /// ๅฎขๆˆท็ซฏ่ถ…ๆ—ถๆธ…็†๏ผˆ็ง’๏ผ‰ - #[typeshare(skip)] - pub client_timeout_secs: u64, -} -``` - -#### MsdConfig - ๅคงๅฎน้‡ๅญ˜ๅ‚จ้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct MsdConfig { - /// ๅฏ็”จ MSD ๅŠŸ่ƒฝ - pub enabled: bool, // ้ป˜่ฎค true - /// ISO/IMG ้•œๅƒๅญ˜ๅ‚จ่ทฏๅพ„ - pub images_path: String, // ้ป˜่ฎค "./data/msd/images" - /// Ventoy ๅฏๅŠจ้ฉฑๅŠจๅ™จๆ–‡ไปถ่ทฏๅพ„ - pub drive_path: String, // ้ป˜่ฎค "./data/msd/ventoy.img" - /// ่™šๆ‹Ÿ้ฉฑๅŠจๅ™จๅคงๅฐ๏ผˆMB๏ผŒๆœ€ๅฐ 1024๏ผ‰ - pub virtual_drive_size_mb: u32, // ้ป˜่ฎค 16384๏ผˆ16GB๏ผ‰ -} -``` - -#### AtxConfig - ATX ็”ตๆบๆŽงๅˆถ้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct AtxConfig { - /// ๅฏ็”จ ATX ๅŠŸ่ƒฝ - pub enabled: bool, - /// ็”ตๆบๆŒ‰้’ฎ้…็ฝฎ๏ผˆ็ŸญๆŒ‰ๅ’Œ้•ฟๆŒ‰ๅ…ฑ็”จ๏ผ‰ - pub power: AtxKeyConfig, - /// ้‡็ฝฎๆŒ‰้’ฎ้…็ฝฎ - pub reset: AtxKeyConfig, - /// LED ๆฃ€ๆต‹้…็ฝฎ๏ผˆๅฏ้€‰๏ผ‰ - pub led: AtxLedConfig, - /// WOL ๆ•ฐๆฎๅŒ…ไฝฟ็”จ็š„็ฝ‘็ปœๆŽฅๅฃ๏ผˆ็ฉบๅญ—็ฌฆไธฒ = ่‡ชๅŠจ๏ผ‰ - pub wol_interface: String, -} -``` - -#### AudioConfig - ้Ÿณ้ข‘้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct AudioConfig { - /// ๅฏ็”จ้Ÿณ้ข‘้‡‡้›† - pub enabled: bool, // ้ป˜่ฎค false - /// ALSA ่ฎพๅค‡ๅ็งฐ - pub device: String, // ้ป˜่ฎค "default" - /// ้Ÿณ้ข‘่ดจ้‡้ข„่ฎพ๏ผš"voice" | "balanced" | "high" - pub quality: String, // ้ป˜่ฎค "balanced" -} -``` - -**ๆณจๆ„**๏ผš้‡‡ๆ ท็އๅ›บๅฎšไธบ 48000Hz๏ผŒๅฃฐ้“ๅ›บๅฎšไธบ 2๏ผˆ็ซ‹ไฝ“ๅฃฐ๏ผ‰๏ผŒ่ฟ™ๆ˜ฏ Opus ็ผ–็ ๅ’Œ WebRTC ็š„ๆœ€ไฝณ้…็ฝฎใ€‚ - -#### WebConfig - Web ๆœๅŠกๅ™จ้…็ฝฎ - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(default)] -#[typeshare] -pub struct WebConfig { - /// HTTP ็ซฏๅฃ - pub http_port: u16, // ้ป˜่ฎค 8080 - /// HTTPS ็ซฏๅฃ - pub https_port: u16, // ้ป˜่ฎค 8443 - /// ็ป‘ๅฎšๅœฐๅ€ - pub bind_address: String, // ้ป˜่ฎค "0.0.0.0" - /// ๅฏ็”จ HTTPS - pub https_enabled: bool, // ้ป˜่ฎค false - /// ่‡ชๅฎšไน‰ SSL ่ฏไนฆ่ทฏๅพ„ - pub ssl_cert_path: Option, - /// ่‡ชๅฎšไน‰ SSL ๅฏ†้’ฅ่ทฏๅพ„ - pub ssl_key_path: Option, -} -``` - ---- - -## 3. TypeScript ็ฑปๅž‹็”Ÿๆˆ - -ไฝฟ็”จ `#[typeshare]` ๅฑžๆ€ง่‡ชๅŠจ็”Ÿๆˆ TypeScript ็ฑปๅž‹๏ผš - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct VideoConfig { - pub device: Option, - pub width: u32, - pub height: u32, -} -``` - -็”Ÿๆˆ็š„ TypeScript๏ผš - -```typescript -export interface VideoConfig { - device?: string; - width: number; - height: number; -} -``` - -็”Ÿๆˆๅ‘ฝไปค๏ผš - -```bash -./scripts/generate-types.sh -# ๆˆ– -typeshare src --lang=typescript --output-file=web/src/types/generated.ts -``` - ---- - -## 4. API ็ซฏ็‚น - -ๆ‰€ๆœ‰้…็ฝฎ็ซฏ็‚นๅ‡้œ€่ฆ **Admin** ๆƒ้™๏ผŒ้‡‡็”จ RESTful ่ฎพ่ฎก๏ผŒๆŒ‰ๅŠŸ่ƒฝๅŸŸๅˆ†็ฆปใ€‚ - -### 4.1 ๅ…จๅฑ€้…็ฝฎ - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆƒ้™ | ๆ่ฟฐ | -|------|------|------|------| -| `/api/config` | GET | Admin | ่Žทๅ–ๅฎŒๆ•ด้…็ฝฎ๏ผˆๆ•ๆ„Ÿไฟกๆฏๅทฒ่ฟ‡ๆปค๏ผ‰ | -| `/api/config` | POST | Admin | ๆ›ดๆ–ฐๅฎŒๆ•ด้…็ฝฎ๏ผˆๅทฒๅบŸๅผƒ๏ผŒ่ฏทไฝฟ็”จๆŒ‰ๅŸŸ PATCH๏ผ‰ | - -### 4.2 ๅˆ†ๅŸŸ้…็ฝฎ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆƒ้™ | ๆ่ฟฐ | -|------|------|------|------| -| `/api/config/video` | GET | Admin | ่Žทๅ–่ง†้ข‘้…็ฝฎ | -| `/api/config/video` | PATCH | Admin | ๆ›ดๆ–ฐ่ง†้ข‘้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/stream` | GET | Admin | ่Žทๅ–ๆต้…็ฝฎ | -| `/api/config/stream` | PATCH | Admin | ๆ›ดๆ–ฐๆต้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/hid` | GET | Admin | ่Žทๅ– HID ้…็ฝฎ | -| `/api/config/hid` | PATCH | Admin | ๆ›ดๆ–ฐ HID ้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/msd` | GET | Admin | ่Žทๅ– MSD ้…็ฝฎ | -| `/api/config/msd` | PATCH | Admin | ๆ›ดๆ–ฐ MSD ้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/atx` | GET | Admin | ่Žทๅ– ATX ้…็ฝฎ | -| `/api/config/atx` | PATCH | Admin | ๆ›ดๆ–ฐ ATX ้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/audio` | GET | Admin | ่Žทๅ–้Ÿณ้ข‘้…็ฝฎ | -| `/api/config/audio` | PATCH | Admin | ๆ›ดๆ–ฐ้Ÿณ้ข‘้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | -| `/api/config/web` | GET | Admin | ่Žทๅ– Web ๆœๅŠกๅ™จ้…็ฝฎ | -| `/api/config/web` | PATCH | Admin | ๆ›ดๆ–ฐ Web ๆœๅŠกๅ™จ้…็ฝฎ๏ผˆ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผ‰ | - -### 4.3 RustDesk ้…็ฝฎ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆƒ้™ | ๆ่ฟฐ | -|------|------|------|------| -| `/api/config/rustdesk` | GET | Admin | ่Žทๅ– RustDesk ้…็ฝฎ | -| `/api/config/rustdesk` | PATCH | Admin | ๆ›ดๆ–ฐ RustDesk ้…็ฝฎ | -| `/api/config/rustdesk/status` | GET | Admin | ่Žทๅ– RustDesk ๆœๅŠก็Šถๆ€ | -| `/api/config/rustdesk/password` | GET | Admin | ่Žทๅ–่ฎพๅค‡ๅฏ†็  | -| `/api/config/rustdesk/regenerate-id` | POST | Admin | ้‡ๆ–ฐ็”Ÿๆˆ่ฎพๅค‡ ID | -| `/api/config/rustdesk/regenerate-password` | POST | Admin | ้‡ๆ–ฐ็”Ÿๆˆ่ฎพๅค‡ๅฏ†็  | - -### 4.4 ่ฏทๆฑ‚/ๅ“ๅบ”็คบไพ‹ - -#### ่Žทๅ–่ง†้ข‘้…็ฝฎ - -```bash -GET /api/config/video -``` - -ๅ“ๅบ”๏ผš -```json -{ - "device": "/dev/video0", - "format": "MJPEG", - "width": 1920, - "height": 1080, - "fps": 30, - "quality": 80 -} -``` - -#### ้ƒจๅˆ†ๆ›ดๆ–ฐ่ง†้ข‘้…็ฝฎ - -```bash -PATCH /api/config/video -Content-Type: application/json - -{ - "width": 1280, - "height": 720, - "fps": 60 -} -``` - -ๅ“ๅบ”๏ผšๆ›ดๆ–ฐๅŽ็š„ๅฎŒๆ•ด VideoConfig -```json -{ - "device": "/dev/video0", - "format": "MJPEG", - "width": 1280, - "height": 720, - "fps": 60, - "quality": 80 -} -``` - -**ๆณจๆ„**๏ผš -- ๆ‰€ๆœ‰ PATCH ่ฏทๆฑ‚้ƒฝๆ”ฏๆŒ้ƒจๅˆ†ๆ›ดๆ–ฐ๏ผŒๅช้œ€่ฆๆไพ›่ฆไฟฎๆ”น็š„ๅญ—ๆฎต -- ๆœชๆไพ›็š„ๅญ—ๆฎตไฟๆŒๅŽŸๆœ‰ๅ€ผไธๅ˜ -- ๆ›ดๆ–ฐๅŽ่ฟ”ๅ›žๅฎŒๆ•ด็š„้…็ฝฎๅฏน่ฑก -- ้…็ฝฎๅ˜ๆ›ดไผš่‡ชๅŠจ่งฆๅ‘็›ธๅ…ณ็ป„ไปถ้‡่ฝฝ - ---- - -## 5. ้…็ฝฎๅ˜ๆ›ด้€š็Ÿฅ - -ConfigStore ๆไพ› broadcast channel ็”จไบŽ้…็ฝฎๅ˜ๆ›ด้€š็Ÿฅ๏ผš - -```rust -/// ้…็ฝฎๅ˜ๆ›ดไบ‹ไปถ -#[derive(Debug, Clone)] -pub struct ConfigChange { - pub key: String, -} - -// ่ฎข้˜…้…็ฝฎๅ˜ๆ›ด -let mut rx = config_store.subscribe(); - -// ็›‘ๅฌๅ˜ๆ›ดไบ‹ไปถ -while let Ok(change) = rx.recv().await { - println!("้…็ฝฎ {} ๅทฒๆ›ดๆ–ฐ", change.key); - // ้‡่ฝฝ็›ธๅ…ณ็ป„ไปถ -} -``` - -**ๅทฅไฝœๆต็จ‹**๏ผš -1. ่ฐƒ็”จ `config_store.set()` ๆˆ– `config_store.update()` -2. ้…็ฝฎๅ†™ๅ…ฅๆ•ฐๆฎๅบ“๏ผˆๆŒไน…ๅŒ–๏ผ‰ -3. ๅŽŸๅญๆ€งๆ›ดๆ–ฐๅ†…ๅญ˜็ผ“ๅญ˜๏ผˆArcSwap๏ผ‰ -4. ๅ‘้€ `ConfigChange` ไบ‹ไปถๅˆฐ broadcast channel -5. ๅ„็ป„ไปถ็š„่ฎข้˜…่€…ๆŽฅๆ”ถไบ‹ไปถๅนถๆ‰ง่กŒ้‡่ฝฝ้€ป่พ‘ - -**็ป„ไปถ้‡่ฝฝ็คบไพ‹**๏ผš -```rust -// VideoStreamManager ็›‘ๅฌ้…็ฝฎๅ˜ๆ›ด -let mut config_rx = config_store.subscribe(); -tokio::spawn(async move { - while let Ok(change) = config_rx.recv().await { - if change.key == "app_config" { - video_manager.reload().await; - } - } -}); -``` - ---- - -## 6. ๆ•ฐๆฎๅบ“็ป“ๆž„ - -ConfigStore ไฝฟ็”จ SQLite ๅญ˜ๅ‚จ้…็ฝฎๅ’Œๅ…ถไป–็ณป็ปŸๆ•ฐๆฎ๏ผš - -### 6.1 ้…็ฝฎ่กจ - -```sql -CREATE TABLE IF NOT EXISTS config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - updated_at TEXT NOT NULL DEFAULT (datetime('now')) -); -``` - -้…็ฝฎไปฅ JSON ๆ ผๅผๅญ˜ๅ‚จ๏ผš -```sql --- ๅบ”็”จ้…็ฝฎ -key: 'app_config' -value: '{"initialized": true, "video": {...}, "hid": {...}, ...}' -``` - -### 6.2 ็”จๆˆท่กจ - -```sql -CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - password_hash TEXT NOT NULL, - is_admin INTEGER NOT NULL DEFAULT 0, - created_at TEXT NOT NULL DEFAULT (datetime('now')), - updated_at TEXT NOT NULL DEFAULT (datetime('now')) -); -``` - -### 6.3 ไผš่ฏ่กจ - -```sql -CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - created_at TEXT NOT NULL DEFAULT (datetime('now')), - expires_at TEXT NOT NULL, - data TEXT -); -``` - -### 6.4 API ไปค็‰Œ่กจ - -```sql -CREATE TABLE IF NOT EXISTS api_tokens ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - token_hash TEXT NOT NULL, - permissions TEXT NOT NULL, - expires_at TEXT, - created_at TEXT NOT NULL DEFAULT (datetime('now')), - last_used TEXT -); -``` - -**ๅญ˜ๅ‚จ็‰น็‚น**๏ผš -- ๆ‰€ๆœ‰้…็ฝฎๅญ˜ๅ‚จๅœจๅ•ไธช JSON ๆ–‡ๆœฌไธญ๏ผˆ`app_config` key๏ผ‰ -- ๆฏๆฌก้…็ฝฎๆ›ดๆ–ฐ้ƒฝๆ›ดๆ–ฐๆ•ดไธช JSON๏ผŒ็ฎ€ๅŒ–ไบ‹ๅŠกๅค„็† -- ไฝฟ็”จ `ON CONFLICT` ๅฎž็Žฐ upsert ๆ“ไฝœ -- ่ฟžๆŽฅๆฑ ๅคงๅฐไธบ 2๏ผˆ1 ่ฏป + 1 ๅ†™๏ผ‰๏ผŒ้€‚ๅˆๅตŒๅ…ฅๅผ็Žฏๅขƒ - ---- - -## 7. ไฝฟ็”จ็คบไพ‹ - -### 7.1 ๅŸบๆœฌ็”จๆณ• - -```rust -use crate::config::ConfigStore; -use std::path::Path; - -// ๅˆ›ๅปบ้…็ฝฎๅญ˜ๅ‚จ -let config_store = ConfigStore::new(Path::new("./data/config.db")).await?; - -// ่Žทๅ–้…็ฝฎ๏ผˆๆ— ้”๏ผŒ้›ถๆ‹ท่ด๏ผ‰ -let config = config_store.get(); -println!("่ง†้ข‘่ฎพๅค‡: {:?}", config.video.device); -println!("ๆ˜ฏๅฆๅทฒๅˆๅง‹ๅŒ–: {}", config.initialized); - -// ๆฃ€ๆŸฅๆ˜ฏๅฆๅทฒๅˆๅง‹ๅŒ– -if !config_store.is_initialized() { - println!("็ณป็ปŸๅฐšๆœชๅˆๅง‹ๅŒ–๏ผŒ่ฏทๅฎŒๆˆๅˆๅง‹่ฎพ็ฝฎ"); -} -``` - -### 7.2 ๆ›ดๆ–ฐ้…็ฝฎ - -```rust -// ๆ–นๅผ 1: ไฝฟ็”จ้—ญๅŒ…ๆ›ดๆ–ฐ๏ผˆๆŽจ่๏ผ‰ -config_store.update(|config| { - config.video.width = 1280; - config.video.height = 720; - config.video.fps = 60; -}).await?; - -// ๆ–นๅผ 2: ๆ•ดไฝ“ๆ›ฟๆข -let mut new_config = (*config_store.get()).clone(); -new_config.stream.mode = StreamMode::WebRTC; -new_config.stream.encoder = EncoderType::Rkmpp; -config_store.set(new_config).await?; -``` - -### 7.3 ่ฎข้˜…้…็ฝฎๅ˜ๆ›ด - -```rust -// ๅœจ็ป„ไปถไธญ็›‘ๅฌ้…็ฝฎๅ˜ๆ›ด -let config_store = state.config.clone(); -let mut rx = config_store.subscribe(); - -tokio::spawn(async move { - while let Ok(change) = rx.recv().await { - tracing::info!("้…็ฝฎ {} ๅทฒๅ˜ๆ›ด", change.key); - - // ้‡ๆ–ฐๅŠ ่ฝฝ้…็ฝฎ - let config = config_store.get(); - - // ๆ‰ง่กŒ้‡่ฝฝ้€ป่พ‘ - if let Err(e) = reload_component(&config).await { - tracing::error!("้‡่ฝฝ็ป„ไปถๅคฑ่ดฅ: {}", e); - } - } -}); -``` - -### 7.4 ๅœจ Handler ไธญไฝฟ็”จ - -```rust -use axum::{extract::State, Json}; -use std::sync::Arc; - -use crate::config::VideoConfig; -use crate::state::AppState; - -// ่Žทๅ–่ง†้ข‘้…็ฝฎ -pub async fn get_video_config( - State(state): State> -) -> Json { - let config = state.config.get(); - Json(config.video.clone()) -} - -// ๆ›ดๆ–ฐ่ง†้ข‘้…็ฝฎ -pub async fn update_video_config( - State(state): State>, - Json(update): Json, -) -> Result> { - // ๆ›ดๆ–ฐ้…็ฝฎ - state.config.update(|config| { - config.video = update; - }).await?; - - // ่ฟ”ๅ›žๆ›ดๆ–ฐๅŽ็š„้…็ฝฎ - let config = state.config.get(); - Ok(Json(config.video.clone())) -} -``` - -### 7.5 ่ฎฟ้—ฎๆ•ฐๆฎๅบ“่ฟžๆŽฅๆฑ  - -```rust -// ConfigStore ่ฟ˜ๆไพ›ๆ•ฐๆฎๅบ“่ฟžๆŽฅๆฑ ่ฎฟ้—ฎ -// ็”จไบŽ็”จๆˆท็ฎก็†ใ€ไผš่ฏ็ฎก็†็ญ‰ๅŠŸ่ƒฝ - -let pool = config_store.pool(); - -// ๆŸฅ่ฏข็”จๆˆท -let user: Option = sqlx::query_as( - "SELECT * FROM users WHERE username = ?" -) -.bind(username) -.fetch_optional(pool) -.await?; -``` - ---- - -## 8. ้ป˜่ฎค้…็ฝฎ - -็ณป็ปŸ้ฆ–ๆฌก่ฟ่กŒๆ—ถไผš่‡ชๅŠจๅˆ›ๅปบ้ป˜่ฎค้…็ฝฎ๏ผš - -```rust -impl Default for AppConfig { - fn default() -> Self { - Self { - initialized: false, // ้œ€่ฆ้€š่ฟ‡ๅˆๅง‹่ฎพ็ฝฎๅ‘ๅฏผๅฎŒๆˆ - auth: AuthConfig { - session_timeout_secs: 86400, // 24ๅฐๆ—ถ - totp_enabled: false, - totp_secret: None, - }, - video: VideoConfig { - device: None, // ่‡ชๅŠจๆฃ€ๆต‹ - format: None, // ่‡ชๅŠจๆฃ€ๆต‹ๆˆ–ไฝฟ็”จ MJPEG - width: 1920, - height: 1080, - fps: 30, - quality: 80, - }, - stream: StreamConfig { - mode: StreamMode::Mjpeg, - encoder: EncoderType::Auto, - bitrate_preset: BitratePreset::Balanced, - stun_server: Some("stun:stun.l.google.com:19302".to_string()), - turn_server: None, - turn_username: None, - turn_password: None, - auto_pause_enabled: false, - auto_pause_delay_secs: 10, - client_timeout_secs: 30, - }, - hid: HidConfig { - backend: HidBackend::None, // ้œ€่ฆ็”จๆˆทๆ‰‹ๅŠจๅฏ็”จ - otg_keyboard: "/dev/hidg0".to_string(), - otg_mouse: "/dev/hidg1".to_string(), - otg_udc: None, // ่‡ชๅŠจๆฃ€ๆต‹ - otg_descriptor: OtgDescriptorConfig::default(), - ch9329_port: "/dev/ttyUSB0".to_string(), - ch9329_baudrate: 9600, - mouse_absolute: true, - }, - msd: MsdConfig { - enabled: true, - images_path: "./data/msd/images".to_string(), - drive_path: "./data/msd/ventoy.img".to_string(), - virtual_drive_size_mb: 16384, // 16GB - }, - atx: AtxConfig { - enabled: false, // ้œ€่ฆ็”จๆˆท้…็ฝฎ็กฌไปถ็ป‘ๅฎš - power: AtxKeyConfig::default(), - reset: AtxKeyConfig::default(), - led: AtxLedConfig::default(), - wol_interface: String::new(), // ่‡ชๅŠจๆฃ€ๆต‹ - }, - audio: AudioConfig { - enabled: false, - device: "default".to_string(), - quality: "balanced".to_string(), - }, - web: WebConfig { - http_port: 8080, - https_port: 8443, - bind_address: "0.0.0.0".to_string(), - https_enabled: false, - ssl_cert_path: None, - ssl_key_path: None, - }, - extensions: ExtensionsConfig::default(), - rustdesk: RustDeskConfig::default(), - } - } -} -``` - -**้…็ฝฎๅˆๅง‹ๅŒ–ๆต็จ‹**๏ผš -1. ็”จๆˆท้ฆ–ๆฌก่ฎฟ้—ฎ Web UI๏ผŒ็ณป็ปŸๆฃ€ๆต‹ๅˆฐ `initialized = false` -2. ้‡ๅฎšๅ‘ๅˆฐๅˆๅง‹่ฎพ็ฝฎๅ‘ๅฏผ๏ผˆ`/setup`๏ผ‰ -3. ็”จๆˆท่ฎพ็ฝฎ็ฎก็†ๅ‘˜่ดฆๆˆทใ€้€‰ๆ‹ฉ่ง†้ข‘่ฎพๅค‡็ญ‰ -4. ๅฎŒๆˆ่ฎพ็ฝฎๅŽ๏ผŒ`initialized` ่ฎพไธบ `true` -5. ๅŽ็ปญๅฏ้€š่ฟ‡่ฎพ็ฝฎ้กต้ข๏ผˆ`/settings`๏ผ‰ไฟฎๆ”นๅ„้กน้…็ฝฎ diff --git a/docs/modules/events.md b/docs/modules/events.md deleted file mode 100644 index 8aa28d0d..00000000 --- a/docs/modules/events.md +++ /dev/null @@ -1,353 +0,0 @@ -# Events ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Events ๆจกๅ—ๆไพ›ไบ‹ไปถๆ€ป็บฟๅŠŸ่ƒฝ๏ผŒ็”จไบŽๆจกๅ—้—ด้€šไฟกๅ’Œ็Šถๆ€ๅนฟๆ’ญใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ไบ‹ไปถๅ‘ๅธƒ/่ฎข้˜… -- ๅคš่ฎข้˜…่€…ๅนฟๆ’ญ -- WebSocket ไบ‹ไปถๆŽจ้€ -- ็Šถๆ€ๅ˜ๆ›ด้€š็Ÿฅ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/events/ -โ””โ”€โ”€ mod.rs # EventBus ๅฎž็Žฐ -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Event System โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Video โ”‚ โ”‚ HID โ”‚ โ”‚ Audio โ”‚ -โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ”‚ publish() โ”‚ publish() โ”‚ publish() - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ EventBus โ”‚ - โ”‚ (broadcast channel) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ”‚ subscribe() โ”‚ subscribe() โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ WebSocket โ”‚ โ”‚ DeviceInfo โ”‚ โ”‚ Internal โ”‚ -โ”‚ Handler โ”‚ โ”‚ Broadcaster โ”‚ โ”‚ Tasks โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 EventBus - -```rust -pub struct EventBus { - /// ๅนฟๆ’ญๅ‘้€ๅ™จ - tx: broadcast::Sender, -} - -impl EventBus { - /// ๅˆ›ๅปบไบ‹ไปถๆ€ป็บฟ - pub fn new() -> Self { - let (tx, _) = broadcast::channel(1024); - Self { tx } - } - - /// ๅ‘ๅธƒไบ‹ไปถ - pub fn publish(&self, event: SystemEvent) { - let _ = self.tx.send(event); - } - - /// ่ฎข้˜…ไบ‹ไปถ - pub fn subscribe(&self) -> broadcast::Receiver { - self.tx.subscribe() - } -} -``` - -### 3.2 SystemEvent - -```rust -#[derive(Clone, Debug, Serialize)] -pub enum SystemEvent { - // ่ง†้ข‘ไบ‹ไปถ - StreamStateChanged { - state: String, - device: Option, - resolution: Option, - fps: Option, - }, - - VideoDeviceChanged { - added: Vec, - removed: Vec, - }, - - // HID ไบ‹ไปถ - HidStateChanged { - backend: String, - initialized: bool, - keyboard_connected: bool, - mouse_connected: bool, - mouse_mode: String, - error: Option, - }, - - // MSD ไบ‹ไปถ - MsdStateChanged { - mode: String, - connected: bool, - image: Option, - error: Option, - }, - - MsdDownloadProgress { - download_id: String, - downloaded: u64, - total: u64, - speed: u64, - }, - - // ATX ไบ‹ไปถ - AtxStateChanged { - power_on: bool, - last_action: Option, - error: Option, - }, - - // ้Ÿณ้ข‘ไบ‹ไปถ - AudioStateChanged { - enabled: bool, - streaming: bool, - device: Option, - error: Option, - }, - - // ้…็ฝฎไบ‹ไปถ - ConfigChanged { - section: String, - }, - - // ่ฎพๅค‡ไฟกๆฏๆฑ‡ๆ€ป - DeviceInfo { - video: VideoInfo, - hid: HidInfo, - msd: MsdInfo, - atx: AtxInfo, - audio: AudioInfo, - }, - - // ็ณป็ปŸ้”™่ฏฏ - SystemError { - module: String, - severity: String, - message: String, - }, - - // RustDesk ไบ‹ไปถ - RustDeskStatusChanged { - status: String, - device_id: Option, - error: Option, - }, - - RustDeskConnectionOpened { - connection_id: String, - peer_id: String, - }, - - RustDeskConnectionClosed { - connection_id: String, - peer_id: String, - reason: String, - }, -} -``` - ---- - -## 4. ่ฎพๅค‡ไฟกๆฏๅนฟๆ’ญๅ™จ - -ๅœจ `main.rs` ไธญๅฏๅŠจ็š„ๅŽๅฐไปปๅŠก๏ผš - -```rust -pub fn spawn_device_info_broadcaster( - state: Arc, - events: Arc, -) -> JoinHandle<()> { - tokio::spawn(async move { - let mut rx = events.subscribe(); - let mut debounce = tokio::time::interval(Duration::from_millis(100)); - let mut pending = false; - - loop { - tokio::select! { - // ๆ”ถๅˆฐไบ‹ไปถ - result = rx.recv() => { - if result.is_ok() { - pending = true; - } - } - - // ้˜ฒๆŠ–ๅฎšๆ—ถๅ™จ - _ = debounce.tick() => { - if pending { - pending = false; - // ๆ”ถ้›†่ฎพๅค‡ไฟกๆฏ - let device_info = state.get_device_info().await; - // ๅนฟๆ’ญ - events.publish(SystemEvent::DeviceInfo(device_info)); - } - } - } - } - }) -} -``` - ---- - -## 5. WebSocket ไบ‹ไปถๆŽจ้€ - -```rust -pub async fn ws_handler( - ws: WebSocketUpgrade, - State(state): State>, -) -> impl IntoResponse { - ws.on_upgrade(|socket| handle_ws(socket, state)) -} - -async fn handle_ws(mut socket: WebSocket, state: Arc) { - let mut rx = state.events.subscribe(); - - loop { - tokio::select! { - // ๅ‘้€ไบ‹ไปถ็ป™ๅฎขๆˆท็ซฏ - result = rx.recv() => { - if let Ok(event) = result { - let json = serde_json::to_string(&event).unwrap(); - if socket.send(Message::Text(json)).await.is_err() { - break; - } - } - } - - // ๆŽฅๆ”ถๅฎขๆˆท็ซฏๆถˆๆฏ - msg = socket.recv() => { - match msg { - Some(Ok(Message::Close(_))) | None => break, - _ => {} - } - } - } - } -} -``` - ---- - -## 6. ไฝฟ็”จ็คบไพ‹ - -### 6.1 ๅ‘ๅธƒไบ‹ไปถ - -```rust -// ่ง†้ข‘ๆจกๅ—ๅ‘ๅธƒ็Šถๆ€ๅ˜ๆ›ด -events.publish(SystemEvent::StreamStateChanged { - state: "streaming".to_string(), - device: Some("/dev/video0".to_string()), - resolution: Some(Resolution { width: 1920, height: 1080 }), - fps: Some(30.0), -}); - -// HID ๆจกๅ—ๅ‘ๅธƒ็Šถๆ€ๅ˜ๆ›ด -events.publish(SystemEvent::HidStateChanged { - backend: "otg".to_string(), - initialized: true, - keyboard_connected: true, - mouse_connected: true, - mouse_mode: "absolute".to_string(), - error: None, -}); -``` - -### 6.2 ่ฎข้˜…ไบ‹ไปถ - -```rust -let mut rx = events.subscribe(); - -loop { - match rx.recv().await { - Ok(SystemEvent::StreamStateChanged { state, .. }) => { - println!("Stream state: {}", state); - } - Ok(SystemEvent::HidStateChanged { backend, .. }) => { - println!("HID backend: {}", backend); - } - Err(_) => break, - } -} -``` - ---- - -## 7. ๅ‰็ซฏไบ‹ไปถๅค„็† - -```typescript -// ่ฟžๆŽฅ WebSocket -const ws = new WebSocket('/api/ws'); - -ws.onmessage = (event) => { - const data = JSON.parse(event.data); - - switch (data.type) { - case 'StreamStateChanged': - updateStreamStatus(data); - break; - case 'HidStateChanged': - updateHidStatus(data); - break; - case 'MsdStateChanged': - updateMsdStatus(data); - break; - case 'DeviceInfo': - updateAllDevices(data); - break; - } -}; -``` - ---- - -## 8. ๆœ€ไฝณๅฎž่ทต - -### 8.1 ไบ‹ไปถ็ฒ’ๅบฆ - -- ไฝฟ็”จ็ป†็ฒ’ๅบฆไบ‹ไปถไพฟไบŽ็ฒพ็กฎๆ›ดๆ–ฐ -- DeviceInfo ็”จไบŽๅˆๅง‹ๅŒ–ๅ’ŒๅฎšๆœŸๅŒๆญฅ - -### 8.2 ้˜ฒๆŠ– - -- ไฝฟ็”จ 100ms ้˜ฒๆŠ–้ฟๅ…ไบ‹ไปถ้ฃŽๆšด -- ๅˆๅนถๅคšไธชๅฟซ้€Ÿๅ˜ๆ›ด - -### 8.3 ้”™่ฏฏๅค„็† - -- ๅ‘ๅธƒๅคฑ่ดฅ้™้ป˜ๅฟฝ็•ฅ (fire-and-forget) -- ่ฎข้˜…่€…ๆ–ญๅผ€่‡ชๅŠจๆธ…็† diff --git a/docs/modules/hid.md b/docs/modules/hid.md deleted file mode 100644 index 5ff9c926..00000000 --- a/docs/modules/hid.md +++ /dev/null @@ -1,1153 +0,0 @@ -# HID ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -HID (Human Interface Device) ๆจกๅ—่ดŸ่ดฃๅฐ†้”ฎ็›˜ๅ’Œ้ผ ๆ ‡ไบ‹ไปถ่ฝฌๅ‘ๅˆฐ็›ฎๆ ‡่ฎก็ฎ—ๆœบ๏ผŒๆ˜ฏ One-KVM ๅฎž็Žฐ่ฟœ็จ‹ๆŽงๅˆถ็š„ๆ ธๅฟƒๆจกๅ—ใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ้”ฎ็›˜ไบ‹ไปถๅค„็† (ๆŒ‰้”ฎใ€ไฟฎ้ฅฐ้”ฎ) -- ้ผ ๆ ‡ไบ‹ไปถๅค„็† (็งปๅŠจใ€็‚นๅ‡ปใ€ๆปš่ฝฎ) -- ๅคšๅช’ไฝ“้”ฎๆ”ฏๆŒ (Consumer Control) -- ๆ”ฏๆŒ็ปๅฏนๅ’Œ็›ธๅฏน้ผ ๆ ‡ๆจกๅผ -- ๅคšๅŽ็ซฏๆ”ฏๆŒ (OTGใ€CH9329) -- WebSocket ๅ’Œ DataChannel ่พ“ๅ…ฅ -- ่‡ชๅŠจ้”™่ฏฏๆขๅคๅ’Œๅฅๅบท็›‘ๆŽง - -### 1.2 USB Endpoint ไฝฟ็”จ - -OTG ๆจกๅผไธ‹็š„ endpoint ๅˆ†้…๏ผš - -| ๅŠŸ่ƒฝ | IN ็ซฏ็‚น | OUT ็ซฏ็‚น | ่ฏดๆ˜Ž | -|------|---------|----------|------| -| Keyboard | 1 | 1 | ๅธฆ LED ๅ้ฆˆ | -| MouseRelative | 1 | 0 | ็›ธๅฏน้ผ ๆ ‡ | -| MouseAbsolute | 1 | 0 | ็ปๅฏน้ผ ๆ ‡ | -| ConsumerControl | 1 | 0 | ๅคšๅช’ไฝ“้”ฎ | -| **HID ๆ€ป่ฎก** | **4** | **1** | | -| MSD | 1 | 1 | ๅคงๅฎน้‡ๅญ˜ๅ‚จ | -| **ๅ…จ้ƒจๆ€ป่ฎก** | **5** | **2** | ๅ…ผๅฎน 6 endpoint ่ฎพๅค‡ | - -> ๆณจ๏ผšEP0 (ๆŽงๅˆถ็ซฏ็‚น) ็‹ฌ็ซ‹ไบŽๆ•ฐๆฎ็ซฏ็‚น๏ผŒไธ่ฎกๅ…ฅไธŠ่ฟฐ็ปŸ่ฎกใ€‚ - -### 1.3 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/hid/ -โ”œโ”€โ”€ mod.rs # HidController ไธปๆŽงๅˆถๅ™จ -โ”œโ”€โ”€ backend.rs # HidBackend trait ๅ’Œ HidBackendType -โ”œโ”€โ”€ otg.rs # OTG USB Gadget ๅŽ็ซฏๅฎž็Žฐ -โ”œโ”€โ”€ ch9329.rs # CH9329 ไธฒๅฃ HID ๆŽงๅˆถๅ™จๅŽ็ซฏ -โ”œโ”€โ”€ consumer.rs # Consumer Control usage codes -โ”œโ”€โ”€ keymap.rs # JS keyCode ๅˆฐ USB HID ็š„่ฝฌๆข่กจ -โ”œโ”€โ”€ types.rs # ไบ‹ไปถ็ฑปๅž‹ๅฎšไน‰ (KeyboardEvent, MouseEvent็ญ‰) -โ”œโ”€โ”€ monitor.rs # ๅฅๅบท็›‘่ง†ๅ™จ (HidHealthMonitor) -โ”œโ”€โ”€ datachannel.rs # DataChannel ไบŒ่ฟ›ๅˆถๅ่ฎฎ่งฃๆž -โ””โ”€โ”€ websocket.rs # WebSocket ไบŒ่ฟ›ๅˆถๅ่ฎฎ้€‚้… -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ๆ•ดไฝ“ๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HID Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - Browser Input Events - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ WebSocket โ”‚ โ”‚ DataChannel โ”‚ -โ”‚ Handler โ”‚ โ”‚ Handler โ”‚ -โ”‚ (websocket.rs) โ”‚ โ”‚(datachannel.rs) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ HidController โ”‚ - โ”‚ (mod.rs) โ”‚ - โ”‚ - send_keyboard() โ”‚ - โ”‚ - send_mouse() โ”‚ - โ”‚ - send_consumer() โ”‚ - โ”‚ - monitor (health) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ OTG Backendโ”‚ โ”‚ CH9329 โ”‚ โ”‚ None โ”‚ -โ”‚ (otg.rs) โ”‚ โ”‚ Backend โ”‚ โ”‚ (dummy) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ /dev/hidg* โ”‚ โ”‚ Serial โ”‚ -โ”‚ USB Gadget โ”‚ โ”‚ Port โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Target PC โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ๅŽ็ซฏ้€‰ๆ‹ฉ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Backend Selection โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -HidBackendType::Otg - โ”‚ - โ”œโ”€โ”€ ๆฃ€ๆŸฅ OtgService ๆ˜ฏๅฆๅฏ็”จ - โ”‚ - โ”œโ”€โ”€ ่ฏทๆฑ‚ HID ๅ‡ฝๆ•ฐ (4ไธช่ฎพๅค‡, ๅ…ฑ4ไธชIN็ซฏ็‚น, 1ไธชOUT็ซฏ็‚น) - โ”‚ โ”œโ”€โ”€ /dev/hidg0 (้”ฎ็›˜, 1 IN, 1 OUT for LED) - โ”‚ โ”œโ”€โ”€ /dev/hidg1 (็›ธๅฏน้ผ ๆ ‡, 1 IN) - โ”‚ โ”œโ”€โ”€ /dev/hidg2 (็ปๅฏน้ผ ๆ ‡, 1 IN) - โ”‚ โ””โ”€โ”€ /dev/hidg3 (Consumer Control, 1 IN) - โ”‚ - โ””โ”€โ”€ ๅˆ›ๅปบ OtgBackend (ไปŽ HidDevicePaths) - -HidBackendType::Ch9329 { port, baud_rate } - โ”‚ - โ”œโ”€โ”€ ๆ‰“ๅผ€ไธฒๅฃ่ฎพๅค‡ - โ”‚ - โ”œโ”€โ”€ ๅˆๅง‹ๅŒ– CH9329 ่Šฏ็‰‡ - โ”‚ - โ””โ”€โ”€ ๅˆ›ๅปบ Ch9329Backend - -HidBackendType::None - โ”‚ - โ””โ”€โ”€ ไธๅˆ›ๅปบๅŽ็ซฏ (HID ๅŠŸ่ƒฝ็ฆ็”จ) -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 HidController (mod.rs) - -HID ๆŽงๅˆถๅ™จไธป็ฑป๏ผŒ็ปŸไธ€็ฎก็†ๆ‰€ๆœ‰ HID ๆ“ไฝœใ€‚ - -```rust -pub struct HidController { - /// OTG Service reference (only used when backend is OTG) - otg_service: Option>, - - /// Active backend - backend: Arc>>>, - - /// Backend type (mutable for reload) - backend_type: RwLock, - - /// Event bus for broadcasting state changes (optional) - events: RwLock>>, - - /// Health monitor for error tracking and recovery - monitor: Arc, -} - -impl HidController { - /// Create a new HID controller with specified backend - pub fn new(backend_type: HidBackendType, otg_service: Option>) -> Self; - - /// Set event bus for broadcasting state changes - pub async fn set_event_bus(&self, events: Arc); - - /// Initialize the HID backend - pub async fn init(&self) -> Result<()>; - - /// Shutdown the HID backend and release resources - pub async fn shutdown(&self) -> Result<()>; - - /// Send keyboard event - pub async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()>; - - /// Send mouse event - pub async fn send_mouse(&self, event: MouseEvent) -> Result<()>; - - /// Send consumer control event (multimedia keys) - pub async fn send_consumer(&self, event: ConsumerEvent) -> Result<()>; - - /// Reset all keys (release all pressed keys) - pub async fn reset(&self) -> Result<()>; - - /// Check if backend is available - pub async fn is_available(&self) -> bool; - - /// Get backend type - pub async fn backend_type(&self) -> HidBackendType; - - /// Get backend info - pub async fn info(&self) -> Option; - - /// Get current state as SystemEvent - pub async fn current_state_event(&self) -> SystemEvent; - - /// Get the health monitor reference - pub fn monitor(&self) -> &Arc; - - /// Get current health status - pub async fn health_status(&self) -> HidHealthStatus; - - /// Check if the HID backend is healthy - pub async fn is_healthy(&self) -> bool; - - /// Reload the HID backend with new type - pub async fn reload(&self, new_backend_type: HidBackendType) -> Result<()>; -} - -pub struct HidInfo { - /// Backend name - pub name: &'static str, - /// Whether backend is initialized - pub initialized: bool, - /// Whether absolute mouse positioning is supported - pub supports_absolute_mouse: bool, - /// Screen resolution for absolute mouse - pub screen_resolution: Option<(u32, u32)>, -} -``` - -### 3.2 HidBackend Trait (backend.rs) - -```rust -#[async_trait] -pub trait HidBackend: Send + Sync { - /// Get backend name - fn name(&self) -> &'static str; - - /// Initialize the backend - async fn init(&self) -> Result<()>; - - /// Send a keyboard event - async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()>; - - /// Send a mouse event - async fn send_mouse(&self, event: MouseEvent) -> Result<()>; - - /// Send a consumer control event (multimedia keys) - async fn send_consumer(&self, event: ConsumerEvent) -> Result<()>; - - /// Reset all inputs (release all keys/buttons) - async fn reset(&self) -> Result<()>; - - /// Shutdown the backend - async fn shutdown(&self) -> Result<()>; - - /// Check if backend supports absolute mouse positioning - fn supports_absolute_mouse(&self) -> bool; - - /// Get screen resolution (for absolute mouse) - fn screen_resolution(&self) -> Option<(u32, u32)>; - - /// Set screen resolution (for absolute mouse) - fn set_screen_resolution(&mut self, width: u32, height: u32); -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum HidBackendType { - /// USB OTG gadget mode - Otg, - - /// CH9329 serial HID controller - Ch9329 { - port: String, - baud_rate: u32, - }, - - /// No HID backend (disabled) - None, -} - -impl HidBackendType { - /// Check if OTG backend is available on this system - pub fn otg_available() -> bool; - - /// Detect the best available backend - pub fn detect() -> Self; - - /// Get backend name as string - pub fn name_str(&self) -> &str; -} -``` - -### 3.3 OTG ๅŽ็ซฏ (otg.rs) - -้€š่ฟ‡ Linux USB OTG gadget ๆจกๆ‹Ÿ HID ่ฎพๅค‡ใ€‚ - -```rust -pub struct OtgBackend { - /// Keyboard device path (/dev/hidg0) - keyboard_path: PathBuf, - /// Relative mouse device path (/dev/hidg1) - mouse_rel_path: PathBuf, - /// Absolute mouse device path (/dev/hidg2) - mouse_abs_path: PathBuf, - /// Consumer control device path (/dev/hidg3) - consumer_path: PathBuf, - - /// Keyboard device file - keyboard_dev: Mutex>, - /// Relative mouse device file - mouse_rel_dev: Mutex>, - /// Absolute mouse device file - mouse_abs_dev: Mutex>, - /// Consumer control device file - consumer_dev: Mutex>, - - /// Current keyboard state - keyboard_state: Mutex, - /// Current mouse button state - mouse_buttons: AtomicU8, - - /// Last known LED state - led_state: RwLock, - /// Screen resolution for absolute mouse - screen_resolution: RwLock>, - - /// UDC name for state checking - udc_name: RwLock>, - /// Whether the device is currently online - online: AtomicBool, - - /// Error tracking (for log throttling) - last_error_log: Mutex, - error_count: AtomicU8, - eagain_count: AtomicU8, -} - -impl OtgBackend { - /// Create OTG backend from device paths provided by OtgService - pub fn from_handles(paths: HidDevicePaths) -> Result; - - /// Set the UDC name for state checking - pub fn set_udc_name(&self, udc: &str); - - /// Check if the UDC is in "configured" state - pub fn is_udc_configured(&self) -> bool; - - /// Check if device is online - pub fn is_online(&self) -> bool; - - /// Read keyboard LED state (non-blocking) - pub fn read_led_state(&self) -> Result>; - - /// Get last known LED state - pub fn led_state(&self) -> LedState; -} - -/// Keyboard LED state -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct LedState { - pub num_lock: bool, - pub caps_lock: bool, - pub scroll_lock: bool, - pub compose: bool, - pub kana: bool, -} - -impl LedState { - /// Create from raw byte - pub fn from_byte(b: u8) -> Self; - - /// Convert to raw byte - pub fn to_byte(&self) -> u8; -} -``` - -#### HID ๆŠฅๅ‘Šๆ ผๅผ - -```rust -/// ้”ฎ็›˜ๆŠฅๅ‘Š (8 ๅญ—่Š‚) -#[derive(Debug, Clone, Default)] -pub struct KeyboardReport { - pub modifiers: u8, // Ctrl, Shift, Alt, Meta - pub reserved: u8, // ไฟ็•™ - pub keys: [u8; 6], // ๆœ€ๅคš 6 ไธชๆŒ‰้”ฎ USB HID code -} - -impl KeyboardReport { - /// Convert to bytes for USB HID - pub fn to_bytes(&self) -> [u8; 8]; - - /// Add a key to the report - pub fn add_key(&mut self, key: u8) -> bool; - - /// Remove a key from the report - pub fn remove_key(&mut self, key: u8); - - /// Clear all keys - pub fn clear(&mut self); -} - -/// ้ผ ๆ ‡ๆŠฅๅ‘Š (็›ธๅฏนๆจกๅผ 4 ๅญ—่Š‚, ็ปๅฏนๆจกๅผ 6 ๅญ—่Š‚) -#[derive(Debug, Clone, Default)] -pub struct MouseReport { - pub buttons: u8, // ๆŒ‰้’ฎ็Šถๆ€ - pub x: i8, // X ็งปๅŠจ (-127 ~ 127) for relative - pub y: i8, // Y ็งปๅŠจ (-127 ~ 127) for relative - pub wheel: i8, // ๆปš่ฝฎ (-127 ~ 127) -} - -impl MouseReport { - /// Convert to bytes for USB HID (relative mouse) - pub fn to_bytes_relative(&self) -> [u8; 4]; - - /// Convert to bytes for USB HID (absolute mouse) - pub fn to_bytes_absolute(&self, x: u16, y: u16) -> [u8; 6]; -} -``` - -#### ้”™่ฏฏๆขๅคๆœบๅˆถ - -OTG ๅŽ็ซฏๅฎž็Žฐไบ†ๅŸบไบŽ PiKVM ๅ’Œ JetKVM ็š„่‡ชๅŠจ้”™่ฏฏๆขๅค๏ผš - -- **EAGAIN (errno 11)**: ่ต„ๆบๆš‚ๆ—ถไธๅฏ็”จ - ไฝฟ็”จ `poll()` ็ญ‰ๅพ…่ฎพๅค‡ๅฏๅ†™๏ผŒ่ถ…ๆ—ถๅŽ้™้ป˜ไธขๅผƒ -- **ESHUTDOWN (errno 108)**: ไผ ่พ“็ซฏ็‚นๅ…ณ้—ญ - ๅ…ณ้—ญ่ฎพๅค‡ๅฅๆŸ„๏ผŒไธ‹ๆฌกๆ“ไฝœๆ—ถ่‡ชๅŠจ้‡ๆ–ฐๆ‰“ๅผ€ -- **Write Timeout**: ไฝฟ็”จ 500ms ่ถ…ๆ—ถ (`HID_WRITE_TIMEOUT_MS`)๏ผŒ่ถ…ๆ—ถๅŽ้™้ป˜ไธขๅผƒๆ•ฐๆฎ -- **ๆ—ฅๅฟ—้™ๆต**: ้˜ฒๆญขๅคง้‡้”™่ฏฏๆ—ฅๅฟ—ๆณ›ๆปฅ - -### 3.4 CH9329 ๅŽ็ซฏ (ch9329.rs) - -้€š่ฟ‡ CH9329 ่Šฏ็‰‡๏ผˆไธฒๅฃ่ฝฌ HID๏ผ‰ๅฎž็Žฐ HID ๅŠŸ่ƒฝใ€‚ - -```rust -pub struct Ch9329Backend { - /// ไธฒๅฃ่ฎพๅค‡ - port: Mutex>, - - /// ่ฎพๅค‡่ทฏๅพ„ - device_path: String, - - /// ๆณข็‰น็އ - baud_rate: u32, - - /// ๅฝ“ๅ‰้”ฎ็›˜็Šถๆ€ - keyboard_state: Mutex, - - /// ่ฟžๆŽฅ็Šถๆ€ - connected: AtomicBool, -} - -impl Ch9329Backend { - /// ๅˆ›ๅปบ CH9329 ๅŽ็ซฏ - pub fn with_baud_rate(device: &str, baud_rate: u32) -> Result; - - /// ๅ‘้€ๅ‘ฝไปค - fn send_command(&self, cmd: &[u8]) -> Result>; - - /// ๅ‘้€้”ฎ็›˜ๆ•ฐๆฎๅŒ… - fn send_keyboard_packet(&self, report: &KeyboardReport) -> Result<()>; - - /// ๅ‘้€้ผ ๆ ‡ๆ•ฐๆฎๅŒ… - fn send_mouse_packet(&self, report: &[u8], absolute: bool) -> Result<()>; -} -``` - -#### CH9329 ๅ่ฎฎ - -``` -ๅธงๆ ผๅผ: -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HEAD โ”‚ ADDR โ”‚ CMD โ”‚ LEN โ”‚ DATA โ”‚ SUM โ”‚ -โ”‚ 0x57 โ”‚ 0xAB โ”‚ 0xXX โ”‚ data_len โ”‚ payload โ”‚ csum โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -ๅ‘ฝไปค็ : -0x02 - ๅ‘้€้”ฎ็›˜ๆ•ฐๆฎ -0x04 - ๅ‘้€็ปๅฏน้ผ ๆ ‡ๆ•ฐๆฎ -0x05 - ๅ‘้€็›ธๅฏน้ผ ๆ ‡ๆ•ฐๆฎ -0x0E - ่Žทๅ–่Šฏ็‰‡ไฟกๆฏ -``` - ---- - -## 4. ไบ‹ไปถ็ฑปๅž‹ - -### 4.1 ้”ฎ็›˜ไบ‹ไปถ (types.rs) - -```rust -/// Keyboard event type -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum KeyEventType { - Down, // ๆŒ‰้”ฎๆŒ‰ไธ‹ - Up, // ๆŒ‰้”ฎ้‡Šๆ”พ -} - -/// Keyboard modifier flags -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -pub struct KeyboardModifiers { - pub left_ctrl: bool, - pub left_shift: bool, - pub left_alt: bool, - pub left_meta: bool, - pub right_ctrl: bool, - pub right_shift: bool, - pub right_alt: bool, - pub right_meta: bool, -} - -impl KeyboardModifiers { - /// Convert to USB HID modifier byte - pub fn to_hid_byte(&self) -> u8; - - /// Create from USB HID modifier byte - pub fn from_hid_byte(byte: u8) -> Self; - - /// Check if any modifier is active - pub fn any(&self) -> bool; -} - -/// Keyboard event -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeyboardEvent { - /// Event type (down/up) - #[serde(rename = "type")] - pub event_type: KeyEventType, - - /// Key code (USB HID usage code or JavaScript keyCode) - pub key: u8, - - /// Modifier keys state - #[serde(default)] - pub modifiers: KeyboardModifiers, - - /// If true, key is already USB HID code (skip js_to_usb conversion) - #[serde(default)] - pub is_usb_hid: bool, -} -``` - -### 4.2 ้ผ ๆ ‡ไบ‹ไปถ (types.rs) - -```rust -/// Mouse button -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum MouseButton { - Left, - Right, - Middle, - Back, - Forward, -} - -impl MouseButton { - /// Convert to USB HID button bit - pub fn to_hid_bit(&self) -> u8; -} - -/// Mouse event type -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum MouseEventType { - Move, // ็›ธๅฏน็งปๅŠจ - MoveAbs, // ็ปๅฏนไฝ็ฝฎ - Down, // ๆŒ‰้’ฎๆŒ‰ไธ‹ - Up, // ๆŒ‰้’ฎ้‡Šๆ”พ - Scroll, // ๆปš่ฝฎๆปšๅŠจ -} - -/// Mouse event -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MouseEvent { - /// Event type - #[serde(rename = "type")] - pub event_type: MouseEventType, - - /// X coordinate or delta - #[serde(default)] - pub x: i32, - - /// Y coordinate or delta - #[serde(default)] - pub y: i32, - - /// Button (for down/up events) - #[serde(default)] - pub button: Option, - - /// Scroll delta (for scroll events) - #[serde(default)] - pub scroll: i8, -} -``` - -### 4.3 Consumer Control ไบ‹ไปถ (types.rs) - -```rust -/// Consumer control event (multimedia keys) -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ConsumerEvent { - /// USB HID Consumer Control Usage Code - pub usage: u16, -} - -// ๅธธ็”จ Usage Codes (ๅฎšไน‰ๅœจ consumer.rs) -pub mod usage { - pub const PLAY_PAUSE: u16 = 0x00CD; - pub const STOP: u16 = 0x00B7; - pub const NEXT_TRACK: u16 = 0x00B5; - pub const PREV_TRACK: u16 = 0x00B6; - pub const MUTE: u16 = 0x00E2; - pub const VOLUME_UP: u16 = 0x00E9; - pub const VOLUME_DOWN: u16 = 0x00EA; -} -``` - ---- - -## 5. ๆŒ‰้”ฎๆ˜ ๅฐ„ - -### 5.1 ๆŒ‰้”ฎ่ฝฌๆข (keymap.rs) - -ๆจกๅ—ไฝฟ็”จๅ›บๅฎšๅคงๅฐ็š„ๆŸฅๆ‰พ่กจ (256 ๅญ—่Š‚) ๅฎž็Žฐ JavaScript keyCode ๅˆฐ USB HID usage code ็š„ O(1) ่ฝฌๆขใ€‚ - -```rust -/// Convert JavaScript keyCode to USB HID keyCode -pub fn js_to_usb(js_code: u8) -> Option; - -/// Check if a key code is a modifier key -pub fn is_modifier_key(usb_code: u8) -> bool; - -/// Get modifier bit for a modifier key -pub fn modifier_bit(usb_code: u8) -> Option; - -// USB HID key codes ๅฎšไน‰ๅœจ usb ๅญๆจกๅ— -pub mod usb { - pub const KEY_A: u8 = 0x04; - pub const KEY_ENTER: u8 = 0x28; - pub const KEY_LEFT_CTRL: u8 = 0xE0; - // ... ็ญ‰็ญ‰ -} - -// JavaScript key codes ๅฎšไน‰ๅœจ js ๅญๆจกๅ— -pub mod js { - pub const KEY_A: u8 = 65; - pub const KEY_ENTER: u8 = 13; - // ... ็ญ‰็ญ‰ -} -``` - -### 5.2 ่ฝฌๆข็คบไพ‹ - -``` -JavaScript โ†’ USB HID -65 (KEY_A) โ†’ 0x04 -13 (ENTER) โ†’ 0x28 -37 (LEFT) โ†’ 0x50 -17 (CTRL) โ†’ 0xE0 -``` - ---- - -## 6. ่พ“ๅ…ฅๅค„็†ๅ™จ - -### 6.1 WebSocket Handler (websocket.rs) - -ไฝฟ็”จไบŒ่ฟ›ๅˆถๅ่ฎฎ (ไธŽ DataChannel ๆ ผๅผ็›ธๅŒ): - -```rust -/// Binary response codes -const RESP_OK: u8 = 0x00; -const RESP_ERR_HID_UNAVAILABLE: u8 = 0x01; -const RESP_ERR_INVALID_MESSAGE: u8 = 0x02; - -/// WebSocket HID upgrade handler -pub async fn ws_hid_handler( - ws: WebSocketUpgrade, - State(state): State> -) -> Response; - -/// Handle HID WebSocket connection -async fn handle_hid_socket(socket: WebSocket, state: Arc); - -/// Handle binary HID message (same format as DataChannel) -async fn handle_binary_message(data: &[u8], state: &AppState) -> Result<(), String>; -``` - -### 6.2 DataChannel Handler (datachannel.rs) - -็”จไบŽ WebRTC ๆจกๅผไธ‹็š„ HID ไบ‹ไปถๅค„็†๏ผŒไฝฟ็”จไบŒ่ฟ›ๅˆถๅ่ฎฎใ€‚ - -#### ไบŒ่ฟ›ๅˆถๆถˆๆฏๆ ผๅผ - -``` -ๆถˆๆฏ็ฑปๅž‹ๅธธ้‡: -MSG_KEYBOARD = 0x01 // ้”ฎ็›˜ไบ‹ไปถ -MSG_MOUSE = 0x02 // ้ผ ๆ ‡ไบ‹ไปถ -MSG_CONSUMER = 0x03 // Consumer Control ไบ‹ไปถ - -้”ฎ็›˜ๆถˆๆฏ (4 ๅญ—่Š‚): -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MSG_TYPE โ”‚ EVENT โ”‚ KEY_CODE โ”‚ MODIFIER โ”‚ -โ”‚ 0x01 โ”‚ 0/1 โ”‚ JS code โ”‚ bitmask โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -EVENT: 0=down, 1=up - -้ผ ๆ ‡ๆถˆๆฏ (7 ๅญ—่Š‚): -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MSG_TYPE โ”‚ EVENT โ”‚ X (i16) โ”‚ Y (i16) โ”‚ BTN/SCRL โ”‚ -โ”‚ 0x02 โ”‚ 0-4 โ”‚ LE โ”‚ LE โ”‚ u8/i8 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -EVENT: 0=move, 1=moveabs, 2=down, 3=up, 4=scroll - -Consumer Control ๆถˆๆฏ (3 ๅญ—่Š‚): -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MSG_TYPE โ”‚ USAGE CODE (u16 LE) โ”‚ -โ”‚ 0x03 โ”‚ e.g. 0x00CD โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -```rust -/// Parsed HID event from DataChannel -#[derive(Debug, Clone)] -pub enum HidChannelEvent { - Keyboard(KeyboardEvent), - Mouse(MouseEvent), - Consumer(ConsumerEvent), -} - -/// Parse a binary HID message from DataChannel -pub fn parse_hid_message(data: &[u8]) -> Option; - -/// Encode events to binary format (for sending to client if needed) -pub fn encode_keyboard_event(event: &KeyboardEvent) -> Vec; -pub fn encode_mouse_event(event: &MouseEvent) -> Vec; -``` - ---- - -## 7. ๅฅๅบท็›‘่ง† - -### 7.1 HidHealthMonitor (monitor.rs) - -```rust -/// HID health status -#[derive(Debug, Clone, PartialEq)] -pub enum HidHealthStatus { - /// Device is healthy and operational - Healthy, - - /// Device has an error, attempting recovery - Error { - reason: String, - error_code: String, - retry_count: u32, - }, - - /// Device is disconnected - Disconnected, -} - -/// HID health monitor configuration -#[derive(Debug, Clone)] -pub struct HidMonitorConfig { - /// Health check interval in milliseconds - pub check_interval_ms: u64, - /// Retry interval when device is lost (milliseconds) - pub retry_interval_ms: u64, - /// Maximum retry attempts before giving up (0 = infinite) - pub max_retries: u32, - /// Log throttle interval in seconds - pub log_throttle_secs: u64, - /// Recovery cooldown in milliseconds - pub recovery_cooldown_ms: u64, -} - -/// HID health monitor -pub struct HidHealthMonitor { - /// Current health status - status: RwLock, - /// Event bus for notifications - events: RwLock>>, - /// Log throttler to prevent log flooding - throttler: LogThrottler, - /// Configuration - config: HidMonitorConfig, - /// Current retry count - retry_count: AtomicU32, - /// Last error code (for change detection) - last_error_code: RwLock>, - /// Last recovery timestamp (for cooldown) - last_recovery_ms: AtomicU64, -} - -impl HidHealthMonitor { - /// Create a new HID health monitor - pub fn new(config: HidMonitorConfig) -> Self; - - /// Create with default configuration - pub fn with_defaults() -> Self; - - /// Set the event bus for broadcasting state changes - pub async fn set_event_bus(&self, events: Arc); - - /// Report an error from HID operations - pub async fn report_error( - &self, - backend: &str, - device: Option<&str>, - reason: &str, - error_code: &str, - ); - - /// Report that a reconnection attempt is starting - pub async fn report_reconnecting(&self, backend: &str); - - /// Report that the device has recovered - pub async fn report_recovered(&self, backend: &str); - - /// Get the current health status - pub async fn status(&self) -> HidHealthStatus; - - /// Get the current retry count - pub fn retry_count(&self) -> u32; - - /// Check if the monitor is in an error state - pub async fn is_error(&self) -> bool; - - /// Check if the monitor is healthy - pub async fn is_healthy(&self) -> bool; - - /// Reset the monitor to healthy state - pub async fn reset(&self); - - /// Check if we should continue retrying - pub fn should_retry(&self) -> bool; - - /// Get the retry interval - pub fn retry_interval(&self) -> Duration; -} -``` - -#### ้”™่ฏฏๅค„็†ๆต็จ‹ - -1. **ๆŠฅๅ‘Š้”™่ฏฏ**: `report_error()` - ๆ›ดๆ–ฐ็Šถๆ€ใ€้™ๆตๆ—ฅๅฟ—ใ€ๅ‘ๅธƒไบ‹ไปถ -2. **้‡่ฟž้€š็Ÿฅ**: `report_reconnecting()` - ๆฏ5ๆฌกๅฐ่ฏ•ๅ‘ๅธƒไธ€ๆฌกไบ‹ไปถ -3. **ๆขๅค้€š็Ÿฅ**: `report_recovered()` - ้‡็ฝฎ็Šถๆ€ใ€ๅ‘ๅธƒๆขๅคไบ‹ไปถ -4. **ๆ—ฅๅฟ—้™ๆต**: 5็ง’ๅ†…ไธ้‡ๅคๆ—ฅๅฟ—็›ธๅŒ้”™่ฏฏ -5. **ๆขๅคๅ†ทๅด**: ๆขๅคๅŽ1็ง’ๅ†…ๆŠ‘ๅˆถ้”™่ฏฏๆ—ฅๅฟ— - ---- - -## 8. ็ณป็ปŸไบ‹ไปถ - -```rust -pub enum SystemEvent { - /// HID state changed - HidStateChanged { - backend: String, - initialized: bool, - error: Option, - error_code: Option, - }, - - /// HID device lost - HidDeviceLost { - backend: String, - device: Option, - reason: String, - error_code: String, - }, - - /// HID reconnecting - HidReconnecting { - backend: String, - attempt: u32, - }, - - /// HID recovered - HidRecovered { - backend: String, - }, -} -``` - ---- - -## 9. ้”™่ฏฏๅค„็† - -```rust -pub enum AppError { - /// HID backend error - HidError { - backend: String, - reason: String, - error_code: String, - }, - - // ... ๅ…ถไป–้”™่ฏฏ็ฑปๅž‹ -} -``` - -ๅธธ่ง้”™่ฏฏ็ : - -- `enoent` - ่ฎพๅค‡ๆ–‡ไปถไธๅญ˜ๅœจ (ENOENT) -- `epipe` - ็ฎก้“ๆ–ญๅผ€ (EPIPE) -- `eshutdown` - ็ซฏ็‚นๅ…ณ้—ญ (ESHUTDOWN) -- `eagain` - ่ต„ๆบๆš‚ๆ—ถไธๅฏ็”จ (EAGAIN) -- `eagain_retry` - EAGAIN ้‡่ฏ•ไธญ (ๅ†…้ƒจไฝฟ็”จ๏ผŒไธๆŠฅๅ‘Š็ป™็›‘่ง†ๅ™จ) -- `enxio` - ่ฎพๅค‡ๆˆ–ๅœฐๅ€ไธๅญ˜ๅœจ (ENXIO) -- `enodev` - ่ฎพๅค‡ไธๅญ˜ๅœจ (ENODEV) -- `eio` - I/O ้”™่ฏฏ (EIO) -- `io_error` - ๅ…ถไป– I/O ้”™่ฏฏ -- `not_opened` - ่ฎพๅค‡ๆœชๆ‰“ๅผ€ -- `init_failed` - ๅˆๅง‹ๅŒ–ๅคฑ่ดฅ - ---- - -## 10. ไฝฟ็”จ็คบไพ‹ - -### 10.1 ๅˆๅง‹ๅŒ– HID ๆŽงๅˆถๅ™จ - -```rust -// ๅˆ›ๅปบ HID ๆŽงๅˆถๅ™จ (OTG ๆจกๅผ) -let hid = HidController::new( - HidBackendType::Otg, - Some(otg_service.clone()) -); - -// ่ฎพ็ฝฎไบ‹ไปถๆ€ป็บฟ -hid.set_event_bus(event_bus.clone()).await; - -// ๅˆๅง‹ๅŒ–ๅŽ็ซฏ -hid.init().await?; -``` - -### 10.2 ๅ‘้€้”ฎ็›˜ไบ‹ไปถ - -```rust -// ๆŒ‰ไธ‹ Ctrl+C -hid.send_keyboard(KeyboardEvent { - event_type: KeyEventType::Down, - key: 67, // JS keyCode for 'C' - modifiers: KeyboardModifiers { - left_ctrl: true, - ..Default::default() - }, - is_usb_hid: false, -}).await?; - -// ้‡Šๆ”พ Ctrl+C -hid.send_keyboard(KeyboardEvent { - event_type: KeyEventType::Up, - key: 67, - modifiers: KeyboardModifiers::default(), - is_usb_hid: false, -}).await?; -``` - -### 10.3 ๅ‘้€้ผ ๆ ‡ไบ‹ไปถ - -```rust -// ็งปๅŠจ้ผ ๆ ‡ๅˆฐ็ปๅฏนไฝ็ฝฎ (ๅฑๅน•ไธญๅฟƒ) -hid.send_mouse(MouseEvent { - event_type: MouseEventType::MoveAbs, - x: 16384, // 0-32767 ่Œƒๅ›ด (HID ๆ ‡ๅ‡†) - y: 16384, - button: None, - scroll: 0, -}).await?; - -// ็‚นๅ‡ปๅทฆ้”ฎ -hid.send_mouse(MouseEvent::button_down(MouseButton::Left)).await?; -hid.send_mouse(MouseEvent::button_up(MouseButton::Left)).await?; - -// ็›ธๅฏน็งปๅŠจ -hid.send_mouse(MouseEvent::move_rel(10, -10)).await?; - -// ๆปš่ฝฎๆปšๅŠจ -hid.send_mouse(MouseEvent::scroll(-1)).await?; -``` - -### 10.4 ๅ‘้€ๅคšๅช’ไฝ“้”ฎ - -```rust -use crate::hid::consumer::usage; - -// ๆ’ญๆ”พ/ๆš‚ๅœ -hid.send_consumer(ConsumerEvent { - usage: usage::PLAY_PAUSE, -}).await?; - -// ้Ÿณ้‡ๅขžๅŠ  -hid.send_consumer(ConsumerEvent { - usage: usage::VOLUME_UP, -}).await?; -``` - -### 10.5 ้‡ๆ–ฐๅŠ ่ฝฝๅŽ็ซฏ - -```rust -// ๅˆ‡ๆขๅˆฐ CH9329 ๅŽ็ซฏ -hid.reload(HidBackendType::Ch9329 { - port: "/dev/ttyUSB0".to_string(), - baud_rate: 9600, -}).await?; -``` - ---- - -## 11. ๅธธ่ง้—ฎ้ข˜ - -### Q: OTG ๆจกๅผไธ‹้”ฎ็›˜/้ผ ๆ ‡ไธๅทฅไฝœ? - -1. ๆฃ€ๆŸฅ `/dev/hidg*` ่ฎพๅค‡ๆ˜ฏๅฆๅญ˜ๅœจ: `ls -l /dev/hidg*` -2. ๆฃ€ๆŸฅ USB gadget ๆ˜ฏๅฆๆญฃ็กฎ้…็ฝฎ: `ls /sys/kernel/config/usb_gadget/` -3. ๆฃ€ๆŸฅ UDC ๆ˜ฏๅฆ็ป‘ๅฎš: `cat /sys/kernel/config/usb_gadget/*/UDC` -4. ๆฃ€ๆŸฅ็›ฎๆ ‡ PC ๆ˜ฏๅฆ่ฏ†ๅˆซ USB ่ฎพๅค‡ (ๅœจ็›ฎๆ ‡ PC ไธŠ่ฟ่กŒ `dmesg` ๆˆ–ๆŸฅ็œ‹่ฎพๅค‡็ฎก็†ๅ™จ) -5. ๆŸฅ็œ‹ One-KVM ๆ—ฅๅฟ—: `journalctl -u one-kvm -f` - -### Q: CH9329 ๆ— ๆณ•ๅˆๅง‹ๅŒ–? - -1. ๆฃ€ๆŸฅไธฒๅฃ่ฎพๅค‡่ทฏๅพ„: `ls -l /dev/ttyUSB*` -2. ๆฃ€ๆŸฅไธฒๅฃๆƒ้™: `sudo chmod 666 /dev/ttyUSB0` -3. ๆฃ€ๆŸฅๆณข็‰น็އ่ฎพ็ฝฎ (้ป˜่ฎค 9600) -4. ไฝฟ็”จ `minicom` ๆˆ– `screen` ๆต‹่ฏ•ไธฒๅฃ่ฟžๆŽฅ: - ```bash - minicom -D /dev/ttyUSB0 -b 9600 - ``` - -### Q: ้ผ ๆ ‡ๅฎšไฝไธๅ‡†็กฎ? - -1. ไฝฟ็”จ็ปๅฏน้ผ ๆ ‡ๆจกๅผ (้ป˜่ฎค) -2. ็กฎไฟๅ‰็ซฏๅ‘้€็š„ๅๆ ‡ๅœจ 0-32767 ่Œƒๅ›ดๅ†… -3. ๆฃ€ๆŸฅๅ‰็ซฏๆ˜ฏๅฆๆญฃ็กฎๅค„็†ๅฑๅน•็ผฉๆ”พ -4. ๆฃ€ๆŸฅๆต่งˆๅ™จ็ผฉๆ”พ็บงๅˆซ (ๅบ”ไธบ 100%) - -### Q: ๆŒ‰้”ฎๆœ‰ๅปถ่ฟŸ? - -1. ๆฃ€ๆŸฅ็ฝ‘็ปœๅปถ่ฟŸ: `ping ` -2. ไฝฟ็”จ WebRTC ๆจกๅผ (DataChannel) ่€Œไธๆ˜ฏ WebSocket -3. ๅ‡ๅฐ‘็ฝ‘็ปœ่ทณๆ•ฐ (้ฟๅ…ๅคšๅฑ‚ไปฃ็†) -4. ๆฃ€ๆŸฅๆœๅŠกๅ™จ CPU ่ดŸ่ฝฝ - -### Q: ้ข‘็นๅ‡บ็Žฐ ESHUTDOWN ้”™่ฏฏ? - -่ฟ™ๆ˜ฏๆญฃๅธธ็Žฐ่ฑก๏ผŒ้€šๅธธๅ‘็”Ÿๅœจ๏ผš -- MSD (ๅคงๅฎน้‡ๅญ˜ๅ‚จ) ่ฎพๅค‡ๆŒ‚่ฝฝ/ๅธ่ฝฝๆ—ถ -- USB ไธปๆœบ้‡ๆ–ฐๆžšไธพ่ฎพๅค‡ๆ—ถ -- ็›ฎๆ ‡ PC ่ฟ›ๅ…ฅไผ‘็œ /ๅ”ค้†’ๆ—ถ - -OTG ๅŽ็ซฏไผš่‡ชๅŠจๅค„็†่ฟ™ไบ›้”™่ฏฏๅนถ้‡ๆ–ฐๆ‰“ๅผ€่ฎพๅค‡๏ผŒๆ— ้œ€ไบบๅทฅๅนฒ้ข„ใ€‚ - -### Q: ๅฆ‚ไฝ•ๆŸฅ็œ‹ LED ็Šถๆ€ (Num Lock, Caps Lock)? - -```rust -let led_state = otg_backend.led_state(); -println!("Caps Lock: {}", led_state.caps_lock); -println!("Num Lock: {}", led_state.num_lock); -``` - -LED ็Šถๆ€ไผšๅœจ้”ฎ็›˜่ฎพๅค‡็š„ OUT endpoint ๆŽฅๆ”ถๅˆฐๆ•ฐๆฎๆ—ถ่‡ชๅŠจๆ›ดๆ–ฐใ€‚ - ---- - -## 12. ๆ€ง่ƒฝไผ˜ๅŒ– - -### 12.1 ้›ถๆ‹ท่ดๅ†™ๅ…ฅ - -OTG ๅŽ็ซฏไฝฟ็”จ `write_all()` ็›ดๆŽฅๅ†™ๅ…ฅ่ฎพๅค‡ๆ–‡ไปถ๏ผŒ้ฟๅ…้ขๅค–็š„ๅ†…ๅญ˜ๆ‹ท่ดใ€‚ - -### 12.2 ้ž้˜ปๅกž I/O - -ๆ‰€ๆœ‰่ฎพๅค‡ๆ–‡ไปถไปฅ `O_NONBLOCK` ๆจกๅผๆ‰“ๅผ€๏ผŒ้…ๅˆ `poll()` ๅฎž็Žฐ่ถ…ๆ—ถๆŽงๅˆถใ€‚ - -### 12.3 ไบ‹ไปถๆ‰นๅค„็† - -ๅ‰็ซฏๅฏไปฅๆ‰น้‡ๅ‘้€ๅคšไธชไบ‹ไปถ๏ผŒๅŽ็ซฏ้€ไธชๅค„็†ใ€‚ๅฏนไบŽ้ผ ๆ ‡็งปๅŠจ๏ผŒ่ถ…ๆ—ถ็š„ๅธงไผš่ขซ้™้ป˜ไธขๅผƒใ€‚ - -### 12.4 ๆ—ฅๅฟ—้™ๆต - -ไฝฟ็”จ `LogThrottler` ้˜ฒๆญขๅคง้‡้‡ๅคๆ—ฅๅฟ—ๅฝฑๅ“ๆ€ง่ƒฝ๏ผš -- HID ้”™่ฏฏๆ—ฅๅฟ—: 5็ง’้™ๆต -- ๆขๅคๅŽๅ†ทๅด: 1็ง’ๅ†…ไธ่ฎฐๅฝ•ๆ–ฐ้”™่ฏฏ - ---- - -## 13. ๅฎ‰ๅ…จ่€ƒ่™‘ - -### 13.1 ่ฎพๅค‡ๆƒ้™ - -- OTG gadget ่ฎพๅค‡ๆ–‡ไปถ (`/dev/hidg*`) ้œ€่ฆ่ฏปๅ†™ๆƒ้™ -- ้€šๅธธ้œ€่ฆ `root` ๆƒ้™ๆˆ–ๆทปๅŠ ็”จๆˆทๅˆฐ `input` ็ป„ -- ๅปบ่ฎฎไฝฟ็”จ udev ่ง„ๅˆ™่‡ชๅŠจ่ฎพ็ฝฎๆƒ้™ - -### 13.2 ่พ“ๅ…ฅ้ชŒ่ฏ - -- ๆ‰€ๆœ‰ๆฅ่‡ชๅ‰็ซฏ็š„ไบ‹ไปถ้ƒฝ็ป่ฟ‡้ชŒ่ฏ -- ๆ— ๆ•ˆ็š„ๆŒ‰้”ฎ็ ไผš่ขซๅฟฝ็•ฅๆˆ–ๆ˜ ๅฐ„ๅˆฐ้ป˜่ฎคๅ€ผ -- ้ผ ๆ ‡ๅๆ ‡ไผš่ขซ้™ๅˆถๅœจๆœ‰ๆ•ˆ่Œƒๅ›ดๅ†… - -### 13.3 ่ต„ๆบ้™ๅˆถ - -- ้”ฎ็›˜ๆŠฅๅ‘Šๆœ€ๅคšๆ”ฏๆŒ 6 ไธชๅŒๆ—ถๆŒ‰้”ฎ (USB HID ๆ ‡ๅ‡†) -- ้ผ ๆ ‡็งปๅŠจ่Œƒๅ›ด้™ๅˆถๅœจ -127~127 (็›ธๅฏน) ๆˆ– 0~32767 (็ปๅฏน) -- ่ถ…ๆ—ถ็š„ HID ๅ†™ๅ…ฅไผš่ขซไธขๅผƒ๏ผŒไธไผšๆ— ้™็ญ‰ๅพ… - ---- - -## 14. ่ฐƒ่ฏ•ๆŠ€ๅทง - -### 14.1 ๅฏ็”จ่ฏฆ็ป†ๆ—ฅๅฟ— - -```bash -RUST_LOG=one_kvm::hid=debug ./one-kvm -``` - -### 14.2 ็›‘ๆŽง HID ่ฎพๅค‡ - -```bash -# ็›‘ๆŽง้”ฎ็›˜ไบ‹ไปถ -sudo cat /dev/hidg0 | hexdump -C - -# ็›‘ๆŽง้ผ ๆ ‡ไบ‹ไปถ -sudo cat /dev/hidg1 | hexdump -C -``` - -### 14.3 ๆฃ€ๆŸฅ USB ๆžšไธพ - -ๅœจ็›ฎๆ ‡ PC ไธŠ: - -```bash -# Linux -dmesg | grep -i hid -lsusb - -# Windows -# ๆ‰“ๅผ€่ฎพๅค‡็ฎก็†ๅ™จ -> ไบบไฝ“ๅญฆ่พ“ๅ…ฅ่ฎพๅค‡ -``` - -### 14.4 ๆต‹่ฏ• CH9329 - -```bash -# ๅ‘้€ๆต‹่ฏ•ๅ‘ฝไปค (่Žทๅ–็‰ˆๆœฌ) -echo -ne '\x57\xAB\x0E\x00\x0E' > /dev/ttyUSB0 -``` - ---- - -## 15. ๅ‚่€ƒ่ต„ๆ–™ - -- [USB HID Usage Tables 1.12](https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf) -- [Linux USB Gadget API](https://www.kernel.org/doc/html/latest/usb/gadget_configfs.html) -- [PiKVM HID Implementation](https://github.com/pikvm/kvmd/blob/master/kvmd/apps/otg/hid/) -- [JetKVM HID Write Timeout](https://github.com/jetkvm/jetkvm/blob/main/jetkvm/hid.c#L25) -- [CH9329 Datasheet](http://www.wch.cn/downloads/CH9329DS1_PDF.html) diff --git a/docs/modules/msd.md b/docs/modules/msd.md deleted file mode 100644 index bacec741..00000000 --- a/docs/modules/msd.md +++ /dev/null @@ -1,617 +0,0 @@ -# MSD ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -MSD (Mass Storage Device) ๆจกๅ—ๆไพ›่™šๆ‹Ÿๅญ˜ๅ‚จ่ฎพๅค‡ๅŠŸ่ƒฝ๏ผŒๅ…่ฎธๅฐ† ISO/IMG ้•œๅƒไฝœไธบ USB ๅญ˜ๅ‚จ่ฎพๅค‡ๆŒ‚่ฝฝๅˆฐ็›ฎๆ ‡่ฎก็ฎ—ๆœบใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- ISO/IMG ้•œๅƒๆŒ‚่ฝฝ -- ้•œๅƒไธ‹่ฝฝ็ฎก็† -- Ventoy ๅคš ISO ๅฏๅŠจ็›˜ -- ็ƒญๆ’ๆ‹”ๆ”ฏๆŒ -- ไธ‹่ฝฝ่ฟ›ๅบฆ่ฟฝ่ธช - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/msd/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ controller.rs # MsdController (20KB) -โ”œโ”€โ”€ image.rs # ้•œๅƒ็ฎก็† (21KB) -โ”œโ”€โ”€ ventoy_drive.rs # Ventoy ้ฉฑๅŠจ (24KB) -โ”œโ”€โ”€ monitor.rs # ๅฅๅบท็›‘่ง† (9KB) -โ””โ”€โ”€ types.rs # ็ฑปๅž‹ๅฎšไน‰ (6KB) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ๆ•ดไฝ“ๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MSD Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - Web API - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ MsdController โ”‚ - โ”‚ (controller.rs) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Image โ”‚ โ”‚ Ventoy โ”‚ โ”‚ OTG โ”‚ -โ”‚ Manager โ”‚ โ”‚ Drive โ”‚ โ”‚ Service โ”‚ -โ”‚ (image.rs) โ”‚ โ”‚(ventoy.rs)โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ /data/ โ”‚ โ”‚ exFAT โ”‚ โ”‚ MSD โ”‚ -โ”‚ images/ โ”‚ โ”‚ Drive โ”‚ โ”‚ Function โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Target PC โ”‚ - โ”‚ (USB Drive) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 MSD ๆจกๅผ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MSD Modes โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Image Mode โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ISO/IMG โ”‚ โ”€โ”€โ–บ MSD LUN โ”€โ”€โ–บ Target PC sees single drive โ”‚ -โ”‚ โ”‚ File โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ ็‰น็‚น: โ”‚ -โ”‚ - ๅ•ไธช้•œๅƒๆ–‡ไปถ โ”‚ -โ”‚ - ็›ดๆŽฅๆŒ‚่ฝฝ โ”‚ -โ”‚ - ้€‚ๅˆ็ณป็ปŸๅฎ‰่ฃ… โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Ventoy Mode โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ISO 1 โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ISO 2 โ”‚ โ”€โ”€โ–บ โ”‚ Ventoy โ”‚ โ”€โ”€โ–บ Target PC sees bootable drive โ”‚ -โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Drive โ”‚ with ISO selection menu โ”‚ -โ”‚ โ”‚ ISO 3 โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ ็‰น็‚น: โ”‚ -โ”‚ - ๅคšไธช ISO ๆ–‡ไปถ โ”‚ -โ”‚ - exFAT ๆ–‡ไปถ็ณป็ปŸ โ”‚ -โ”‚ - ๅฏๅŠจ่œๅ•้€‰ๆ‹ฉ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 MsdController (controller.rs) - -MSD ๆŽงๅˆถๅ™จไธป็ฑปใ€‚ - -```rust -pub struct MsdController { - /// ๅฝ“ๅ‰็Šถๆ€ - state: Arc>, - - /// ้•œๅƒ็ฎก็†ๅ™จ - image_manager: Arc, - - /// Ventoy ้ฉฑๅŠจๅ™จ - ventoy_drive: Arc>>, - - /// OTG ๆœๅŠก - otg_service: Arc, - - /// MSD ๅ‡ฝๆ•ฐๅฅๆŸ„ - msd_function: Arc>>, - - /// ไบ‹ไปถๆ€ป็บฟ - events: Arc, - - /// ๆ•ฐๆฎ็›ฎๅฝ• - data_dir: PathBuf, -} - -impl MsdController { - /// ๅˆ›ๅปบๆŽงๅˆถๅ™จ - pub async fn new( - otg_service: Arc, - data_dir: PathBuf, - events: Arc, - ) -> Result>; - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> MsdState; - - /// ่ฟžๆŽฅ MSD - pub async fn connect(&self) -> Result<()>; - - /// ๆ–ญๅผ€ MSD - pub async fn disconnect(&self) -> Result<()>; - - /// ๅˆ‡ๆขๅˆฐ้•œๅƒๆจกๅผ - pub async fn set_image(&self, image_id: &str) -> Result<()>; - - /// ๅˆ‡ๆขๅˆฐ Ventoy ๆจกๅผ - pub async fn set_ventoy(&self) -> Result<()>; - - /// ๆธ…้™คๅฝ“ๅ‰ๆŒ‚่ฝฝ - pub async fn clear(&self) -> Result<()>; - - /// ๅˆ—ๅ‡บ้•œๅƒ - pub fn list_images(&self) -> Vec; - - /// ไธŠไผ ้•œๅƒ - pub async fn upload_image(&self, name: &str, data: Bytes) -> Result; - - /// ไปŽ URL ไธ‹่ฝฝ้•œๅƒ - pub async fn download_image(&self, url: &str) -> Result; - - /// ๅˆ ้™ค้•œๅƒ - pub async fn delete_image(&self, image_id: &str) -> Result<()>; - - /// ่Žทๅ–ไธ‹่ฝฝ่ฟ›ๅบฆ - pub fn get_download_progress(&self, download_id: &str) -> Option; -} - -pub struct MsdState { - /// ๆ˜ฏๅฆๅฏ็”จ - pub available: bool, - - /// ๅฝ“ๅ‰ๆจกๅผ - pub mode: MsdMode, - - /// ๆ˜ฏๅฆๅทฒ่ฟžๆŽฅ - pub connected: bool, - - /// ๅฝ“ๅ‰้•œๅƒไฟกๆฏ - pub current_image: Option, - - /// ้ฉฑๅŠจๅ™จไฟกๆฏ - pub drive_info: Option, - - /// ้”™่ฏฏไฟกๆฏ - pub error: Option, -} - -pub enum MsdMode { - /// ๆœชๆฟ€ๆดป - None, - - /// ๅ•้•œๅƒๆจกๅผ - Image, - - /// Ventoy ๆจกๅผ - Drive, -} -``` - -### 3.2 ImageManager (image.rs) - -้•œๅƒๆ–‡ไปถ็ฎก็†ๅ™จใ€‚ - -```rust -pub struct ImageManager { - /// ้•œๅƒ็›ฎๅฝ• - images_dir: PathBuf, - - /// ้•œๅƒๅˆ—่กจ็ผ“ๅญ˜ - images: RwLock>, - - /// ไธ‹่ฝฝไปปๅŠก - downloads: RwLock>, - - /// HTTP ๅฎขๆˆท็ซฏ - http_client: reqwest::Client, -} - -impl ImageManager { - /// ๅˆ›ๅปบ็ฎก็†ๅ™จ - pub fn new(images_dir: PathBuf) -> Result; - - /// ๆ‰ซๆ้•œๅƒ็›ฎๅฝ• - pub fn scan_images(&self) -> Result>; - - /// ่Žทๅ–้•œๅƒไฟกๆฏ - pub fn get_image(&self, id: &str) -> Option; - - /// ๆทปๅŠ ้•œๅƒ - pub async fn add_image(&self, name: &str, data: Bytes) -> Result; - - /// ๅˆ ้™ค้•œๅƒ - pub fn delete_image(&self, id: &str) -> Result<()>; - - /// ๅผ€ๅง‹ไธ‹่ฝฝ - pub async fn start_download(&self, url: &str) -> Result; - - /// ๅ–ๆถˆไธ‹่ฝฝ - pub fn cancel_download(&self, download_id: &str) -> Result<()>; - - /// ่Žทๅ–ไธ‹่ฝฝ่ฟ›ๅบฆ - pub fn get_download_progress(&self, download_id: &str) -> Option; - - /// ้ชŒ่ฏ้•œๅƒๆ–‡ไปถ - fn validate_image(path: &Path) -> Result; -} - -pub struct ImageInfo { - /// ๅ”ฏไธ€ ID - pub id: String, - - /// ๆ–‡ไปถๅ - pub name: String, - - /// ๆ–‡ไปถๅคงๅฐ - pub size: u64, - - /// ๆ ผๅผ - pub format: ImageFormat, - - /// ๅˆ›ๅปบๆ—ถ้—ด - pub created_at: DateTime, - - /// ไธ‹่ฝฝ็Šถๆ€ - pub download_status: Option, -} - -pub enum ImageFormat { - /// ISO ๅ…‰็›˜้•œๅƒ - Iso, - - /// ๅŽŸๅง‹็ฃ็›˜้•œๅƒ - Img, - - /// ๆœช็Ÿฅๆ ผๅผ - Unknown, -} - -pub struct DownloadProgress { - /// ๅทฒไธ‹่ฝฝๅญ—่Š‚ - pub downloaded: u64, - - /// ๆ€ปๅญ—่Š‚ๆ•ฐ - pub total: u64, - - /// ไธ‹่ฝฝ้€Ÿๅบฆ (bytes/sec) - pub speed: u64, - - /// ้ข„่ฎกๅ‰ฉไฝ™ๆ—ถ้—ด - pub eta_secs: u64, - - /// ็Šถๆ€ - pub status: DownloadStatus, -} - -pub enum DownloadStatus { - Pending, - Downloading, - Completed, - Failed(String), - Cancelled, -} -``` - -### 3.3 VentoyDrive (ventoy_drive.rs) - -Ventoy ๅฏๅฏๅŠจ้ฉฑๅŠจๅ™จ็ฎก็†ใ€‚ - -```rust -pub struct VentoyDrive { - /// ้ฉฑๅŠจๅ™จ่ทฏๅพ„ - drive_path: PathBuf, - - /// ้•œๅƒ่ทฏๅพ„ - images: Vec, - - /// ๅฎน้‡ - capacity: u64, - - /// ๅทฒ็”จ็ฉบ้—ด - used: u64, -} - -impl VentoyDrive { - /// ๅˆ›ๅปบ Ventoy ้ฉฑๅŠจๅ™จ - pub fn create(drive_path: PathBuf, capacity: u64) -> Result; - - /// ๆทปๅŠ  ISO - pub fn add_iso(&mut self, iso_path: &Path) -> Result<()>; - - /// ็งป้™ค ISO - pub fn remove_iso(&mut self, name: &str) -> Result<()>; - - /// ๅˆ—ๅ‡บ ISO - pub fn list_isos(&self) -> Vec; - - /// ่Žทๅ–้ฉฑๅŠจๅ™จไฟกๆฏ - pub fn info(&self) -> DriveInfo; - - /// ่Žทๅ–้ฉฑๅŠจๅ™จ่ทฏๅพ„ - pub fn path(&self) -> &Path; -} - -pub struct DriveInfo { - /// ๅฎน้‡ - pub capacity: u64, - - /// ๅทฒ็”จ็ฉบ้—ด - pub used: u64, - - /// ๅฏ็”จ็ฉบ้—ด - pub available: u64, - - /// ISO ๅˆ—่กจ - pub isos: Vec, -} -``` - ---- - -## 4. ็ฑปๅž‹ๅฎšไน‰ - -### 4.1 MSD ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct MsdConfig { - /// ๆ˜ฏๅฆๅฏ็”จ MSD - pub enabled: bool, - - /// ้•œๅƒ็›ฎๅฝ• - pub images_dir: Option, - - /// ้ป˜่ฎคๆจกๅผ - pub default_mode: MsdMode, - - /// Ventoy ๅฎน้‡ (MB) - pub ventoy_capacity_mb: u32, -} - -impl Default for MsdConfig { - fn default() -> Self { - Self { - enabled: true, - images_dir: None, - default_mode: MsdMode::None, - ventoy_capacity_mb: 4096, // 4GB - } - } -} -``` - ---- - -## 5. API ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/api/msd/status` | GET | ่Žทๅ– MSD ็Šถๆ€ | -| `/api/msd/connect` | POST | ่ฟžๆŽฅ MSD | -| `/api/msd/disconnect` | POST | ๆ–ญๅผ€ MSD | -| `/api/msd/images` | GET | ๅˆ—ๅ‡บ้•œๅƒ | -| `/api/msd/images` | POST | ไธŠไผ ้•œๅƒ | -| `/api/msd/images/:id` | DELETE | ๅˆ ้™ค้•œๅƒ | -| `/api/msd/images/download` | POST | ไปŽ URL ไธ‹่ฝฝ | -| `/api/msd/images/download/:id` | GET | ่Žทๅ–ไธ‹่ฝฝ่ฟ›ๅบฆ | -| `/api/msd/images/download/:id` | DELETE | ๅ–ๆถˆไธ‹่ฝฝ | -| `/api/msd/set-image` | POST | ่ฎพ็ฝฎๅฝ“ๅ‰้•œๅƒ | -| `/api/msd/set-ventoy` | POST | ่ฎพ็ฝฎ Ventoy ๆจกๅผ | -| `/api/msd/clear` | POST | ๆธ…้™คๆŒ‚่ฝฝ | - -### ๅ“ๅบ”ๆ ผๅผ - -```json -// GET /api/msd/status -{ - "available": true, - "mode": "image", - "connected": true, - "current_image": { - "id": "abc123", - "name": "ubuntu-22.04.iso", - "size": 4700000000, - "format": "iso" - }, - "drive_info": null, - "error": null -} - -// GET /api/msd/images -{ - "images": [ - { - "id": "abc123", - "name": "ubuntu-22.04.iso", - "size": 4700000000, - "format": "iso", - "created_at": "2024-01-15T10:30:00Z" - } - ] -} - -// POST /api/msd/images/download -// Request: { "url": "https://example.com/image.iso" } -// Response: { "download_id": "xyz789" } - -// GET /api/msd/images/download/xyz789 -{ - "downloaded": 1234567890, - "total": 4700000000, - "speed": 12345678, - "eta_secs": 280, - "status": "downloading" -} -``` - ---- - -## 6. ไบ‹ไปถ - -```rust -pub enum SystemEvent { - MsdStateChanged { - mode: MsdMode, - connected: bool, - image: Option, - error: Option, - }, - - MsdDownloadProgress { - download_id: String, - progress: DownloadProgress, - }, - - MsdDownloadComplete { - download_id: String, - image_id: String, - success: bool, - error: Option, - }, -} -``` - ---- - -## 7. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum MsdError { - #[error("MSD not available")] - NotAvailable, - - #[error("Already connected")] - AlreadyConnected, - - #[error("Not connected")] - NotConnected, - - #[error("Image not found: {0}")] - ImageNotFound(String), - - #[error("Invalid image format: {0}")] - InvalidFormat(String), - - #[error("Download failed: {0}")] - DownloadFailed(String), - - #[error("Storage full")] - StorageFull, - - #[error("OTG error: {0}")] - OtgError(String), - - #[error("IO error: {0}")] - IoError(#[from] std::io::Error), -} -``` - ---- - -## 8. ไฝฟ็”จ็คบไพ‹ - -### 8.1 ๆŒ‚่ฝฝ ISO ้•œๅƒ - -```rust -let msd = MsdController::new(otg_service, data_dir, events).await?; - -// ๅˆ—ๅ‡บ้•œๅƒ -let images = msd.list_images(); -println!("Available images: {:?}", images); - -// ่ฎพ็ฝฎ้•œๅƒ -msd.set_image("abc123").await?; - -// ่ฟžๆŽฅๅˆฐ็›ฎๆ ‡ PC -msd.connect().await?; - -// ็›ฎๆ ‡ PC ็Žฐๅœจๅฏไปฅ็œ‹ๅˆฐ USB ้ฉฑๅŠจๅ™จ... - -// ๆ–ญๅผ€่ฟžๆŽฅ -msd.disconnect().await?; -``` - -### 8.2 ไปŽ URL ไธ‹่ฝฝ - -```rust -// ๅผ€ๅง‹ไธ‹่ฝฝ -let download_id = msd.download_image("https://example.com/ubuntu.iso").await?; - -// ็›‘ๆŽง่ฟ›ๅบฆ -loop { - if let Some(progress) = msd.get_download_progress(&download_id) { - println!("Progress: {}%", progress.downloaded * 100 / progress.total); - - if matches!(progress.status, DownloadStatus::Completed) { - break; - } - } - tokio::time::sleep(Duration::from_secs(1)).await; -} -``` - -### 8.3 ไฝฟ็”จ Ventoy ๆจกๅผ - -```rust -// ๅˆ‡ๆขๅˆฐ Ventoy ๆจกๅผ -msd.set_ventoy().await?; - -// ่Žทๅ–้ฉฑๅŠจๅ™จไฟกๆฏ -let state = msd.state(); -if let Some(drive_info) = state.drive_info { - println!("Capacity: {} MB", drive_info.capacity / 1024 / 1024); - println!("ISOs: {:?}", drive_info.isos); -} - -// ่ฟžๆŽฅ -msd.connect().await?; -``` - ---- - -## 9. ๅธธ่ง้—ฎ้ข˜ - -### Q: ้•œๅƒๆ— ๆณ•ๆŒ‚่ฝฝ? - -1. ๆฃ€ๆŸฅ้•œๅƒๆ–‡ไปถๅฎŒๆ•ดๆ€ง -2. ็กฎ่ฎคๆ–‡ไปถๆ ผๅผๆญฃ็กฎ -3. ๆฃ€ๆŸฅๅญ˜ๅ‚จ็ฉบ้—ด - -### Q: ็›ฎๆ ‡ PC ไธ่ฏ†ๅˆซ? - -1. ๆฃ€ๆŸฅ USB ่ฟžๆŽฅ -2. ๅฐ่ฏ•้‡ๆ–ฐ่ฟžๆŽฅ -3. ๆŸฅ็œ‹็›ฎๆ ‡ PC ็š„่ฎพๅค‡็ฎก็†ๅ™จ - -### Q: ไธ‹่ฝฝ้€Ÿๅบฆๆ…ข? - -1. ๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅ -2. ไฝฟ็”จๆ›ด่ฟ‘็š„้•œๅƒๆบ -3. ๆฃ€ๆŸฅ็ฃ็›˜ I/O - -### Q: Ventoy ๅฏๅŠจๅคฑ่ดฅ? - -1. ๆฃ€ๆŸฅ็›ฎๆ ‡ PC BIOS ่ฎพ็ฝฎ -2. ๅฐ่ฏ•ไธๅŒ็š„ๅฏๅŠจๆจกๅผ -3. ็กฎ่ฎค ISO ๆ–‡ไปถๆ”ฏๆŒ Ventoy diff --git a/docs/modules/otg.md b/docs/modules/otg.md deleted file mode 100644 index e5e1a2a3..00000000 --- a/docs/modules/otg.md +++ /dev/null @@ -1,667 +0,0 @@ -# OTG ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -OTG (On-The-Go) ๆจกๅ—่ดŸ่ดฃ็ฎก็† Linux USB Gadget๏ผŒไธบ HID ๅ’Œ MSD ๅŠŸ่ƒฝๆไพ›็ปŸไธ€็š„ USB ่ฎพๅค‡็ฎก็†ใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- USB Gadget ็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็† -- HID ๅ‡ฝๆ•ฐ้…็ฝฎ (้”ฎ็›˜ใ€้ผ ๆ ‡) -- MSD ๅ‡ฝๆ•ฐ้…็ฝฎ (่™šๆ‹Ÿๅญ˜ๅ‚จ) -- ConfigFS ๆ“ไฝœ -- UDC ็ป‘ๅฎš/่งฃ็ป‘ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/otg/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ service.rs # OtgService (17KB) -โ”œโ”€โ”€ manager.rs # OtgGadgetManager (12KB) -โ”œโ”€โ”€ hid.rs # HID Function (7KB) -โ”œโ”€โ”€ msd.rs # MSD Function (14KB) -โ”œโ”€โ”€ configfs.rs # ConfigFS ๆ“ไฝœ (4KB) -โ”œโ”€โ”€ endpoint.rs # ็ซฏ็‚นๅˆ†้… (2KB) -โ””โ”€โ”€ report_desc.rs # HID ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ (6KB) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ่ฎพ่ฎก็›ฎๆ ‡ - -่งฃๅ†ณ HID ๅ’Œ MSD ๅ…ฑไบซๅŒไธ€ไธช USB Gadget ็š„ๆ‰€ๆœ‰ๆƒ้—ฎ้ข˜๏ผš - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ OTG Ownership Model โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ OtgService โ”‚ โ—„โ”€โ”€ ๅ”ฏไธ€ๆ‰€ๆœ‰่€… - โ”‚ (service.rs) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ - enable_hid() enable_msd() ็Šถๆ€ๆŸฅ่ฏข - โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚OtgGadgetManager โ”‚ - โ”‚ (manager.rs) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HID โ”‚ โ”‚ MSD โ”‚ โ”‚ UDC โ”‚ -โ”‚ Func โ”‚ โ”‚ Func โ”‚ โ”‚ Bind โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ConfigFS ็ป“ๆž„ - -``` -/sys/kernel/config/usb_gadget/one-kvm/ -โ”œโ”€โ”€ idVendor # 0x05ac (Apple) -โ”œโ”€โ”€ idProduct # 0x0001 -โ”œโ”€โ”€ bcdDevice # 0x0100 -โ”œโ”€โ”€ bcdUSB # 0x0200 -โ”œโ”€โ”€ bMaxPacketSize0 # 64 -โ”‚ -โ”œโ”€โ”€ strings/ -โ”‚ โ””โ”€โ”€ 0x409/ # English -โ”‚ โ”œโ”€โ”€ manufacturer # "One-KVM" -โ”‚ โ”œโ”€โ”€ product # "KVM Device" -โ”‚ โ””โ”€โ”€ serialnumber # UUID -โ”‚ -โ”œโ”€โ”€ configs/ -โ”‚ โ””โ”€โ”€ c.1/ -โ”‚ โ”œโ”€โ”€ MaxPower # 500 -โ”‚ โ”œโ”€โ”€ strings/ -โ”‚ โ”‚ โ””โ”€โ”€ 0x409/ -โ”‚ โ”‚ โ””โ”€โ”€ configuration # "Config 1" -โ”‚ โ””โ”€โ”€ (function symlinks) -โ”‚ -โ”œโ”€โ”€ functions/ -โ”‚ โ”œโ”€โ”€ hid.usb0/ # ้”ฎ็›˜ -โ”‚ โ”‚ โ”œโ”€โ”€ protocol # 1 (keyboard) -โ”‚ โ”‚ โ”œโ”€โ”€ subclass # 1 (boot) -โ”‚ โ”‚ โ”œโ”€โ”€ report_length # 8 -โ”‚ โ”‚ โ””โ”€โ”€ report_desc # (binary) -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ hid.usb1/ # ็›ธๅฏน้ผ ๆ ‡ -โ”‚ โ”‚ โ”œโ”€โ”€ protocol # 2 (mouse) -โ”‚ โ”‚ โ”œโ”€โ”€ subclass # 1 (boot) -โ”‚ โ”‚ โ”œโ”€โ”€ report_length # 4 -โ”‚ โ”‚ โ””โ”€โ”€ report_desc # (binary) -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ hid.usb2/ # ็ปๅฏน้ผ ๆ ‡ -โ”‚ โ”‚ โ”œโ”€โ”€ protocol # 2 (mouse) -โ”‚ โ”‚ โ”œโ”€โ”€ subclass # 0 (none) -โ”‚ โ”‚ โ”œโ”€โ”€ report_length # 6 -โ”‚ โ”‚ โ””โ”€โ”€ report_desc # (binary) -โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€ mass_storage.usb0/ # ่™šๆ‹Ÿๅญ˜ๅ‚จ -โ”‚ โ”œโ”€โ”€ stall # 1 -โ”‚ โ””โ”€โ”€ lun.0/ -โ”‚ โ”œโ”€โ”€ cdrom # 1 (ISO mode) -โ”‚ โ”œโ”€โ”€ ro # 1 (read-only) -โ”‚ โ”œโ”€โ”€ removable # 1 -โ”‚ โ”œโ”€โ”€ nofua # 1 -โ”‚ โ””โ”€โ”€ file # /path/to/image.iso -โ”‚ -โ””โ”€โ”€ UDC # UDC ่ฎพๅค‡ๅ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 OtgService (service.rs) - -OTG ๆœๅŠกไธป็ฑป๏ผŒๆไพ›็ปŸไธ€็š„ USB Gadget ็ฎก็†ๆŽฅๅฃใ€‚ - -```rust -pub struct OtgService { - /// Gadget ็ฎก็†ๅ™จ - manager: Arc>, - - /// ๅฝ“ๅ‰็Šถๆ€ - state: Arc>, - - /// HID ๅ‡ฝๆ•ฐๅฅๆŸ„ - hid_function: Arc>>, - - /// MSD ๅ‡ฝๆ•ฐๅฅๆŸ„ - msd_function: Arc>>, - - /// ่ฏทๆฑ‚่ฎกๆ•ฐๅ™จ (lock-free) - pending_requests: AtomicU8, -} - -impl OtgService { - /// ๅˆ›ๅปบๆœๅŠก - pub fn new() -> Result; - - /// ๅฏ็”จ HID ๅŠŸ่ƒฝ - pub async fn enable_hid(&self) -> Result; - - /// ็ฆ็”จ HID ๅŠŸ่ƒฝ - pub async fn disable_hid(&self) -> Result<()>; - - /// ๅฏ็”จ MSD ๅŠŸ่ƒฝ - pub async fn enable_msd(&self) -> Result; - - /// ็ฆ็”จ MSD ๅŠŸ่ƒฝ - pub async fn disable_msd(&self) -> Result<()>; - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> OtgServiceState; - - /// ๆฃ€ๆŸฅ HID ๆ˜ฏๅฆๅฏ็”จ - pub fn is_hid_enabled(&self) -> bool; - - /// ๆฃ€ๆŸฅ MSD ๆ˜ฏๅฆๅฏ็”จ - pub fn is_msd_enabled(&self) -> bool; -} - -pub struct OtgServiceState { - /// Gadget ๆ˜ฏๅฆๆฟ€ๆดป - pub gadget_active: bool, - - /// HID ๆ˜ฏๅฆๅฏ็”จ - pub hid_enabled: bool, - - /// MSD ๆ˜ฏๅฆๅฏ็”จ - pub msd_enabled: bool, - - /// HID ่ฎพๅค‡่ทฏๅพ„ - pub hid_paths: Option, - - /// ้”™่ฏฏไฟกๆฏ - pub error: Option, -} - -pub struct HidDevicePaths { - pub keyboard: PathBuf, // /dev/hidg0 - pub mouse_relative: PathBuf, // /dev/hidg1 - pub mouse_absolute: PathBuf, // /dev/hidg2 -} -``` - -### 3.2 OtgGadgetManager (manager.rs) - -Gadget ็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็†ๅ™จใ€‚ - -```rust -pub struct OtgGadgetManager { - /// Gadget ่ทฏๅพ„ - gadget_path: PathBuf, - - /// UDC ่ฎพๅค‡ๅ - udc_name: Option, - - /// ๆ˜ฏๅฆๅทฒๅˆ›ๅปบ - created: bool, - - /// ๆ˜ฏๅฆๅทฒ็ป‘ๅฎš - bound: bool, - - /// ็ซฏ็‚นๅˆ†้…ๅ™จ - endpoint_allocator: EndpointAllocator, -} - -impl OtgGadgetManager { - /// ๅˆ›ๅปบ็ฎก็†ๅ™จ - pub fn new() -> Result; - - /// ๅˆ›ๅปบ Gadget - pub fn create_gadget(&mut self, config: &GadgetConfig) -> Result<()>; - - /// ้”€ๆฏ Gadget - pub fn destroy_gadget(&mut self) -> Result<()>; - - /// ็ป‘ๅฎš UDC - pub fn bind_udc(&mut self) -> Result<()>; - - /// ่งฃ็ป‘ UDC - pub fn unbind_udc(&mut self) -> Result<()>; - - /// ๆทปๅŠ ๅ‡ฝๆ•ฐ - pub fn add_function(&mut self, func: &dyn GadgetFunction) -> Result<()>; - - /// ็งป้™คๅ‡ฝๆ•ฐ - pub fn remove_function(&mut self, func: &dyn GadgetFunction) -> Result<()>; - - /// ้“พๆŽฅๅ‡ฝๆ•ฐๅˆฐ้…็ฝฎ - pub fn link_function(&self, func: &dyn GadgetFunction) -> Result<()>; - - /// ๅ–ๆถˆ้“พๆŽฅๅ‡ฝๆ•ฐ - pub fn unlink_function(&self, func: &dyn GadgetFunction) -> Result<()>; - - /// ๆฃ€ๆต‹ๅฏ็”จ UDC - fn detect_udc() -> Result; -} - -pub struct GadgetConfig { - pub name: String, // "one-kvm" - pub vendor_id: u16, // 0x05ac - pub product_id: u16, // 0x0001 - pub manufacturer: String, // "One-KVM" - pub product: String, // "KVM Device" - pub serial: String, // UUID -} -``` - -### 3.3 HID Function (hid.rs) - -```rust -pub struct HidFunction { - /// ้”ฎ็›˜ๅ‡ฝๆ•ฐ - keyboard: HidFunctionConfig, - - /// ็›ธๅฏน้ผ ๆ ‡ๅ‡ฝๆ•ฐ - mouse_relative: HidFunctionConfig, - - /// ็ปๅฏน้ผ ๆ ‡ๅ‡ฝๆ•ฐ - mouse_absolute: HidFunctionConfig, -} - -pub struct HidFunctionConfig { - /// ๅ‡ฝๆ•ฐๅ - pub name: String, // "hid.usb0" - - /// ๅ่ฎฎ - pub protocol: u8, // 1=keyboard, 2=mouse - - /// ๅญ็ฑป - pub subclass: u8, // 1=boot, 0=none - - /// ๆŠฅๅ‘Š้•ฟๅบฆ - pub report_length: u8, - - /// ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub report_desc: Vec, -} - -impl HidFunction { - /// ๅˆ›ๅปบ HID ๅ‡ฝๆ•ฐ - pub fn new() -> Self; - - /// ่Žทๅ–้”ฎ็›˜ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn keyboard_report_desc() -> Vec; - - /// ่Žทๅ–็›ธๅฏน้ผ ๆ ‡ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn mouse_relative_report_desc() -> Vec; - - /// ่Žทๅ–็ปๅฏน้ผ ๆ ‡ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn mouse_absolute_report_desc() -> Vec; -} - -impl GadgetFunction for HidFunction { - fn name(&self) -> &str; - fn function_type(&self) -> &str; // "hid" - fn configure(&self, path: &Path) -> Result<()>; -} -``` - -### 3.4 MSD Function (msd.rs) - -```rust -pub struct MsdFunction { - /// ๅ‡ฝๆ•ฐๅ - name: String, - - /// LUN ้…็ฝฎ - luns: Vec, -} - -pub struct MsdLun { - /// LUN ็ผ–ๅท - pub lun_id: u8, - - /// ้•œๅƒๆ–‡ไปถ่ทฏๅพ„ - pub file: Option, - - /// ๆ˜ฏๅฆ CD-ROM ๆจกๅผ - pub cdrom: bool, - - /// ๆ˜ฏๅฆๅช่ฏป - pub readonly: bool, - - /// ๆ˜ฏๅฆๅฏ็งป้™ค - pub removable: bool, -} - -impl MsdFunction { - /// ๅˆ›ๅปบ MSD ๅ‡ฝๆ•ฐ - pub fn new() -> Self; - - /// ่ฎพ็ฝฎ้•œๅƒๆ–‡ไปถ - pub fn set_image(&mut self, path: &Path, cdrom: bool) -> Result<()>; - - /// ๆธ…้™ค้•œๅƒ - pub fn clear_image(&mut self) -> Result<()>; - - /// ๅผนๅ‡บไป‹่ดจ - pub fn eject(&mut self) -> Result<()>; -} - -impl GadgetFunction for MsdFunction { - fn name(&self) -> &str; - fn function_type(&self) -> &str; // "mass_storage" - fn configure(&self, path: &Path) -> Result<()>; -} -``` - -### 3.5 ConfigFS ๆ“ไฝœ (configfs.rs) - -```rust -pub struct ConfigFs; - -impl ConfigFs { - /// ConfigFS ๆ น่ทฏๅพ„ - const ROOT: &'static str = "/sys/kernel/config/usb_gadget"; - - /// ๅˆ›ๅปบ็›ฎๅฝ• - pub fn mkdir(path: &Path) -> Result<()>; - - /// ๅˆ ้™ค็›ฎๅฝ• - pub fn rmdir(path: &Path) -> Result<()>; - - /// ๅ†™ๅ…ฅๆ–‡ไปถ - pub fn write_file(path: &Path, content: &str) -> Result<()>; - - /// ๅ†™ๅ…ฅไบŒ่ฟ›ๅˆถๆ–‡ไปถ - pub fn write_binary(path: &Path, data: &[u8]) -> Result<()>; - - /// ่ฏปๅ–ๆ–‡ไปถ - pub fn read_file(path: &Path) -> Result; - - /// ๅˆ›ๅปบ็ฌฆๅท้“พๆŽฅ - pub fn symlink(target: &Path, link: &Path) -> Result<()>; - - /// ๅˆ ้™ค็ฌฆๅท้“พๆŽฅ - pub fn unlink(path: &Path) -> Result<()>; - - /// ๅˆ—ๅ‡บ็›ฎๅฝ• - pub fn list_dir(path: &Path) -> Result>; -} -``` - -### 3.6 ็ซฏ็‚นๅˆ†้… (endpoint.rs) - -```rust -pub struct EndpointAllocator { - /// ๅทฒไฝฟ็”จ็š„็ซฏ็‚น - used_endpoints: HashSet, - - /// ๆœ€ๅคง็ซฏ็‚นๆ•ฐ - max_endpoints: u8, -} - -impl EndpointAllocator { - /// ๅˆ›ๅปบๅˆ†้…ๅ™จ - pub fn new(max_endpoints: u8) -> Self; - - /// ๅˆ†้…็ซฏ็‚น - pub fn allocate(&mut self, count: u8) -> Result>; - - /// ้‡Šๆ”พ็ซฏ็‚น - pub fn release(&mut self, endpoints: &[u8]); - - /// ๆฃ€ๆŸฅๅฏ็”จ็ซฏ็‚นๆ•ฐ - pub fn available(&self) -> u8; -} -``` - -### 3.7 ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ (report_desc.rs) - -```rust -pub struct ReportDescriptor; - -impl ReportDescriptor { - /// ๆ ‡ๅ‡†้”ฎ็›˜ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn keyboard() -> Vec { - vec![ - 0x05, 0x01, // Usage Page (Generic Desktop) - 0x09, 0x06, // Usage (Keyboard) - 0xA1, 0x01, // Collection (Application) - 0x05, 0x07, // Usage Page (Key Codes) - 0x19, 0xE0, // Usage Minimum (224) - 0x29, 0xE7, // Usage Maximum (231) - 0x15, 0x00, // Logical Minimum (0) - 0x25, 0x01, // Logical Maximum (1) - 0x75, 0x01, // Report Size (1) - 0x95, 0x08, // Report Count (8) - 0x81, 0x02, // Input (Data, Variable, Absolute) - 0x95, 0x01, // Report Count (1) - 0x75, 0x08, // Report Size (8) - 0x81, 0x01, // Input (Constant) - 0x95, 0x06, // Report Count (6) - 0x75, 0x08, // Report Size (8) - 0x15, 0x00, // Logical Minimum (0) - 0x25, 0x65, // Logical Maximum (101) - 0x05, 0x07, // Usage Page (Key Codes) - 0x19, 0x00, // Usage Minimum (0) - 0x29, 0x65, // Usage Maximum (101) - 0x81, 0x00, // Input (Data, Array) - 0xC0, // End Collection - ] - } - - /// ็›ธๅฏน้ผ ๆ ‡ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn mouse_relative() -> Vec; - - /// ็ปๅฏน้ผ ๆ ‡ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - pub fn mouse_absolute() -> Vec; -} -``` - ---- - -## 4. ็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็† - -### 4.1 ๅˆๅง‹ๅŒ–ๆต็จ‹ - -``` -OtgService::new() - โ”‚ - โ”œโ”€โ”€ ๆฃ€ๆต‹ UDC ่ฎพๅค‡ - โ”‚ โ””โ”€โ”€ ่ฏปๅ– /sys/class/udc/ - โ”‚ - โ”œโ”€โ”€ ๅˆ›ๅปบ OtgGadgetManager - โ”‚ - โ””โ”€โ”€ ๅˆๅง‹ๅŒ–็Šถๆ€ - -enable_hid() - โ”‚ - โ”œโ”€โ”€ ๆฃ€ๆŸฅ Gadget ๆ˜ฏๅฆๅญ˜ๅœจ - โ”‚ โ””โ”€โ”€ ๅฆ‚ไธๅญ˜ๅœจ๏ผŒๅˆ›ๅปบ Gadget - โ”‚ - โ”œโ”€โ”€ ๅˆ›ๅปบ HID ๅ‡ฝๆ•ฐ - โ”‚ โ”œโ”€โ”€ hid.usb0 (้”ฎ็›˜) - โ”‚ โ”œโ”€โ”€ hid.usb1 (็›ธๅฏน้ผ ๆ ‡) - โ”‚ โ””โ”€โ”€ hid.usb2 (็ปๅฏน้ผ ๆ ‡) - โ”‚ - โ”œโ”€โ”€ ้…็ฝฎๅ‡ฝๆ•ฐ - โ”‚ โ””โ”€โ”€ ๅ†™ๅ…ฅๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - โ”‚ - โ”œโ”€โ”€ ้“พๆŽฅๅ‡ฝๆ•ฐๅˆฐ้…็ฝฎ - โ”‚ - โ”œโ”€โ”€ ็ป‘ๅฎš UDC (ๅฆ‚ๆœช็ป‘ๅฎš) - โ”‚ - โ””โ”€โ”€ ็ญ‰ๅพ…่ฎพๅค‡่Š‚็‚นๅ‡บ็Žฐ - โ””โ”€โ”€ /dev/hidg0, hidg1, hidg2 -``` - -### 4.2 ๆธ…็†ๆต็จ‹ - -``` -disable_hid() - โ”‚ - โ”œโ”€โ”€ ๆฃ€ๆŸฅๆ˜ฏๅฆๆœ‰ๅ…ถไป–ๅ‡ฝๆ•ฐไฝฟ็”จ - โ”‚ - โ”œโ”€โ”€ ๅฆ‚ๆžœๅชๆœ‰ HID๏ผŒ่งฃ็ป‘ UDC - โ”‚ - โ”œโ”€โ”€ ๅ–ๆถˆ้“พๆŽฅ HID ๅ‡ฝๆ•ฐ - โ”‚ - โ””โ”€โ”€ ๅˆ ้™ค HID ๅ‡ฝๆ•ฐ็›ฎๅฝ• - -disable_msd() - โ”‚ - โ”œโ”€โ”€ ๅŒไธŠ... - โ”‚ - โ””โ”€โ”€ ๅฆ‚ๆžœๆฒกๆœ‰ไปปไฝ•ๅ‡ฝๆ•ฐ๏ผŒ้”€ๆฏ Gadget -``` - ---- - -## 5. ้…็ฝฎ - -### 5.1 OTG ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct OtgConfig { - /// ๆ˜ฏๅฆๅฏ็”จ OTG - pub enabled: bool, - - /// ๅŽ‚ๅ•† ID - pub vendor_id: u16, - - /// ไบงๅ“ ID - pub product_id: u16, - - /// ๅŽ‚ๅ•†ๅ็งฐ - pub manufacturer: String, - - /// ไบงๅ“ๅ็งฐ - pub product: String, -} - -impl Default for OtgConfig { - fn default() -> Self { - Self { - enabled: true, - vendor_id: 0x05ac, // Apple - product_id: 0x0001, - manufacturer: "One-KVM".to_string(), - product: "KVM Device".to_string(), - } - } -} -``` - ---- - -## 6. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum OtgError { - #[error("No UDC device found")] - NoUdcDevice, - - #[error("Gadget already exists")] - GadgetExists, - - #[error("Gadget not found")] - GadgetNotFound, - - #[error("Function already exists: {0}")] - FunctionExists(String), - - #[error("UDC busy")] - UdcBusy, - - #[error("ConfigFS error: {0}")] - ConfigFsError(String), - - #[error("Permission denied: {0}")] - PermissionDenied(String), - - #[error("Device node not found: {0}")] - DeviceNodeNotFound(String), -} -``` - ---- - -## 7. ไฝฟ็”จ็คบไพ‹ - -### 7.1 ๅฏ็”จ HID - -```rust -let otg = OtgService::new()?; - -// ๅฏ็”จ HID -let paths = otg.enable_hid().await?; -println!("Keyboard: {:?}", paths.keyboard); -println!("Mouse relative: {:?}", paths.mouse_relative); -println!("Mouse absolute: {:?}", paths.mouse_absolute); - -// ไฝฟ็”จ่ฎพๅค‡... - -// ็ฆ็”จ HID -otg.disable_hid().await?; -``` - -### 7.2 ๅฏ็”จ MSD - -```rust -let otg = OtgService::new()?; - -// ๅฏ็”จ MSD -let mut msd = otg.enable_msd().await?; - -// ๆŒ‚่ฝฝ ISO -msd.set_image(Path::new("/data/ubuntu.iso"), true)?; - -// ๅผนๅ‡บ -msd.eject()?; - -// ็ฆ็”จ MSD -otg.disable_msd().await?; -``` - ---- - -## 8. ๅธธ่ง้—ฎ้ข˜ - -### Q: ๆ‰พไธๅˆฐ UDC ่ฎพๅค‡? - -1. ๆฃ€ๆŸฅๅ†…ๆ ธๆ˜ฏๅฆๆ”ฏๆŒ USB Gadget -2. ๅŠ ่ฝฝๅฟ…่ฆ็š„ๅ†…ๆ ธๆจกๅ—: - ```bash - modprobe libcomposite - modprobe usb_f_hid - modprobe usb_f_mass_storage - ``` -3. ๆฃ€ๆŸฅ `/sys/class/udc/` ็›ฎๅฝ• - -### Q: ๆƒ้™้”™่ฏฏ? - -1. ไปฅ root ่ฟ่กŒ -2. ๆˆ–้…็ฝฎ udev ่ง„ๅˆ™ - -### Q: ่ฎพๅค‡่Š‚็‚นไธๅ‡บ็Žฐ? - -1. ๆฃ€ๆŸฅ UDC ๆ˜ฏๅฆๆญฃ็กฎ็ป‘ๅฎš -2. ๆŸฅ็œ‹ `dmesg` ๆ—ฅๅฟ— -3. ๆฃ€ๆŸฅ ConfigFS ้…็ฝฎ - -### Q: ็›ฎๆ ‡ PC ไธ่ฏ†ๅˆซ? - -1. ๆฃ€ๆŸฅ USB ็บฟ็ผ† -2. ๆฃ€ๆŸฅๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ -3. ไฝฟ็”จ `lsusb` ็กฎ่ฎค่ฎพๅค‡ diff --git a/docs/modules/rustdesk.md b/docs/modules/rustdesk.md deleted file mode 100644 index 01611b64..00000000 --- a/docs/modules/rustdesk.md +++ /dev/null @@ -1,1258 +0,0 @@ -# RustDesk ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -RustDesk ๆจกๅ—ๅฎž็Žฐ RustDesk ๅ่ฎฎ้›†ๆˆ,ๅ…่ฎธไฝฟ็”จๆ ‡ๅ‡† RustDesk ๅฎขๆˆท็ซฏ่ฎฟ้—ฎ One-KVM ่ฎพๅค‡ใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- RustDesk ๅ่ฎฎๅฎž็Žฐ (Protobuf + NaCl ๅŠ ๅฏ†) -- Rendezvous ๆœๅŠกๅ™จ (hbbs) ้€šไฟก -- ไธญ็ปงๆœๅŠกๅ™จ (hbbr) ้€šไฟก -- P2P ็›ด่ฟžไธŽไธญ็ปงๅ›ž้€€ -- ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟžๆ”ฏๆŒ -- ่ง†้ข‘/้Ÿณ้ข‘/HID ่ฝฌๆข -- ็ซฏๅˆฐ็ซฏๅŠ ๅฏ† (Curve25519 + XSalsa20-Poly1305) -- ็ญพๅ่ฎค่ฏ (Ed25519) -- ๅŠจๆ€็ผ–็ ๅ™จๅๅ•† (H264/H265/VP8/VP9) -- ่พ“ๅ…ฅ่Š‚ๆต (้˜ฒๆญข HID EAGAIN) -- CapsLock ็Šถๆ€ๅŒๆญฅ -- ็ฎก้“่‡ชๅŠจ้‡่ฎข้˜… (ๆ”ฏๆŒ็ƒญๆ›ดๆ–ฐ) - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/rustdesk/ -โ”œโ”€โ”€ mod.rs # RustDeskService ไธปๆœๅŠก็ฑป -โ”œโ”€โ”€ connection.rs # ๅฎขๆˆท็ซฏ่ฟžๆŽฅๅค„็† (Connection, ConnectionManager) -โ”œโ”€โ”€ rendezvous.rs # Rendezvous ไธญไป‹่€… (RendezvousMediator) -โ”œโ”€โ”€ punch.rs # P2P ็›ด่ฟžๅฐ่ฏ•ไธŽไธญ็ปงๅ›ž้€€ -โ”œโ”€โ”€ crypto.rs # NaCl ๅŠ ๅฏ† (Curve25519 + Ed25519) -โ”œโ”€โ”€ config.rs # ้…็ฝฎ็ฎก็† (RustDeskConfig) -โ”œโ”€โ”€ hid_adapter.rs # HID ไบ‹ไปถ่ฝฌๆข (RustDesk โ†’ One-KVM) -โ”œโ”€โ”€ frame_adapters.rs # ้Ÿณ่ง†้ข‘ๅธง่ฝฌๆข (้›ถๆ‹ท่ดไผ˜ๅŒ–) -โ”œโ”€โ”€ protocol.rs # Protobuf ๅ่ฎฎๅŒ…่ฃ… -โ””โ”€โ”€ bytes_codec.rs # ๅ˜้•ฟๅธง็ผ–่งฃ็  -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 RustDesk ็ฝ‘็ปœๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ RustDesk Network Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ RustDesk โ”‚ โ”‚ One-KVM โ”‚ -โ”‚ Client โ”‚ โ”‚ Device โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ”‚ 1. ๆŸฅ่ฏข่ฎพๅค‡ๅœฐๅ€ โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ hbbs โ”‚ โ”‚ - โ”‚ โ”‚ (Rendezvous)โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ”‚ 2. ่ฟ”ๅ›žๅœฐๅ€ โ”‚ - โ”‚ โ”‚ - โ”‚ 3a. ็›ดๆŽฅ่ฟžๆŽฅ (ๅฆ‚ๆžœๅฏ่พพ) โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 3b. ไธญ็ปง่ฟžๆŽฅ (ๅฆ‚ๆžœ NAT) โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ hbbr โ”‚ โ”‚ - โ”‚ โ”‚ (Relay) โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 4. ๅปบ็ซ‹ๅŠ ๅฏ†้€š้“ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 5. ไผ ่พ“่ง†้ข‘/้Ÿณ้ข‘/HID โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ -``` - -### 2.2 ๆจกๅ—ๅ†…้ƒจๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ RustDesk Module Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ RustDeskService โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Rendezvous โ”‚ โ”‚ Connection โ”‚ โ”‚ Crypto โ”‚ -โ”‚ Mediator โ”‚ โ”‚ Manager โ”‚ โ”‚ (Keys) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ”‚ UDP โ”‚ TCP (P2P/Relay/Intranet) - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ hbbs Server โ”‚ โ”‚ Connections โ”‚ -โ”‚ (Registration) โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ Connection 1 โ”‚ โ”‚ - โ”‚ โ”‚ Connection 2 โ”‚ โ”‚ - โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HID Adapter โ”‚ โ”‚ Frame Adapters โ”‚ โ”‚ Input Throttler โ”‚ -โ”‚ (Event Conv) โ”‚ โ”‚ (Zero-Copy) โ”‚ โ”‚ (Anti-EAGAIN) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HID โ”‚ โ”‚ Video/Audio Manager โ”‚ -โ”‚ Controller โ”‚ โ”‚ (Shared Pipeline) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 RustDeskService (mod.rs) - -RustDesk ๆœๅŠกไธป็ฑป,็ฎก็†ๆ•ดไธช RustDesk ๅ่ฎฎ้›†ๆˆใ€‚ - -```rust -pub struct RustDeskService { - config: Arc>, - status: Arc>, - rendezvous: Arc>>>, - rendezvous_handle: Arc>>>, - tcp_listener_handle: Arc>>>, - listen_port: Arc>, - connection_manager: Arc, - video_manager: Arc, - hid: Arc, - audio: Arc, - shutdown_tx: broadcast::Sender<()>, -} - -impl RustDeskService { - /// ๅˆ›ๅปบๆ–ฐๆœๅŠกๅฎžไพ‹ - pub fn new( - config: RustDeskConfig, - video_manager: Arc, - hid: Arc, - audio: Arc, - ) -> Self; - - /// ๅฏๅŠจๆœๅŠก (ๅฏๅŠจ Rendezvous ๆณจๅ†Œๅ’Œ TCP ็›‘ๅฌ) - pub async fn start(&self) -> anyhow::Result<()>; - - /// ๅœๆญขๆœๅŠก - pub async fn stop(&self) -> anyhow::Result<()>; - - /// ้‡ๅฏๆœๅŠก (็”จไบŽ้…็ฝฎๆ›ดๆ–ฐ) - pub async fn restart(&self, config: RustDeskConfig) -> anyhow::Result<()>; - - /// ่Žทๅ–่ฎพๅค‡ ID - pub fn device_id(&self) -> String; - - /// ่Žทๅ–ๆœๅŠก็Šถๆ€ - pub fn status(&self) -> ServiceStatus; - - /// ่Žทๅ– Rendezvous ็Šถๆ€ - pub fn rendezvous_status(&self) -> Option; - - /// ่Žทๅ–่ฟžๆŽฅๆ•ฐ้‡ - pub fn connection_count(&self) -> usize; - - /// ่Žทๅ– TCP ็›‘ๅฌ็ซฏๅฃ - pub fn listen_port(&self) -> u16; - - /// ไฟๅญ˜ๅ‡ญๆฎ (ๅฏ†้’ฅๅ’Œ UUID) ๅˆฐ้…็ฝฎ - /// ่ฟ”ๅ›žๆ›ดๆ–ฐๅŽ็š„้…็ฝฎ (ๅฆ‚ๆžœๆœ‰ๅ˜ๆ›ด) - pub fn save_credentials(&self) -> Option; -} - -pub enum ServiceStatus { - Stopped, - Starting, - Running, - Error(String), -} -``` - -**ไธป่ฆๆต็จ‹:** -1. **ๅฏๅŠจๆต็จ‹**: ๅˆๅง‹ๅŒ–ๅŠ ๅฏ† โ†’ ๅฏๅŠจ TCP ็›‘ๅฌๅ™จ โ†’ ๅˆ›ๅปบ Rendezvous Mediator โ†’ ่ฎพ็ฝฎๅ›ž่ฐƒ โ†’ ๅผ€ๅง‹ๆณจๅ†Œๅพช็Žฏ -2. **่ฟžๆŽฅๅค„็†**: P2P ็›ด่ฟžๅฐ่ฏ• โ†’ ไธญ็ปงๅ›ž้€€ โ†’ ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž -3. **ๅœๆญขๆต็จ‹**: ๅ‘้€ๅœๆญขไฟกๅท โ†’ ๅ…ณ้—ญๆ‰€ๆœ‰่ฟžๆŽฅ โ†’ ๅœๆญข Rendezvous โ†’ ็ญ‰ๅพ…ไปปๅŠก็ป“ๆŸ - -### 3.2 RendezvousMediator (rendezvous.rs) - -Rendezvous ๆœๅŠกๅ™จ้€šไฟกไธญไป‹่€…,ๅค„็†่ฎพๅค‡ๆณจๅ†Œใ€ๅฟƒ่ทณ็ปดๆŠคๅ’Œ่ฟžๆŽฅ่ฏทๆฑ‚ใ€‚ - -```rust -pub struct RendezvousMediator { - config: Arc>, - keypair: Arc>>, // Curve25519 ๅŠ ๅฏ†ๅฏ†้’ฅ - signing_keypair: Arc>>, // Ed25519 ็ญพๅๅฏ†้’ฅ - status: Arc>, - uuid: Arc>, // ่ฎพๅค‡ UUID (ๆŒไน…ๅŒ–) - uuid_needs_save: Arc>, - serial: Arc>, // ้…็ฝฎๅบๅˆ—ๅท - key_confirmed: Arc>, // ๅ…ฌ้’ฅๆณจๅ†Œ็กฎ่ฎค - keep_alive_ms: Arc>, - relay_callback: Arc>>, - punch_callback: Arc>>, - intranet_callback: Arc>>, - listen_port: Arc>, // TCP ็›‘ๅฌ็ซฏๅฃ - shutdown_tx: broadcast::Sender<()>, -} - -impl RendezvousMediator { - /// ๅˆ›ๅปบๆ–ฐ็š„ Rendezvous ไธญไป‹่€… - pub fn new(config: RustDeskConfig) -> Self; - - /// ๅฏๅŠจๆณจๅ†Œๅพช็Žฏ - pub async fn start(&self) -> anyhow::Result<()>; - - /// ๅœๆญขไธญไป‹่€… - pub fn stop(&self); - - /// ่Žทๅ–ๆˆ–็”ŸๆˆๅŠ ๅฏ†ๅฏ†้’ฅๅฏน - pub fn ensure_keypair(&self) -> KeyPair; - - /// ่Žทๅ–ๆˆ–็”Ÿๆˆ็ญพๅๅฏ†้’ฅๅฏน - pub fn ensure_signing_keypair(&self) -> SigningKeyPair; - - /// ่ฎพ็ฝฎ TCP ็›‘ๅฌ็ซฏๅฃ - pub fn set_listen_port(&self, port: u16); - - /// ่ฎพ็ฝฎไธญ็ปง่ฏทๆฑ‚ๅ›ž่ฐƒ - pub fn set_relay_callback(&self, callback: RelayCallback); - - /// ่ฎพ็ฝฎ P2P ็ฉฟๅญ”ๅ›ž่ฐƒ - pub fn set_punch_callback(&self, callback: PunchCallback); - - /// ่ฎพ็ฝฎๅฑ€ๅŸŸ็ฝ‘่ฟžๆŽฅๅ›ž่ฐƒ - pub fn set_intranet_callback(&self, callback: IntranetCallback); - - /// ่Žทๅ–ๅฝ“ๅ‰็Šถๆ€ - pub fn status(&self) -> RendezvousStatus; - - /// ๆฃ€ๆŸฅ UUID ๆ˜ฏๅฆ้œ€่ฆไฟๅญ˜ - pub fn uuid_needs_save(&self) -> bool; - - /// ๆ ‡่ฎฐ UUID ๅทฒไฟๅญ˜ - pub fn mark_uuid_saved(&self); -} - -pub enum RendezvousStatus { - Disconnected, // ๆœช่ฟžๆŽฅ - Connecting, // ๆญฃๅœจ่ฟžๆŽฅ - Connected, // ๅทฒ่ฟžๆŽฅไฝ†ๆœชๆณจๅ†Œ - Registered, // ๅทฒๆณจๅ†Œ - Error(String), // ้”™่ฏฏ็Šถๆ€ -} - -/// ไธญ็ปง่ฏทๆฑ‚ๅ›ž่ฐƒ -/// ๅ‚ๆ•ฐ: (rendezvous_addr, relay_server, uuid, socket_addr, device_id) -pub type RelayCallback = Arc, String) + Send + Sync>; - -/// P2P ็ฉฟๅญ”ๅ›ž่ฐƒ -/// ๅ‚ๆ•ฐ: (peer_addr, rendezvous_addr, relay_server, uuid, socket_addr, device_id) -pub type PunchCallback = Arc, String, String, String, Vec, String) + Send + Sync>; - -/// ๅฑ€ๅŸŸ็ฝ‘่ฟžๆŽฅๅ›ž่ฐƒ -/// ๅ‚ๆ•ฐ: (rendezvous_addr, peer_socket_addr, local_addr, relay_server, device_id) -pub type IntranetCallback = Arc, SocketAddr, String, String) + Send + Sync>; -``` - -**ๅ…ณ้”ฎๆœบๅˆถ:** -- **UDP ้€šไฟก**: ไฝฟ็”จ UDP ไธŽ hbbs ๆœๅŠกๅ™จ้€šไฟก -- **ๅŒๅฏ†้’ฅ็ณป็ปŸ**: Curve25519 ็”จไบŽๅŠ ๅฏ†,Ed25519 ็”จไบŽ็ญพๅ -- **UUID ๆŒไน…ๅŒ–**: ้ฟๅ…้‡ๆ–ฐๆณจๅ†Œๆ—ถ็š„ UUID_MISMATCH ้”™่ฏฏ -- **ๅœฐๅ€ๆททๆท†**: ไฝฟ็”จ `AddrMangle` ็ผ–็ ๅœฐๅ€้ฟๅ…้˜ฒ็ซๅข™็ฏกๆ”น -- **ไธ‰็ง่ฟžๆŽฅๆจกๅผ**: P2P ็›ด่ฟžใ€ไธญ็ปง่ฟžๆŽฅใ€ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž - -### 3.3 Connection & ConnectionManager (connection.rs) - -ๅฎขๆˆท็ซฏ่ฟžๆŽฅๅค„็†,ๅŒ…ๅซ่ฟžๆŽฅ็”Ÿๅ‘ฝๅ‘จๆœŸ็ฎก็†ๅ’Œๆ•ฐๆฎไผ ่พ“ใ€‚ - -```rust -/// ๅ•ไธชๅฎขๆˆท็ซฏ่ฟžๆŽฅ -pub struct Connection { - id: u32, - device_id: String, - peer_id: String, - state: Arc>, - signing_keypair: SigningKeyPair, - temp_keypair: (box_::PublicKey, box_::SecretKey), // ๆฏ่ฟžๆŽฅไธดๆ—ถๅฏ†้’ฅ - password: String, - hid: Option>, - audio: Option>, - video_manager: Option>, - session_key: Option, - encryption_enabled: bool, - negotiated_codec: Option, - input_throttler: InputThrottler, // ่พ“ๅ…ฅ่Š‚ๆต้˜ฒๆญข EAGAIN - last_caps_lock: bool, // CapsLock ็Šถๆ€่ทŸ่ธช - // ... ๆ›ดๅคšๅญ—ๆฎต -} - -impl Connection { - /// ๅˆ›ๅปบๆ–ฐ่ฟžๆŽฅ - pub fn new( - id: u32, - config: &RustDeskConfig, - signing_keypair: SigningKeyPair, - hid: Option>, - audio: Option>, - video_manager: Option>, - ) -> (Self, mpsc::UnboundedReceiver); - - /// ๅค„็† TCP ่ฟžๆŽฅ - pub async fn handle_tcp(&mut self, stream: TcpStream, peer_addr: SocketAddr) -> anyhow::Result<()>; - - /// ๅ…ณ้—ญ่ฟžๆŽฅ - pub fn close(&self); -} - -/// ่ฟžๆŽฅ็ฎก็†ๅ™จ -pub struct ConnectionManager { - connections: Arc>>>>, - next_id: Arc>, - config: Arc>, - keypair: Arc>>, - signing_keypair: Arc>>, - hid: Arc>>>, - audio: Arc>>>, - video_manager: Arc>>>, -} - -impl ConnectionManager { - pub fn new(config: RustDeskConfig) -> Self; - pub async fn accept_connection(&self, stream: TcpStream, peer_addr: SocketAddr) -> anyhow::Result; - pub fn connection_count(&self) -> usize; - pub fn close_all(&self); -} - -pub enum ConnectionState { - Pending, // ็ญ‰ๅพ…่ฟžๆŽฅ - Handshaking, // ๆกๆ‰‹ไธญ - Active, // ๆดป่ทƒ - Closed, // ๅทฒๅ…ณ้—ญ - Error(String), // ้”™่ฏฏ -} - -/// ่พ“ๅ…ฅ่Š‚ๆตๅ™จ (้˜ฒๆญข HID EAGAIN ้”™่ฏฏ) -struct InputThrottler { - last_mouse_time: Instant, - mouse_interval: Duration, // ้ป˜่ฎค 16ms (โ‰ˆ60Hz) -} -``` - -**่ฟžๆŽฅๆต็จ‹:** -1. **ๆกๆ‰‹**: ๅ‘้€ SignedId (ๅซไธดๆ—ถๅ…ฌ้’ฅ) โ†’ ๆŽฅๆ”ถ PublicKey (ๅซๅฏน็งฐๅฏ†้’ฅ) โ†’ ่งฃๅฏ†ๅฏน็งฐๅฏ†้’ฅ -2. **่ฎค่ฏ**: ๅ‘้€ Hash (ๅฏ†็ ็›) โ†’ ๆŽฅๆ”ถ LoginRequest (ๅฏ†็ ๅ“ˆๅธŒ) โ†’ ้ชŒ่ฏๅฏ†็  -3. **็ผ–็ ๅๅ•†**: ๆ นๆฎๅฏ็”จ็ผ–็ ๅ™จ้€‰ๆ‹ฉๆœ€ไผ˜็ผ–่งฃ็ ๅ™จ(H264 > H265 > VP8 > VP9) -4. **ๆตไผ ่พ“**: ่ฎข้˜…ๅ…ฑไบซ่ง†้ข‘/้Ÿณ้ข‘็ฎก้“ โ†’ ่ฝฌๆขไธบ RustDesk ๆ ผๅผ โ†’ ๅŠ ๅฏ†ๅ‘้€ -5. **่พ“ๅ…ฅๅค„็†**: ๆŽฅๆ”ถ KeyEvent/MouseEvent โ†’ ่Š‚ๆต โ†’ ่ฝฌๆขไธบ USB HID โ†’ ๅ‘้€ๅˆฐ HID ๆŽงๅˆถๅ™จ - -### 3.4 RustDeskKeys (crypto.rs) - -ๅŠ ๅฏ†ๅฏ†้’ฅ็ฎก็†ใ€‚ - -```rust -pub struct RustDeskKeys { - /// ่ฎพๅค‡ ID - pub device_id: String, - - /// Curve25519 ๅ…ฌ้’ฅ - pub public_key: [u8; 32], - - /// Curve25519 ็ง้’ฅ - secret_key: [u8; 32], - - /// Ed25519 ็ญพๅๅ…ฌ้’ฅ - pub sign_public_key: [u8; 32], - - /// Ed25519 ็ญพๅ็ง้’ฅ - sign_secret_key: [u8; 64], -} - -impl RustDeskKeys { - /// ็”Ÿๆˆๆ–ฐๅฏ†้’ฅ - pub fn generate() -> Self; - - /// ไปŽ้…็ฝฎๅŠ ่ฝฝ - pub fn from_config(config: &KeyConfig) -> Result; - - /// ไฟๅญ˜ๅˆฐ้…็ฝฎ - pub fn to_config(&self) -> KeyConfig; - - /// ่ฎก็ฎ—ๅ…ฑไบซๅฏ†้’ฅ - pub fn shared_secret(&self, peer_public_key: &[u8; 32]) -> [u8; 32]; - - /// ็ญพๅๆถˆๆฏ - pub fn sign(&self, message: &[u8]) -> [u8; 64]; - - /// ้ชŒ่ฏ็ญพๅ - pub fn verify(public_key: &[u8; 32], message: &[u8], signature: &[u8; 64]) -> bool; -} - -pub struct EncryptedChannel { - /// ๅ‘้€ๅฏ†้’ฅ - send_key: [u8; 32], - - /// ๆŽฅๆ”ถๅฏ†้’ฅ - recv_key: [u8; 32], - - /// ๅ‘้€ nonce - send_nonce: AtomicU64, - - /// ๆŽฅๆ”ถ nonce - recv_nonce: AtomicU64, -} - -impl EncryptedChannel { - /// ๅŠ ๅฏ†ๆถˆๆฏ - pub fn encrypt(&self, plaintext: &[u8]) -> Vec; - - /// ่งฃๅฏ†ๆถˆๆฏ - pub fn decrypt(&self, ciphertext: &[u8]) -> Result>; -} -``` - -### 3.5 HidAdapter (hid_adapter.rs) - -RustDesk HID ไบ‹ไปถ่ฝฌๆขใ€‚ - -```rust -pub struct HidAdapter { - hid: Arc, -} - -impl HidAdapter { - /// ๅˆ›ๅปบ้€‚้…ๅ™จ - pub fn new(hid: Arc) -> Self; - - /// ๅค„็†้”ฎ็›˜ไบ‹ไปถ - pub async fn handle_keyboard(&self, event: &RdKeyboardEvent) -> Result<()>; - - /// ๅค„็†้ผ ๆ ‡ไบ‹ไปถ - pub async fn handle_mouse(&self, event: &RdMouseEvent) -> Result<()>; - - /// ่ฝฌๆข้”ฎ็  - fn convert_keycode(rd_key: u32) -> Option; - - /// ่ฝฌๆข้ผ ๆ ‡ๆŒ‰้’ฎ - fn convert_button(rd_button: u32) -> Option; -} - -/// RustDesk ้”ฎ็›˜ไบ‹ไปถ -pub struct RdKeyboardEvent { - pub keycode: u32, - pub down: bool, - pub modifiers: u32, -} - -/// RustDesk ้ผ ๆ ‡ไบ‹ไปถ -pub struct RdMouseEvent { - pub x: i32, - pub y: i32, - pub mask: u32, -} -``` - -### 3.6 FrameAdapter (frame_adapters.rs) - -ๅธงๆ ผๅผ่ฝฌๆข๏ผŒๆ”ฏๆŒ้›ถๆ‹ท่ดใ€‚ - -```rust -pub struct VideoFrameAdapter { - codec: VideoCodec, - seq: u32, - timestamp_base: u64, -} - -impl VideoFrameAdapter { - /// ๅˆ›ๅปบ้€‚้…ๅ™จ - pub fn new(codec: VideoCodec) -> Self; - - /// ้›ถๆ‹ท่ด่ฝฌๆข (ๆŽจ่) - /// Bytes ๆ˜ฏๅผ•็”จ่ฎกๆ•ฐ็ฑปๅž‹๏ผŒclone ๅชๅขžๅŠ ๅผ•็”จ่ฎกๆ•ฐ - pub fn encode_frame_bytes_zero_copy( - &mut self, - data: Bytes, - is_keyframe: bool, - timestamp_ms: u64, - ) -> Bytes; - - /// ่ฝฌๆข่ง†้ข‘ๅธงๅˆฐ RustDesk ๆ ผๅผ (ไผšๆ‹ท่ดๆ•ฐๆฎ) - pub fn encode_frame_bytes( - &mut self, - data: &[u8], - is_keyframe: bool, - timestamp_ms: u64, - ) -> Bytes; -} - -/// RustDesk ่ง†้ข‘ๅธง (protobuf ็”Ÿๆˆ) -/// ๆณจๆ„: data ๅญ—ๆฎตไฝฟ็”จ bytes::Bytes ็ฑปๅž‹ไปฅๆ”ฏๆŒ้›ถๆ‹ท่ด -pub struct EncodedVideoFrame { - pub data: Bytes, // ้›ถๆ‹ท่ด: ๅผ•็”จ่ฎกๆ•ฐๅ…ฑไบซ - pub key: bool, - pub pts: i64, -} - -pub enum VideoCodec { - H264, - H265, - VP8, - VP9, - AV1, -} -``` - -### 3.7 ๅ่ฎฎๆถˆๆฏ (protocol.rs) - -Protobuf ๆถˆๆฏๅŒ…่ฃ…๏ผŒไฝฟ็”จ protobuf-rust๏ผˆไธŽ RustDesk ๆœๅŠกๅ™จไธ€่‡ด๏ผ‰ใ€‚ - -```rust -/// ไฝฟ็”จ protobuf-codegen ็”Ÿๆˆ็š„ protobuf ๆถˆๆฏ -pub mod hbb { - include!(concat!(env!("OUT_DIR"), "/protos/mod.rs")); -} - -// Re-export commonly used types -pub use hbb::rendezvous::{...}; -pub use hbb::message::{...}; - -/// ่งฃ็  RendezvousMessage -pub fn decode_rendezvous_message(buf: &[u8]) -> Result { - RendezvousMessage::parse_from_bytes(buf) -} - -/// ่งฃ็  Message (session message) -pub fn decode_message(buf: &[u8]) -> Result { - Message::parse_from_bytes(buf) -} -``` - -### 3.8 ๅธง็ผ–็  (bytes_codec.rs) - -ๅ˜้•ฟๅธงๅ่ฎฎใ€‚ - -```rust -pub struct BytesCodec { - state: DecodeState, - buffer: BytesMut, -} - -impl BytesCodec { - /// ็ผ–็ ๅธง - pub fn encode_frame(data: &[u8]) -> Vec { - let mut buf = Vec::with_capacity(4 + data.len()); - buf.extend_from_slice(&(data.len() as u32).to_be_bytes()); - buf.extend_from_slice(data); - buf - } - - /// ่งฃ็ ๅธง - pub fn decode_frame(&mut self, src: &mut BytesMut) -> Result>; -} - -enum DecodeState { - Length, - Data(usize), -} -``` - ---- - -## 4. ๅ่ฎฎ่ฏฆ่งฃ - -### 4.1 Protobuf ๅฎšไน‰ - -```protobuf -// protos/rendezvous.proto -message RegisterPeer { - string id = 1; - bytes public_key = 2; -} - -message RegisterPeerResponse { - bool ok = 1; - string error = 2; -} - -message PunchHoleRequest { - string id = 1; - string nat_type = 2; -} - -// protos/message.proto -message VideoFrame { - bytes data = 1; - bool key = 2; - int64 pts = 3; - VideoCodec codec = 4; -} - -message AudioFrame { - bytes data = 1; - int64 timestamp = 2; -} - -message KeyboardEvent { - uint32 keycode = 1; - bool down = 2; - uint32 modifiers = 3; -} - -message MouseEvent { - int32 x = 1; - int32 y = 2; - uint32 mask = 3; -} -``` - -### 4.2 ่ฟžๆŽฅๆกๆ‰‹ - -``` -1. TCP ่ฟžๆŽฅ - Client โ”€โ”€โ”€โ”€โ–บ Device - -2. ๅ…ฌ้’ฅไบคๆข - Client โ—„โ”€โ”€โ”€โ–บ Device - -3. DH ๅฏ†้’ฅๅๅ•† - shared_secret = X25519(my_private, peer_public) - -4. ๅฏ†้’ฅๆดพ็”Ÿ - send_key = HKDF(shared_secret, "send") - recv_key = HKDF(shared_secret, "recv") - -5. ่ฎค่ฏ (ๅฏ้€‰) - Client โ”€โ”€โ”€โ”€โ–บ Device: encrypted(password) - Client โ—„โ”€โ”€โ”€โ”€ Device: encrypted(ok/fail) - -6. ๅผ€ๅง‹ไผ ่พ“ -``` - ---- - -## 5. ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct RustDeskConfig { - /// ๆ˜ฏๅฆๅฏ็”จ - pub enabled: bool, - - /// Rendezvous ๆœๅŠกๅ™จๅœฐๅ€ (hbbs) - /// ๆ ผๅผ: "rs.example.com" ๆˆ– "192.168.1.100:21116" - /// ๅฟ…ๅกซ้กน - ไธๅ†ๆไพ›ๅ…ฌๅ…ฑๆœๅŠกๅ™จ,้œ€่‡ช่กŒ้…็ฝฎ - pub rendezvous_server: String, - - /// ไธญ็ปงๆœๅŠกๅ™จๅœฐๅ€ (hbbr),้ป˜่ฎคไธŽ rendezvous ๅŒไธปๆœบ - /// ๆ ผๅผ: "relay.example.com" ๆˆ– "192.168.1.100:21117" - pub relay_server: Option, - - /// ไธญ็ปงๆœๅŠกๅ™จ่ฎค่ฏๅฏ†้’ฅ (ๅฆ‚ๆžœไธญ็ปงๆœๅŠกๅ™จไฝฟ็”จ -k ้€‰้กน) - #[typeshare(skip)] - pub relay_key: Option, - - /// ่ฎพๅค‡ ID (9 ไฝๆ•ฐๅญ—),่‡ชๅŠจ็”Ÿๆˆ - pub device_id: String, - - /// ่ฎพๅค‡ๅฏ†็  (ๅฎขๆˆท็ซฏ่ฟžๆŽฅ่ฎค่ฏ) - #[typeshare(skip)] - pub device_password: String, - - /// Curve25519 ๅ…ฌ้’ฅ (Base64 ็ผ–็ ),็”จไบŽๅŠ ๅฏ† - #[typeshare(skip)] - pub public_key: Option, - - /// Curve25519 ็ง้’ฅ (Base64 ็ผ–็ ),็”จไบŽๅŠ ๅฏ† - #[typeshare(skip)] - pub private_key: Option, - - /// Ed25519 ็ญพๅๅ…ฌ้’ฅ (Base64 ็ผ–็ ),็”จไบŽ SignedId ้ชŒ่ฏ - #[typeshare(skip)] - pub signing_public_key: Option, - - /// Ed25519 ็ญพๅ็ง้’ฅ (Base64 ็ผ–็ ),็”จไบŽ็ญพๅ SignedId - #[typeshare(skip)] - pub signing_private_key: Option, - - /// UUID (ๆŒไน…ๅŒ–,้ฟๅ… UUID_MISMATCH ้”™่ฏฏ) - #[typeshare(skip)] - pub uuid: Option, -} - -impl RustDeskConfig { - /// ๆฃ€ๆŸฅ้…็ฝฎๆ˜ฏๅฆๆœ‰ๆ•ˆ - pub fn is_valid(&self) -> bool; - - /// ่Žทๅ–ๆœ‰ๆ•ˆ็š„ Rendezvous ๆœๅŠกๅ™จๅœฐๅ€ - pub fn effective_rendezvous_server(&self) -> &str; - - /// ่Žทๅ–ๅธฆ้ป˜่ฎค็ซฏๅฃ็š„ Rendezvous ๅœฐๅ€ - pub fn rendezvous_addr(&self) -> String; - - /// ่Žทๅ–ๅธฆ้ป˜่ฎค็ซฏๅฃ็š„ไธญ็ปงๆœๅŠกๅ™จๅœฐๅ€ - pub fn relay_addr(&self) -> Option; - - /// ็กฎไฟ UUID ๅญ˜ๅœจ (่‡ชๅŠจ็”Ÿๆˆๅนถๆ ‡่ฎฐ้œ€่ฆไฟๅญ˜) - pub fn ensure_uuid(&mut self) -> ([u8; 16], bool); -} -``` - -### ้…็ฝฎๆ–‡ไปถ็คบไพ‹ - -**ไฝฟ็”จ่‡ชๅปบๆœๅŠกๅ™จ:** -```toml -[rustdesk] -enabled = true -rendezvous_server = "192.168.1.100:21116" -relay_server = "192.168.1.100:21117" -device_id = "123456789" -device_password = "mypassword" -# ๅฏ†้’ฅๅ’Œ UUID ็”ฑ็จ‹ๅบ่‡ชๅŠจ็”Ÿๆˆๅ’Œไฟๅญ˜ -``` - -**ๆณจๆ„**: ไธๅ†ๆไพ›ๅ…ฌๅ…ฑๆœๅŠกๅ™จ,้œ€่‡ช่กŒ้…็ฝฎ RustDesk ๆœๅŠกๅ™จใ€‚ - ---- - -## 6. API ็ซฏ็‚น - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/api/rustdesk/status` | GET | ่Žทๅ–ๆœๅŠก็Šถๆ€ | -| `/api/rustdesk/start` | POST | ๅฏๅŠจๆœๅŠก | -| `/api/rustdesk/stop` | POST | ๅœๆญขๆœๅŠก | -| `/api/rustdesk/config` | GET | ่Žทๅ–้…็ฝฎ | -| `/api/rustdesk/config` | PATCH | ๆ›ดๆ–ฐ้…็ฝฎ | -| `/api/rustdesk/device-id` | GET | ่Žทๅ–่ฎพๅค‡ ID | -| `/api/rustdesk/connections` | GET | ่Žทๅ–่ฟžๆŽฅๅˆ—่กจ | -| `/api/rustdesk/connections/:id` | DELETE | ๆ–ญๅผ€่ฟžๆŽฅ | - -### ๅ“ๅบ”ๆ ผๅผ - -```json -// GET /api/rustdesk/status -{ - "status": "running", - "device_id": "123456789", - "rendezvous_connected": true, - "active_connections": 1 -} - -// GET /api/rustdesk/connections -{ - "connections": [ - { - "id": "conn-abc", - "peer_id": "987654321", - "connected_at": "2024-01-15T10:30:00Z", - "ip": "192.168.1.100" - } - ] -} -``` - ---- - -## 7. ไบ‹ไปถ - -```rust -pub enum SystemEvent { - RustDeskStatusChanged { - status: String, - device_id: Option, - error: Option, - }, - - RustDeskConnectionOpened { - connection_id: String, - peer_id: String, - }, - - RustDeskConnectionClosed { - connection_id: String, - peer_id: String, - reason: String, - }, -} -``` - ---- - -## 8. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum RustDeskError { - #[error("Service not running")] - NotRunning, - - #[error("Already running")] - AlreadyRunning, - - #[error("Rendezvous connection failed: {0}")] - RendezvousFailed(String), - - #[error("Authentication failed")] - AuthFailed, - - #[error("Connection refused")] - ConnectionRefused, - - #[error("Encryption error: {0}")] - EncryptionError(String), - - #[error("Protocol error: {0}")] - ProtocolError(String), - - #[error("Timeout")] - Timeout, -} -``` - ---- - -## 9. ไฝฟ็”จ็คบไพ‹ - -### 9.1 ๅฏๅŠจๆœๅŠก - -```rust -let config = RustDeskConfig { - enabled: true, - rendezvous_server: "hbbs.example.com:21116".to_string(), // ๅฟ…ๅกซ - ้…็ฝฎๆ‚จ็š„ๆœๅŠกๅ™จ - device_id: "123456789".to_string(), - device_password: "mypassword".to_string(), - ..Default::default() -}; - -let service = RustDeskService::new( - config, - video_manager, - hid, - audio, -); - -service.start().await?; - -println!("Device ID: {}", service.device_id()); -println!("Listen Port: {}", service.listen_port()); -println!("Status: {:?}", service.status()); -``` - -### 9.2 ไฝฟ็”จ่‡ชๅปบๆœๅŠกๅ™จ - -```rust -let config = RustDeskConfig { - enabled: true, - rendezvous_server: "192.168.1.100:21116".to_string(), - relay_server: Some("192.168.1.100:21117".to_string()), - relay_key: Some("your_licence_key".to_string()), // ๅฆ‚ๆžœไฝฟ็”จ -k ้€‰้กน - device_id: "123456789".to_string(), - device_password: "mypassword".to_string(), - ..Default::default() -}; - -let service = RustDeskService::new(config, video_manager, hid, audio); -service.start().await?; -``` - -### 9.3 ๅฎขๆˆท็ซฏ่ฟžๆŽฅ - -**ไฝฟ็”จ RustDesk ๅฎขๆˆท็ซฏ:** -1. ไธ‹่ฝฝๅนถๅฎ‰่ฃ… RustDesk ๅฎขๆˆท็ซฏ ([https://rustdesk.com](https://rustdesk.com)) -2. ๅฆ‚ๆžœไฝฟ็”จ่‡ชๅปบๆœๅŠกๅ™จ,ๅœจ่ฎพ็ฝฎไธญ้…็ฝฎ ID ๆœๅŠกๅ™จๅœฐๅ€ -3. ่พ“ๅ…ฅ่ฎพๅค‡ ID (9 ไฝๆ•ฐๅญ—) -4. ่พ“ๅ…ฅๅฏ†็  (ๅฆ‚ๆžœ่ฎพ็ฝฎ) -5. ็‚นๅ‡ป่ฟžๆŽฅ - -**่ฟžๆŽฅ่ฟ‡็จ‹:** -``` -ๅฎขๆˆท็ซฏ One-KVM - โ”‚ โ”‚ - โ”‚ 1. ๆŸฅ่ฏข่ฎพๅค‡ (hbbs) โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ hbbs โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ”‚ 2. ่ฟ”ๅ›žๅœฐๅ€ไฟกๆฏ โ”‚ - โ”‚ โ”‚ - โ”‚ 3a. ๅฐ่ฏ• P2P ็›ด่ฟž (3s ่ถ…ๆ—ถ) โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 3b. ๅคฑ่ดฅๅˆ™้€š่ฟ‡ไธญ็ปง (hbbr) โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ hbbr โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 4. ๆกๆ‰‹ + ่ฎค่ฏ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ - โ”‚ 5. ่ง†้ข‘/้Ÿณ้ข‘/HID ไผ ่พ“ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ -``` - -### 9.4 ไฟๅญ˜ๅ‡ญๆฎ - -```rust -// ๅฏๅŠจๅŽ,ๅ‡ญๆฎไผš่‡ชๅŠจ็”Ÿๆˆ -// ๅฎšๆœŸไฟๅญ˜ๅ‡ญๆฎๅˆฐ้…็ฝฎๆ–‡ไปถ,้ฟๅ…้‡ๅฏๅŽ UUID ๅ˜ๅŒ– -if let Some(updated_config) = service.save_credentials() { - // ไฟๅญ˜ๅˆฐ้…็ฝฎๅญ˜ๅ‚จ - config_manager.save_rustdesk_config(&updated_config).await?; -} -``` - ---- - -## 10. ๆ€ง่ƒฝไผ˜ๅŒ– - -### 10.1 ้›ถๆ‹ท่ด่ฎพ่ฎก - -RustDesk ๆจกๅ—ไฝฟ็”จ `bytes::Bytes` ็ฑปๅž‹ๅฎž็Žฐ้›ถๆ‹ท่ด: - -```rust -// build.rs ้…็ฝฎ protobuf ไฝฟ็”จ Bytes -protobuf_codegen::Codegen::new() - .pure() - .out_dir(&protos_dir) - .inputs(["protos/rendezvous.proto", "protos/message.proto"]) - .include("protos") - .customize(protobuf_codegen::Customize::default().tokio_bytes(true)) - .run() - .expect("Failed to compile protobuf files"); - -// ๅธง่ฝฌๆขๆ—ถ็›ดๆŽฅไผ ้€’ Bytes,ๅชๅขžๅŠ ๅผ•็”จ่ฎกๆ•ฐ -let msg_bytes = video_adapter.encode_frame_bytes_zero_copy( - frame.data.clone(), // clone ๅชๅขžๅŠ ๅผ•็”จ่ฎกๆ•ฐ,ไธๆ‹ท่ดๆ•ฐๆฎ - frame.is_keyframe, - frame.pts_ms as u64, -); - -// TCP ๅ‘้€ไนŸไฝฟ็”จ Bytes,้ฟๅ…ๆ‹ท่ด -writer.write_all(&msg_bytes).await?; -``` - -### 10.2 ่พ“ๅ…ฅ่Š‚ๆต (Input Throttling) - -้˜ฒๆญข HID ่ฎพๅค‡ EAGAIN ้”™่ฏฏๅ’Œๆ้ซ˜ๆ€ง่ƒฝ: - -```rust -pub struct InputThrottler { - last_mouse_time: Instant, - mouse_interval: Duration, // ้ป˜่ฎค 16ms (โ‰ˆ60Hz) -} - -impl InputThrottler { - /// ๆฃ€ๆŸฅๆ˜ฏๅฆๅบ”่ฏฅๅ‘้€้ผ ๆ ‡ไบ‹ไปถ - pub fn should_send_mouse(&mut self) -> bool { - let now = Instant::now(); - if now.duration_since(self.last_mouse_time) >= self.mouse_interval { - self.last_mouse_time = now; - true - } else { - false // ่ทณ่ฟ‡ๆญคไบ‹ไปถ,้ฟๅ… HID ็ผ“ๅ†ฒๅŒบๆบขๅ‡บ - } - } -} -``` - -**ๆ•ˆๆžœ:** -- ้˜ฒๆญข HID write() ่ฟ”ๅ›ž EAGAIN (่ต„ๆบๆš‚ๆ—ถไธๅฏ็”จ) -- ๅ‡ๅฐ‘ CPU ไฝฟ็”จ็އ (่ฟ‡ๆปคๅ†—ไฝ™็š„้ผ ๆ ‡็งปๅŠจไบ‹ไปถ) -- ไฟๆŒๆต็•…็š„้ผ ๆ ‡ไฝ“้ชŒ (60Hz ๅทฒ่ถณๅคŸ) - -### 10.3 ๅ…ฑไบซ็ฎก้“ๆžถๆž„ - -่ง†้ข‘/้Ÿณ้ข‘ไฝฟ็”จๅ…ฑไบซ็ฎก้“,ๅคšไธช่ฟžๆŽฅ่ฎข้˜…ๅŒไธ€ๆ•ฐๆฎๆต: - -```rust -// ่ง†้ข‘็ฎก้“ (broadcast channel) -let (tx, _rx) = broadcast::channel(4); // ๅฎน้‡ 4 ๅธง - -// ่ฟžๆŽฅ 1 ่ฎข้˜… -let mut rx1 = tx.subscribe(); - -// ่ฟžๆŽฅ 2 ่ฎข้˜… (ๅ…ฑไบซๅŒไธ€็ผ–็ ๆ•ฐๆฎ) -let mut rx2 = tx.subscribe(); - -// ๅ‘้€ๅธงๆ—ถ,ๆ‰€ๆœ‰่ฎข้˜…่€…้ƒฝไผšๆ”ถๅˆฐ (้›ถๆ‹ท่ด) -tx.send(frame).unwrap(); -``` - -**ไผ˜็‚น:** -- ๅ•ๆฌก็ผ–็ ,ๅคšๆฌกไฝฟ็”จ (ๅ‡ๅฐ‘ CPU/GPU ่ดŸ่ฝฝ) -- ้›ถๆ‹ท่ดๅ…ฑไบซ (ไฝฟ็”จ `Bytes` ๅผ•็”จ่ฎกๆ•ฐ) -- ่‡ชๅŠจ่ƒŒๅŽ‹ๆŽงๅˆถ (ๆ…ขๅฎขๆˆท็ซฏไผš lag,ไธๅฝฑๅ“ๅฟซๅฎขๆˆท็ซฏ) - -### 10.4 ็ฎก้“้‡่ฎข้˜…ๆœบๅˆถ - -ๅฝ“่ง†้ข‘็ฎก้“้‡ๅฏๆ—ถ (ๅฆ‚ๅˆ‡ๆข็ ็އ),่ฟžๆŽฅ่‡ชๅŠจ้‡ๆ–ฐ่ฎข้˜…: - -```rust -async fn run_video_streaming(...) { - // ๅค–ๅฑ‚ๅพช็Žฏ: ๅค„็†็ฎก้“้‡ๅฏ - 'subscribe_loop: loop { - // ่ฎข้˜…่ง†้ข‘็ฎก้“ - let mut encoded_frame_rx = video_manager.subscribe_encoded_frames().await; - - // ๅ†…ๅฑ‚ๅพช็Žฏ: ๆŽฅๆ”ถๅธง - loop { - match encoded_frame_rx.recv().await { - Ok(frame) => { /* ๅ‘้€ๅธง */ } - Err(RecvError::Lagged(n)) => { - warn!("Video lagged {} frames", n); - } - Err(RecvError::Closed) => { - // ็ฎก้“้‡ๅฏ,้‡ๆ–ฐ่ฎข้˜… - info!("Video pipeline closed, re-subscribing..."); - tokio::time::sleep(Duration::from_millis(100)).await; - continue 'subscribe_loop; - } - } - } - } -} -``` - -### 10.5 ้ข„ๅˆ†้…็ผ“ๅ†ฒๅŒบ - -TCP ๅ‘้€ไฝฟ็”จ้ข„ๅˆ†้…็ผ“ๅ†ฒๅŒบๅ‡ๅฐ‘ๅ†…ๅญ˜ๅˆ†้…: - -```rust -// ้ข„ๅˆ†้… 128KB ็ผ“ๅ†ฒๅŒบ (่ถณๅคŸๅคง้ƒจๅˆ†่ง†้ข‘ๅธง) -let mut frame_buf = BytesMut::with_capacity(128 * 1024); - -// ๅค็”จ็ผ“ๅ†ฒๅŒบๅ‘้€ๅคšไธชๅธง -loop { - frame_buf.clear(); - write_frame_buffered(&mut writer, &data, &mut frame_buf).await?; -} -``` - -### 10.6 ็ผ–็ ๅ™จๅๅ•† - -ๆ นๆฎ็กฌไปถ่ƒฝๅŠ›ๅŠจๆ€้€‰ๆ‹ฉๆœ€ไผ˜็ผ–็ ๅ™จ: - -```rust -// ไผ˜ๅ…ˆ็บง: H264 > H265 > VP8 > VP9 -let available_encoders = video_manager.available_encoders(); -let preferred_order = [ - VideoEncoderType::H264, - VideoEncoderType::H265, - VideoEncoderType::VP8, - VideoEncoderType::VP9, -]; - -for codec in preferred_order { - if available_encoders.contains(&codec) { - // ไฝฟ็”จๆญค็ผ–็ ๅ™จ - negotiated_codec = Some(codec); - break; - } -} -``` - -**ๆ•ˆๆžœ:** -- ไผ˜ๅ…ˆไฝฟ็”จ็กฌไปถๅŠ ้€Ÿ (H264/H265) -- ๅ›ž้€€ๅˆฐ่ฝฏไปถ็ผ–็  (VP8/VP9) ๅฆ‚ๆžœ็กฌไปถไธๅฏ็”จ -- ๅฎขๆˆท็ซฏ่‡ชๅŠจ้€‚้… (RustDesk ๆ”ฏๆŒๆ‰€ๆœ‰็ผ–็ ๅ™จ) - ---- - -## 11. P2P ็›ด่ฟžไธŽไธญ็ปงๅ›ž้€€ - -### 11.1 ่ฟžๆŽฅ็ญ–็•ฅ - -ๅฝ“ๆ”ถๅˆฐ PunchHole ่ฏทๆฑ‚ๆ—ถ,็ณป็ปŸไผšๅ…ˆๅฐ่ฏ• P2P ็›ด่ฟž,ๅคฑ่ดฅๅŽ่‡ชๅŠจๅ›ž้€€ๅˆฐไธญ็ปง: - -``` -PunchHole ่ฏทๆฑ‚ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๅฐ่ฏ• P2P ็›ด่ฟž โ”‚ โ—„โ”€โ”€ 3็ง’่ถ…ๆ—ถ -โ”‚ (TCP connect) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” - โ”‚ ๆˆๅŠŸ? โ”‚ - โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - Yes โ”‚ No - โ–ผ โ”‚ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ็›ด่ฟž โ”‚โ”‚โ”‚ ไธญ็ปงๅ›ž้€€ โ”‚ -โ”‚ ้€šไฟก โ”‚โ”‚โ”‚ (hbbr) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 11.2 punch.rs ๆจกๅ— - -```rust -/// P2P ็›ด่ฟž่ถ…ๆ—ถๆ—ถ้—ด -const DIRECT_CONNECT_TIMEOUT_MS: u64 = 3000; - -/// ็›ด่ฟž็ป“ๆžœ -pub enum PunchResult { - DirectConnection(TcpStream), - NeedRelay, -} - -/// ๅฐ่ฏ• P2P ็›ด่ฟž -pub async fn try_direct_connection(peer_addr: SocketAddr) -> PunchResult; - -/// Punch hole ๅค„็†ๅ™จ -pub struct PunchHoleHandler { - connection_manager: Arc, -} -``` - -### 11.3 ไธญ็ปง่ฎค่ฏ - -ๅฆ‚ๆžœไธญ็ปงๆœๅŠกๅ™จ้…็ฝฎไบ† `-k` ้€‰้กน๏ผŒ้œ€๏ฟฝ๏ฟฝๅœจ้…็ฝฎไธญ่ฎพ็ฝฎ `relay_key`๏ผš - -```rust -// ๅ‘้€ RequestRelay ๆ—ถๅŒ…ๅซ licence_key -let request_relay = make_request_relay(uuid, relay_key, socket_addr); -``` - ---- - -## 12. ๅธธ่ง้—ฎ้ข˜ - -### Q: ๆ— ๆณ•่ฟžๆŽฅๅˆฐๆธฒๆŸ“ๆœๅŠกๅ™จ? - -1. ๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅ -2. ๆฃ€ๆŸฅๆœๅŠกๅ™จๅœฐๅ€ -3. ๆฃ€ๆŸฅ้˜ฒ็ซๅข™ - -### Q: ๅฎขๆˆท็ซฏ่ฟžๆŽฅๅคฑ่ดฅ? - -1. ๆฃ€ๆŸฅ่ฎพๅค‡ ID -2. ๆฃ€ๆŸฅๅฏ†็  -3. ๆฃ€ๆŸฅ NAT ็ฉฟ้€ - -### Q: ่ง†้ข‘ๅปถ่ฟŸ้ซ˜? - -1. ไฝฟ็”จๆ›ด่ฟ‘็š„ไธญ็ปงๆœๅŠกๅ™จ -2. ๆฃ€ๆŸฅ็ฝ‘็ปœๅธฆๅฎฝ -3. ้™ไฝŽ่ง†้ข‘่ดจ้‡ - -### Q: ๅˆ‡ๆข็”ป่ดจๅŽ่ง†้ข‘้™ๆญข? - -1. ๆฃ€ๆŸฅๆ—ฅๅฟ—ๆ˜ฏๅฆๆœ‰ "re-subscribing" ไฟกๆฏ -2. ็กฎ่ฎค็ฎก้“้‡ๅฏๅŽๆˆๅŠŸ้‡ๆ–ฐ่ฎข้˜… -3. ๆฃ€ๆŸฅ broadcast channel ๆ˜ฏๅฆๆญฃๅธธๅ…ณ้—ญๅ’Œ้‡ๅปบ - -### Q: ๅฆ‚ไฝ•่‡ชๅปบๆœๅŠกๅ™จ? - -ๅ‚่€ƒ RustDesk Server ้ƒจ็ฝฒๆ–‡ๆกฃ: -- hbbs: Rendezvous ๆœๅŠกๅ™จ (้ป˜่ฎค็ซฏๅฃ 21116) -- hbbr: ไธญ็ปงๆœๅŠกๅ™จ (้ป˜่ฎค็ซฏๅฃ 21117) - -**Docker ๅฟซ้€Ÿ้ƒจ็ฝฒ:** -```bash -docker run -d --name hbbs \ - -p 21116:21116 \ - -p 21116:21116/udp \ - -p 21118:21118 \ - -v ./hbbs:/root \ - rustdesk/rustdesk-server hbbs - -docker run -d --name hbbr \ - -p 21117:21117 \ - -p 21119:21119 \ - -v ./hbbr:/root \ - rustdesk/rustdesk-server hbbr -``` - -### Q: ่พ“ๅ…ฅ่Š‚ๆตๆ˜ฏไป€ไนˆ? - -่พ“ๅ…ฅ่Š‚ๆต้™ๅˆถ้ผ ๆ ‡ไบ‹ไปถๅ‘้€้ข‘็އไธบ 60Hz (16ms),้˜ฒๆญข: -- HID ่ฎพๅค‡ๅ†™ๅ…ฅ้”™่ฏฏ (EAGAIN) -- CPU ไฝฟ็”จ็އ่ฟ‡้ซ˜ -- ็ฝ‘็ปœๅธฆๅฎฝๆตช่ดน - -่ฟ™ๅฏน็”จๆˆทไฝ“้ชŒๅ‡ ไนŽๆ— ๅฝฑๅ“,ๅ› ไธบ 60Hz ๅทฒ็ป่ถณๅคŸๆต็•…ใ€‚ - ---- - -## 13. ๅฎž็Žฐไบฎ็‚น - -### 13.1 ๅŒๅฏ†้’ฅ็ณป็ปŸ - -- **Curve25519**: ็”จไบŽ ECDH ๅฏ†้’ฅไบคๆขๅ’ŒๅŠ ๅฏ† -- **Ed25519**: ็”จไบŽ SignedId ็ญพๅๅ’Œ้ชŒ่ฏ -- ๆฏไธช่ฟžๆŽฅไฝฟ็”จไธดๆ—ถ Curve25519 ๅฏ†้’ฅๅฏน,ๆ้ซ˜ๅฎ‰ๅ…จๆ€ง - -### 13.2 ไธ‰็ง่ฟžๆŽฅๆจกๅผ - -1. **P2P ็›ด่ฟž**: ๆœ€ๅฟซ,ๅปถ่ฟŸๆœ€ไฝŽ,ไผ˜ๅ…ˆๅฐ่ฏ• -2. **ไธญ็ปง่ฟžๆŽฅ**: ้€š่ฟ‡ hbbr ไธญ็ปง,้€‚็”จไบŽ NAT ็Žฏๅขƒ -3. **ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž**: ๅŒไธ€ๅฑ€ๅŸŸ็ฝ‘ๅ†…็š„ไผ˜ๅŒ–่ทฏๅพ„ - -### 13.3 ้›ถๆ‹ท่ดๆžถๆž„ - -- ไฝฟ็”จ `bytes::Bytes` ๅผ•็”จ่ฎกๆ•ฐ,้ฟๅ…ๅ†…ๅญ˜ๆ‹ท่ด -- ่ง†้ข‘/้Ÿณ้ข‘ๆ•ฐๆฎๅœจ็ฎก้“ไธญๅ…ฑไบซ,ๅ•ๆฌก็ผ–็ ๅคšๆฌกไฝฟ็”จ -- Protobuf ๆถˆๆฏ็›ดๆŽฅไฝฟ็”จ `Bytes` ๅญ—ๆฎต (tokio_bytes = true) - -### 13.4 ๅฎน้”™ไธŽๆขๅค - -- **็ฎก้“้‡่ฎข้˜…**: ่ง†้ข‘/้Ÿณ้ข‘็ฎก้“้‡ๅฏๆ—ถ่‡ชๅŠจ้‡ๆ–ฐ่ฟžๆŽฅ -- **UUID ๆŒไน…ๅŒ–**: ้ฟๅ…้‡ๅฏๅŽ UUID_MISMATCH ้”™่ฏฏ -- **่ฟžๆŽฅ้‡่ฏ•**: Rendezvous ่ฟžๆŽฅๅคฑ่ดฅๆ—ถ่‡ชๅŠจ้‡่ฏ•,ๆŒ‡ๆ•ฐ้€€้ฟ - -### 13.5 ๆ€ง่ƒฝไผ˜ๅŒ– - -- ้ข„ๅˆ†้…็ผ“ๅ†ฒๅŒบ (128KB) -- ่พ“ๅ…ฅ่Š‚ๆต (60Hz ้ผ ๆ ‡) -- ๅ…ฑไบซ็ฎก้“ (broadcast channel) -- ็ผ–็ ๅ™จๅๅ•† (็กฌไปถไผ˜ๅ…ˆ) -- ้›ถๆ‹ท่ดไผ ่พ“ (Bytes) - ---- - -## 14. ไธŽๅŽŸ็‰ˆ RustDesk ็š„ๅทฎๅผ‚ - -| ็‰นๆ€ง | One-KVM RustDesk | ๅŽŸ็‰ˆ RustDesk Server | -|------|------------------|---------------------| -| ่ง’่‰ฒ | ่ขซๆŽง็ซฏ (ๅ—ๆŽง่ฎพๅค‡) | ๆœๅŠก็ซฏ (ไธญ็ปง/ๆณจๅ†Œ) | -| ่ง†้ข‘ๆบ | V4L2 ็กฌไปถๆ•่Žท | ๅฑๅน•ๆ•่Žท (ๆกŒ้ข) | -| HID | USB OTG Gadget | ๆ“ไฝœ็ณป็ปŸ API | -| ๅŠ ๅฏ† | NaCl (Curve25519) | ๅŒ | -| ๅ่ฎฎ | RustDesk Protocol | ๅŒ | -| P2P | ๆ”ฏๆŒ | ๆ”ฏๆŒ | -| ไธญ็ปง | ๆ”ฏๆŒ | ๆไพ›ไธญ็ปงๆœๅŠก | -| ๅ…ฌๅ…ฑๆœๅŠกๅ™จ | ไธๆไพ›,้œ€่‡ชๅปบ | N/A | -| ๅคš่ฟžๆŽฅ | ๆ”ฏๆŒ | N/A | -| ่พ“ๅ…ฅ่Š‚ๆต | 60Hz ้™ๆต | ๆ— ้™ๅˆถ | - -**ๅ…ณ้”ฎๅŒบๅˆซ**: One-KVM ๅฎž็Žฐ็š„ๆ˜ฏ RustDesk **่ขซๆŽง็ซฏ** (็ฑปไผผ RustDesk Desktop ็š„ๆœๅŠกๅ™จๆจกๅผ),่€Œไธๆ˜ฏ RustDesk Server (hbbs/hbbr)ใ€‚ - ---- - -## 15. ๅ‚่€ƒ่ต„ๆ–™ - -- [RustDesk ๅฎ˜ๆ–น็ฝ‘็ซ™](https://rustdesk.com) -- [RustDesk GitHub](https://github.com/rustdesk/rustdesk) -- [RustDesk Server GitHub](https://github.com/rustdesk/rustdesk-server) -- [RustDesk ๅ่ฎฎๆ–‡ๆกฃ](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common/protos) -- [NaCl ๅŠ ๅฏ†ๅบ“](https://nacl.cr.yp.to/) -- [Protobuf ๆ–‡ๆกฃ](https://protobuf.dev/) diff --git a/docs/modules/video.md b/docs/modules/video.md deleted file mode 100644 index 5efd394c..00000000 --- a/docs/modules/video.md +++ /dev/null @@ -1,1446 +0,0 @@ -# Video ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Video ๆจกๅ—่ดŸ่ดฃ่ง†้ข‘้‡‡้›†ใ€็ผ–็ ๅ’Œๆตไผ ่พ“๏ผŒๆ˜ฏ One-KVM ็š„ๆ ธๅฟƒๅŠŸ่ƒฝๆจกๅ—ใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- V4L2 ่ง†้ข‘่ฎพๅค‡้‡‡้›† -- ๅคšๆ ผๅผๅƒ็ด ่ฝฌๆข -- ็กฌไปถ/่ฝฏไปถ่ง†้ข‘็ผ–็  -- MJPEG ๅ’Œ WebRTC ๆตไผ ่พ“ -- ๅธงๅŽป้‡ๅ’Œ่ดจ้‡ๆŽงๅˆถ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/video/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ capture.rs # V4L2 ่ง†้ข‘้‡‡้›† -โ”œโ”€โ”€ device.rs # V4L2 ่ฎพๅค‡ๆžšไธพๅ’Œ่ƒฝๅŠ›ๆŸฅ่ฏข -โ”œโ”€โ”€ streamer.rs # ่ง†้ข‘ๆตๆœๅŠก (MJPEG) -โ”œโ”€โ”€ stream_manager.rs # ๆต็ฎก็†ๅ™จ (็ปŸไธ€็ฎก็† MJPEG/WebRTC) -โ”œโ”€โ”€ video_session.rs # ่ง†้ข‘ไผš่ฏ็ฎก็† (ๅคš็ผ–็ ๅ™จไผš่ฏ) -โ”œโ”€โ”€ shared_video_pipeline.rs # ๅ…ฑไบซ่ง†้ข‘็ผ–็ ็ฎก้“ (ๅคš็ผ–่งฃ็ ๅ™จ) -โ”œโ”€โ”€ h264_pipeline.rs # H264 ไธ“็”จ็ผ–็ ็ฎก้“ (WebRTC) -โ”œโ”€โ”€ format.rs # ๅƒ็ด ๆ ผๅผๅฎšไน‰ -โ”œโ”€โ”€ frame.rs # ่ง†้ข‘ๅธง็ป“ๆž„ (้›ถๆ‹ท่ด) -โ”œโ”€โ”€ convert.rs # ๆ ผๅผ่ฝฌๆข (libyuv SIMD) -โ”œโ”€โ”€ decoder/ # ่งฃ็ ๅ™จ -โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ””โ”€โ”€ mjpeg.rs # MJPEG ่งฃ็  (TurboJPEG/VAAPI) -โ””โ”€โ”€ encoder/ # ็ผ–็ ๅ™จ - โ”œโ”€โ”€ mod.rs - โ”œโ”€โ”€ traits.rs # Encoder trait + BitratePreset - โ”œโ”€โ”€ codec.rs # ็ผ–็ ๅ™จ็ฑปๅž‹ๅฎšไน‰ - โ”œโ”€โ”€ h264.rs # H264 ็ผ–็  - โ”œโ”€โ”€ h265.rs # H265 ็ผ–็  - โ”œโ”€โ”€ vp8.rs # VP8 ็ผ–็  - โ”œโ”€โ”€ vp9.rs # VP9 ็ผ–็  - โ”œโ”€โ”€ jpeg.rs # JPEG ็ผ–็  - โ””โ”€โ”€ registry.rs # ็ผ–็ ๅ™จๆณจๅ†Œ่กจ (็กฌไปถๆŽขๆต‹) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ๆ•ฐๆฎๆต - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Video Data Flow โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -V4L2 Device (/dev/video0) - โ”‚ - โ”‚ Raw frames (MJPEG/YUYV/NV12) - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ VideoCapturer โ”‚ โ—„โ”€โ”€โ”€ capture.rs -โ”‚ - open_device() โ”‚ -โ”‚ - read_frame() โ”‚ -โ”‚ - set_format() โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ VideoFrame - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Streamer โ”‚ โ—„โ”€โ”€โ”€ streamer.rs -โ”‚ - start() โ”‚ -โ”‚ - stop() โ”‚ -โ”‚ - get_info() โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MJPEG โ”‚ โ”‚ SharedVideoPipeline โ”‚ -โ”‚ Mode โ”‚ โ”‚ - Decode (MJPEGโ†’YUV) โ”‚ -โ”‚ โ”‚ โ”‚ - Convert (YUVโ†’target) โ”‚ -โ”‚ โ”‚ โ”‚ - Encode (H264/H265/VP8) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HTTP โ”‚ โ”‚ WebRTC โ”‚ -โ”‚ Stream โ”‚ โ”‚ RTP โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ็ป„ไปถๅ…ณ็ณป - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Component Relationships โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -VideoStreamManager (ๅ•ไธ€ๅ…ฅๅฃ) - โ”‚ - โ”œโ”€โ”€ mode: StreamMode (ๅฝ“ๅ‰ๆฟ€ๆดป็š„ๆจกๅผ) - โ”‚ - โ”œโ”€โ”€โ–บ MJPEG Mode - โ”‚ โ””โ”€โ”€โ–บ Streamer โ”€โ”€โ–บ MjpegStreamHandler - โ”‚ โ””โ”€โ”€โ–บ VideoCapturer - โ”‚ - โ””โ”€โ”€โ–บ WebRTC Mode - โ””โ”€โ”€โ–บ WebRtcStreamer - โ”œโ”€โ”€โ–บ VideoSessionManager (ๅคšไผš่ฏ็ฎก็†) - โ”‚ โ””โ”€โ”€โ–บ ๅคšไธช VideoSession (ๆฏไธชไผš่ฏ็‹ฌ็ซ‹็š„็ผ–่งฃ็ ๅ™จ) - โ””โ”€โ”€โ–บ SharedVideoPipeline (ๅ…ฑไบซ็ผ–็ ็ฎก้“) - โ”œโ”€โ”€โ–บ VideoCapturer - โ”œโ”€โ”€โ–บ MjpegDecoder (MJPEG โ†’ YUV420P/NV12) - โ”‚ โ”œโ”€โ”€ MjpegTurboDecoder (่ฝฏไปถ) - โ”‚ โ””โ”€โ”€ MjpegVaapiDecoder (็กฌไปถ) - โ”œโ”€โ”€โ–บ PixelConverter (ๆ ผๅผ่ฝฌๆข) - โ”‚ โ”œโ”€โ”€ Nv12Converter (YUYV/RGB โ†’ NV12) - โ”‚ โ””โ”€โ”€ Yuv420pConverter - โ””โ”€โ”€โ–บ Encoders[] (้€š่ฟ‡ EncoderRegistry ้€‰ๆ‹ฉ) - โ”œโ”€โ”€ H264Encoder (VAAPI/RKMPP/V4L2/x264) - โ”œโ”€โ”€ H265Encoder (VAAPI/RKMPP) - โ”œโ”€โ”€ VP8Encoder (VAAPI) - โ””โ”€โ”€ VP9Encoder (VAAPI) -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 VideoCapturer (capture.rs) - -ๅผ‚ๆญฅ V4L2 ่ง†้ข‘้‡‡้›†ๅ™จ๏ผŒไฝฟ็”จ memory-mapped ็ผ“ๅ†ฒๅŒบ่ฟ›่กŒ้ซ˜ๆ€ง่ƒฝ่ง†้ข‘้‡‡้›†ใ€‚ - -#### ไธป่ฆๆŽฅๅฃ - -```rust -pub struct VideoCapturer { - /// V4L2 ่ฎพๅค‡ๅฅๆŸ„ - device: Arc>, - /// ้‡‡้›†ไปปๅŠกๅฅๆŸ„ - capture_task: Option>, - /// ๅธงๅนฟๆ’ญ้€š้“ - frame_tx: broadcast::Sender, - /// ้‡‡้›†็Šถๆ€ - state: Arc>, - /// ็ปŸ่ฎกไฟกๆฏ - stats: Arc>, -} - -impl VideoCapturer { - /// ๅˆ›ๅปบ้‡‡้›†ๅ™จ (ไธ็ซ‹ๅณๆ‰“ๅผ€่ฎพๅค‡) - pub fn new() -> Arc; - - /// ๅฏๅŠจ้‡‡้›† - pub async fn start(&self, config: CaptureConfig) -> Result<()>; - - /// ๅœๆญข้‡‡้›† - pub async fn stop(&self) -> Result<()>; - - /// ่ฎข้˜…ๅธงๆต (ๅนฟๆ’ญๆจกๅผ) - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ่Žทๅ–ๅฝ“ๅ‰็Šถๆ€ - pub fn state(&self) -> CaptureState; - - /// ่Žทๅ–็ปŸ่ฎกไฟกๆฏ - pub fn stats(&self) -> CaptureStats; -} -``` - -#### ้‡‡้›†้…็ฝฎ - -```rust -pub struct CaptureConfig { - /// ่ฎพๅค‡่ทฏๅพ„ - pub device_path: PathBuf, // /dev/video0 - /// ๅˆ†่พจ็އ - pub resolution: Resolution, // 1920x1080 - /// ๅƒ็ด ๆ ผๅผ - pub format: PixelFormat, // MJPEG/YUYV/NV12 - /// ๅธง็އ (0 = ๆœ€ๅคง) - pub fps: u32, // 30 - /// ็ผ“ๅ†ฒๅŒบๆ•ฐ้‡ (้ป˜่ฎค 2) - pub buffer_count: u32, - /// ่ถ…ๆ—ถๆ—ถ้—ด - pub timeout: Duration, - /// JPEG ่ดจ้‡ (1-100) - pub jpeg_quality: u8, -} -``` - -#### ้‡‡้›†็Šถๆ€ - -```rust -#[derive(Clone, Copy)] -pub enum CaptureState { - Idle, // ๆœชๅˆๅง‹ๅŒ– - Starting, // ๆญฃๅœจๅฏๅŠจ - Running, // ๆญฃๅœจ้‡‡้›† - Stopping, // ๆญฃๅœจๅœๆญข - NoSignal, // ๆ— ไฟกๅท - DeviceLost, // ่ฎพๅค‡ไธขๅคฑ - Error, // ้”™่ฏฏ็Šถๆ€ -} -``` - -#### ไฝฟ็”จ็คบไพ‹ - -```rust -// ๅˆ›ๅปบ้‡‡้›†ๅ™จ -let capturer = VideoCapturer::new(); - -// ๅฏๅŠจ้‡‡้›† -let config = CaptureConfig { - device_path: PathBuf::from("/dev/video0"), - resolution: Resolution::HD1080, - format: PixelFormat::Mjpeg, - fps: 30, - buffer_count: 2, - timeout: Duration::from_secs(2), - jpeg_quality: 80, -}; -capturer.start(config).await?; - -// ่ฎข้˜…ๅธงๆต -let mut frame_rx = capturer.subscribe(); -while let Ok(frame) = frame_rx.recv().await { - // ๅค„็†ๅธง - process_frame(frame).await; -} -``` - -### 3.2 VideoDevice (device.rs) - -V4L2 ่ฎพๅค‡ๆžšไธพๅ’Œ่ƒฝๅŠ›ๆŸฅ่ฏขๅทฅๅ…ทใ€‚ - -```rust -/// ่ง†้ข‘่ฎพๅค‡ไฟกๆฏ -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VideoDeviceInfo { - /// ่ฎพๅค‡่ทฏๅพ„ (/dev/video0) - pub path: PathBuf, - /// ่ฎพๅค‡ๅ็งฐ - pub name: String, - /// ้ฉฑๅŠจๅ็งฐ - pub driver: String, - /// ๆ€ป็บฟไฟกๆฏ - pub bus_info: String, - /// ๅก็‰‡ๅ็งฐ - pub card: String, - /// ๆ”ฏๆŒ็š„ๅƒ็ด ๆ ผๅผๅˆ—่กจ - pub formats: Vec, - /// ่ฎพๅค‡่ƒฝๅŠ› - pub capabilities: DeviceCapabilities, - /// ๆ˜ฏๅฆไธบ้‡‡้›†ๅก (่‡ชๅŠจ่ฏ†ๅˆซ) - pub is_capture_card: bool, - /// ไผ˜ๅ…ˆ็บงๅˆ†ๆ•ฐ (็”จไบŽ่‡ชๅŠจ้€‰ๆ‹ฉ่ฎพๅค‡) - pub priority: u32, -} - -/// ๆžšไธพๆ‰€ๆœ‰่ง†้ข‘่ฎพๅค‡ -pub fn enumerate_devices() -> Result>; - -/// ่‡ชๅŠจ้€‰ๆ‹ฉๆœ€ไฝณ่ฎพๅค‡ (ไผ˜ๅ…ˆ็บงๆœ€้ซ˜็š„้‡‡้›†ๅก) -pub fn find_best_device() -> Result; -``` - -### 3.3 VideoFrame (frame.rs) - -่ง†้ข‘ๅธงๆ•ฐๆฎ็ป“ๆž„๏ผŒๆ”ฏๆŒ้›ถๆ‹ท่ดๅ’ŒๅธงๅŽป้‡ใ€‚ - -```rust -pub struct VideoFrame { - /// ๅธงๆ•ฐๆฎ (ๅผ•็”จ่ฎกๆ•ฐ) - data: Arc, - - /// xxHash64 ็ผ“ๅญ˜ (็”จไบŽๅŽป้‡) - hash: Arc>, - - /// ๅˆ†่พจ็އ - resolution: Resolution, - - /// ๅƒ็ด ๆ ผๅผ - format: PixelFormat, - - /// ่กŒๆญฅ้•ฟ - stride: u32, - - /// ๆ˜ฏๅฆๅ…ณ้”ฎๅธง - key_frame: bool, - - /// ๅธงๅบๅท - sequence: u64, - - /// ้‡‡้›†ๆ—ถ้—ดๆˆณ - capture_ts: Instant, - - /// ๆ˜ฏๅฆๆœ‰ไฟกๅท - online: bool, -} - -impl VideoFrame { - /// ๅˆ›ๅปบๆ–ฐๅธง - pub fn new(data: Bytes, resolution: Resolution, format: PixelFormat) -> Self; - - /// ่Žทๅ–ๅธงๆ•ฐๆฎ - pub fn data(&self) -> &[u8]; - - /// ่ฎก็ฎ—ๅธงๅ“ˆๅธŒ (ๆ‡’ๅŠ ่ฝฝ) - pub fn hash(&self) -> u64; - - /// ๆฃ€ๆŸฅๅธงๆ˜ฏๅฆ็›ธๅŒ (็”จไบŽๅŽป้‡) - pub fn is_same_as(&self, other: &Self) -> bool; - - /// ๅ…‹้š†ๅธง (้›ถๆ‹ท่ด) - pub fn clone_ref(&self) -> Self; -} -``` - -### 3.3 PixelFormat (format.rs) - -ๆ”ฏๆŒ็š„ๅƒ็ด ๆ ผๅผๅฎšไน‰ใ€‚ - -```rust -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PixelFormat { - // ๅŽ‹็ผฉๆ ผๅผ - Mjpeg, // Motion JPEG (ไผ˜ๅ…ˆ็บง: 100) - Jpeg, // Static JPEG (ไผ˜ๅ…ˆ็บง: 99) - - // YUV 4:2:2 ๆ‰“ๅŒ…ๆ ผๅผ - Yuyv, // YUYV/YUY2 (ไผ˜ๅ…ˆ็บง: 80) - Yvyu, // YVYU (ไผ˜ๅ…ˆ็บง: 64) - Uyvy, // UYVY (ไผ˜ๅ…ˆ็บง: 65) - - // YUV ๅŠๅนณ้ขๆ ผๅผ - Nv12, // NV12 (ไผ˜ๅ…ˆ็บง: 75) - Nv16, // NV16 (ไผ˜ๅ…ˆ็บง: 60) - Nv24, // NV24 (ไผ˜ๅ…ˆ็บง: 55) - - // YUV ๅนณ้ขๆ ผๅผ - Yuv420, // I420/YU12 (ไผ˜ๅ…ˆ็บง: 70) - Yvu420, // YV12 (ไผ˜ๅ…ˆ็บง: 63) - - // RGB ๆ ผๅผ - Rgb565, // RGB565 (ไผ˜ๅ…ˆ็บง: 40) - Rgb24, // RGB24 (ไผ˜ๅ…ˆ็บง: 50) - Bgr24, // BGR24 (ไผ˜ๅ…ˆ็บง: 49) - - // ็ฐๅบฆ - Grey, // 8-bit grayscale (ไผ˜ๅ…ˆ็บง: 10) -} - -impl PixelFormat { - /// ่Žทๅ–ๆ ผๅผไผ˜ๅ…ˆ็บง (่ถŠ้ซ˜่ถŠๅฅฝ) - pub fn priority(&self) -> u32; - - /// ่ฎก็ฎ—ๅธงๅคงๅฐ - pub fn frame_size(&self, width: u32, height: u32) -> usize; - - /// ่ฝฌๆขไธบ V4L2 FourCC - pub fn to_fourcc(&self) -> u32; - - /// ไปŽ V4L2 FourCC ่ฝฌๆข - pub fn from_fourcc(fourcc: u32) -> Option; - - /// ๆ˜ฏๅฆๅŽ‹็ผฉๆ ผๅผ - pub fn is_compressed(&self) -> bool; -} -``` - -### 3.4 PixelFormat (format.rs) - -ๆ”ฏๆŒ็š„ๅƒ็ด ๆ ผๅผๅฎšไน‰ (ไธŽๅฎž้™…ไปฃ็ ไธ€่‡ด)ใ€‚ - -```rust -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum PixelFormat { - // ๅŽ‹็ผฉๆ ผๅผ - Mjpeg, // Motion JPEG - Jpeg, // Static JPEG - - // YUV 4:2:2 ๆ‰“ๅŒ…ๆ ผๅผ - Yuyv, // YUYV/YUY2 - Yvyu, // YVYU - Uyvy, // UYVY - - // YUV ๅŠๅนณ้ขๆ ผๅผ - Nv12, // NV12 (Y + interleaved UV) - Nv16, // NV16 - Nv24, // NV24 - - // YUV ๅนณ้ขๆ ผๅผ - Yuv420, // I420/YU12 - Yvu420, // YV12 - - // RGB ๆ ผๅผ - Rgb565, // RGB565 - Rgb24, // RGB24 - Bgr24, // BGR24 - - // ็ฐๅบฆ - Grey, // 8-bit grayscale -} - -impl PixelFormat { - /// ่ฝฌๆขไธบ V4L2 FourCC - pub fn to_fourcc(&self) -> FourCC; - - /// ไปŽ V4L2 FourCC ่ฝฌๆข - pub fn from_fourcc(fourcc: FourCC) -> Option; - - /// ๆ˜ฏๅฆๅŽ‹็ผฉๆ ผๅผ - pub fn is_compressed(&self) -> bool; - - /// ่Žทๅ–ๆฏๅƒ็ด ๅญ—่Š‚ๆ•ฐ (ๆœชๅŽ‹็ผฉๆ ผๅผ) - pub fn bytes_per_pixel(&self) -> Option; - - /// ่ฎก็ฎ—ๅธงๅคงๅฐ - pub fn frame_size(&self, resolution: Resolution) -> Option; -} -``` - -### 3.5 SharedVideoPipeline (shared_video_pipeline.rs) - -ๅคšไผš่ฏๅ…ฑไบซ็š„่ง†้ข‘็ผ–็ ็ฎก้“ใ€‚ - -```rust -pub struct SharedVideoPipeline { - /// ่ง†้ข‘้‡‡้›†ๅ™จ - capturer: Arc>, - - /// MJPEG ่งฃ็ ๅ™จ - decoder: MjpegDecoder, - - /// YUV ่ฝฌๆขๅ™จ - converter: YuvConverter, - - /// ็ผ–็ ๅ™จๅฎžไพ‹ - encoders: HashMap>, - - /// ๆดป่ทƒไผš่ฏ - sessions: Arc>>, - - /// ้…็ฝฎ - config: PipelineConfig, -} - -impl SharedVideoPipeline { - /// ๅˆ›ๅปบ็ฎก้“ - pub async fn new(config: PipelineConfig) -> Result; - - /// ๅฏๅŠจ็ฎก้“ - pub async fn start(&self) -> Result<()>; - - /// ๅœๆญข็ฎก้“ - pub async fn stop(&self) -> Result<()>; - - /// ๆทปๅŠ ไผš่ฏ่ฎข้˜… - pub fn subscribe(&self, codec: VideoCodec) -> Receiver; - - /// ็งป้™คไผš่ฏ่ฎข้˜… - pub fn unsubscribe(&self, session_id: &str); - - /// ็ผ–็ ๅ•ๅธง (ๅคš็ผ–็ ๅ™จ) - async fn encode_frame(&self, frame: VideoFrame) -> Result<()>; -} -``` - -#### ็ผ–็ ๆต็จ‹ - -``` -Input: VideoFrame (MJPEG) - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MJPEG Decode โ”‚ turbojpeg / VAAPI -โ”‚ MJPEG โ†’ YUV420 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ YUV Convert โ”‚ libyuv (SIMD) -โ”‚ YUV420 โ†’ target โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ H264 โ”‚ โ”‚ H265 โ”‚ โ”‚ VP8 โ”‚ โ”‚ VP9 โ”‚ -โ”‚Encoderโ”‚ โ”‚Encoderโ”‚ โ”‚Encoderโ”‚ โ”‚Encoderโ”‚ -โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - EncodedFrame[] - (distribute to sessions) -``` - -### 3.5 SharedVideoPipeline (shared_video_pipeline.rs) - -้€š็”จๅ…ฑไบซ่ง†้ข‘็ผ–็ ็ฎก้“๏ผŒๆ”ฏๆŒ H264/H265/VP8/VP9 ๅคš็ง็ผ–็ ๅ™จใ€‚ - -```rust -pub struct SharedVideoPipeline { - /// ้…็ฝฎ - config: SharedVideoPipelineConfig, - /// ็ผ–็ ๅ™จๅฎžไพ‹ - encoder: Arc>>, - /// ๅƒ็ด ่ฝฌๆขๅ™จ - converter: Arc>>, - /// MJPEG ่งฃ็ ๅ™จ - mjpeg_decoder: Arc>>>, - /// ็ผ–็ ๅธงๅนฟๆ’ญ้€š้“ - encoded_tx: broadcast::Sender, - /// ็ปŸ่ฎกไฟกๆฏ - stats: Arc>, - /// ่ฟ่กŒ็Šถๆ€ - running: AtomicBool, -} - -impl SharedVideoPipeline { - /// ๅˆ›ๅปบ็ฎก้“ - pub async fn new(config: SharedVideoPipelineConfig) -> Result>; - - /// ๅฏๅŠจ็ฎก้“ - pub async fn start(&self, frame_rx: broadcast::Receiver) -> Result<()>; - - /// ๅœๆญข็ฎก้“ - pub async fn stop(&self) -> Result<()>; - - /// ่ฎข้˜…็ผ–็ ๅธง - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ่Žทๅ–็ปŸ่ฎกไฟกๆฏ - pub fn stats(&self) -> SharedVideoPipelineStats; - - /// ็ผ–็ ๅ•ๅธง (ๅ†…้ƒจๆ–นๆณ•) - async fn encode_frame(&self, frame: VideoFrame) -> Result; -} -``` - -#### ็ฎก้“้…็ฝฎ - -```rust -#[derive(Debug, Clone)] -pub struct SharedVideoPipelineConfig { - /// ่พ“ๅ…ฅๅˆ†่พจ็އ - pub resolution: Resolution, - /// ่พ“ๅ…ฅๅƒ็ด ๆ ผๅผ - pub input_format: PixelFormat, - /// ่พ“ๅ‡บ็ผ–็ ๅ™จ็ฑปๅž‹ - pub output_codec: VideoEncoderType, - /// ็ ็އ้ข„่ฎพ (ๆ›ฟไปฃๅŽŸๅง‹ bitrate_kbps) - pub bitrate_preset: BitratePreset, - /// ็›ฎๆ ‡ๅธง็އ - pub fps: u32, - /// ็ผ–็ ๅ™จๅŽ็ซฏ (None = ่‡ชๅŠจ้€‰ๆ‹ฉ) - pub encoder_backend: Option, -} - -impl SharedVideoPipelineConfig { - /// ๅˆ›ๅปบ H264 ้…็ฝฎ - pub fn h264(resolution: Resolution, preset: BitratePreset) -> Self; - - /// ๅˆ›ๅปบ H265 ้…็ฝฎ - pub fn h265(resolution: Resolution, preset: BitratePreset) -> Self; - - /// ๅˆ›ๅปบ VP8 ้…็ฝฎ - pub fn vp8(resolution: Resolution, preset: BitratePreset) -> Self; - - /// ๅˆ›ๅปบ VP9 ้…็ฝฎ - pub fn vp9(resolution: Resolution, preset: BitratePreset) -> Self; -} -``` - -### 3.6 VideoSessionManager (video_session.rs) - -็ฎก็†ๅคšไธช WebRTC ่ง†้ข‘ไผš่ฏ๏ผŒๆฏไธชไผš่ฏๅฏไฝฟ็”จไธๅŒ็š„็ผ–่งฃ็ ๅ™จใ€‚ - -```rust -pub struct VideoSessionManager { - /// ไผš่ฏๆ˜ ๅฐ„ (session_id -> VideoSession) - sessions: Arc>>, - /// ็ฎก้“ๆ˜ ๅฐ„ (codec -> SharedVideoPipeline) - pipelines: Arc>>>, - /// ้…็ฝฎ - config: VideoSessionManagerConfig, -} - -impl VideoSessionManager { - /// ๅˆ›ๅปบไผš่ฏ็ฎก็†ๅ™จ - pub fn new(config: VideoSessionManagerConfig) -> Arc; - - /// ๅˆ›ๅปบๆ–ฐไผš่ฏ - pub async fn create_session( - &self, - session_id: String, - codec: VideoEncoderType, - ) -> Result>; - - /// ๅ…ณ้—ญไผš่ฏ - pub async fn close_session(&self, session_id: &str) -> Result<()>; - - /// ่Žทๅ–ไผš่ฏไฟกๆฏ - pub fn get_session_info(&self, session_id: &str) -> Option; - - /// ๅˆ—ๅ‡บๆ‰€ๆœ‰ไผš่ฏ - pub fn list_sessions(&self) -> Vec; - - /// ่Žทๅ–ๆˆ–ๅˆ›ๅปบ็ผ–็ ็ฎก้“ - async fn get_or_create_pipeline( - &self, - codec: VideoEncoderType, - ) -> Result>; -} -``` - -### 3.7 Streamer (streamer.rs) - -้ซ˜ๅฑ‚่ง†้ข‘ๆตๆœๅŠก๏ผŒ็ฎก็†้‡‡้›†ๅ’Œๅˆ†ๅ‘ใ€‚ - -```rust -pub struct Streamer { - /// ้‡‡้›†ๅ™จ - capturer: Option>>, - - /// ้‡‡้›†ไปปๅŠกๅฅๆŸ„ - capture_task: Option>, - - /// ๅธงๅนฟๆ’ญ้€š้“ - frame_tx: broadcast::Sender, - - /// ็Šถๆ€ - state: Arc>, - - /// ้…็ฝฎ - config: StreamerConfig, - - /// ไบ‹ไปถๆ€ป็บฟ - events: Arc, -} - -impl Streamer { - /// ๅˆ›ๅปบๆตๆœๅŠก - pub fn new(events: Arc) -> Self; - - /// ๅฏๅŠจๆต - pub async fn start(&self, config: StreamerConfig) -> Result<()>; - - /// ๅœๆญขๆต - pub async fn stop(&self) -> Result<()>; - - /// ่ฎข้˜…ๅธง - pub fn subscribe(&self) -> broadcast::Receiver; - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> StreamerState; - - /// ่Žทๅ–ไฟกๆฏ - pub fn get_info(&self) -> StreamerInfo; - - /// ๅบ”็”จ้…็ฝฎ - pub async fn apply_config(&self, config: StreamerConfig) -> Result<()>; -} - -pub struct StreamerState { - pub status: StreamStatus, - pub device: Option, - pub resolution: Option, - pub format: Option, - pub fps: f32, - pub frame_count: u64, - pub error: Option, -} - -pub enum StreamStatus { - Idle, - Starting, - Streaming, - Stopping, - Error, -} -``` - -### 3.7 Streamer (streamer.rs) - -้ซ˜ๅฑ‚ MJPEG ่ง†้ข‘ๆตๆœๅŠก๏ผŒ้›†ๆˆ้‡‡้›†ใ€่ฎพๅค‡็ฎก็†ๅ’Œ็Šถๆ€็›‘ๆŽงใ€‚ - -```rust -pub struct Streamer { - /// ้…็ฝฎ - config: RwLock, - /// ่ง†้ข‘้‡‡้›†ๅ™จ - capturer: RwLock>>, - /// MJPEG ๆตๅค„็†ๅ™จ - mjpeg_handler: Arc, - /// ๅฝ“ๅ‰่ฎพๅค‡ไฟกๆฏ - current_device: RwLock>, - /// ๆต็Šถๆ€ - state: RwLock, - /// ไบ‹ไปถๆ€ป็บฟ (ๅฏ้€‰) - events: RwLock>>, -} - -impl Streamer { - /// ๅˆ›ๅปบๆตๆœๅŠก - pub fn new() -> Arc; - - /// ๅฏๅŠจๆต - pub async fn start(&self, config: StreamerConfig) -> Result<()>; - - /// ๅœๆญขๆต - pub async fn stop(&self) -> Result<()>; - - /// ่ฎพ็ฝฎไบ‹ไปถๆ€ป็บฟ - pub async fn set_event_bus(&self, events: Arc); - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> StreamerState; - - /// ่Žทๅ– MJPEG ๅค„็†ๅ™จ - pub fn mjpeg_handler(&self) -> Arc; - - /// ๅบ”็”จ้…็ฝฎ (็ƒญๆ›ดๆ–ฐ) - pub async fn apply_config(&self, config: StreamerConfig) -> Result<()>; -} - -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum StreamerState { - Uninitialized, // ๆœชๅˆๅง‹ๅŒ– - Ready, // ๅฐฑ็ปชไฝ†ๆœชๆตๅผไผ ่พ“ - Streaming, // ๆญฃๅœจๆตๅผไผ ่พ“ - NoSignal, // ๆ— ่ง†้ข‘ไฟกๅท - Error, // ้”™่ฏฏ - DeviceLost, // ่ฎพๅค‡ไธขๅคฑ - Recovering, // ่ฎพๅค‡ๆขๅคไธญ -} -``` - -### 3.8 VideoStreamManager (stream_manager.rs) - -็ปŸไธ€่ง†้ข‘ๆต็ฎก็†ๅ™จ๏ผŒไฝœไธบๅ”ฏไธ€ๅ…ฅๅฃๅ่ฐƒ MJPEG ๅ’Œ WebRTC ไธค็งๆตๆจกๅผใ€‚ - -```rust -pub struct VideoStreamManager { - /// ๅฝ“ๅ‰ๆตๆจกๅผ - mode: RwLock, - /// MJPEG ๆตๆœๅŠก - streamer: Arc, - /// WebRTC ๆตๆœๅŠก - webrtc_streamer: Arc, - /// ไบ‹ไปถๆ€ป็บฟ - events: RwLock>>, - /// ้…็ฝฎๅญ˜ๅ‚จ - config_store: RwLock>, - /// ๆจกๅผๅˆ‡ๆข้” - switching: AtomicBool, -} - -impl VideoStreamManager { - /// ๅˆ›ๅปบ็ฎก็†ๅ™จ (ๆŒ‡ๅฎš WebRtcStreamer) - pub fn with_webrtc_streamer( - streamer: Arc, - webrtc_streamer: Arc, - ) -> Arc; - - /// ๅฏๅŠจๆต (ๅฏๅŠจๅฝ“ๅ‰ๆจกๅผ) - pub async fn start(&self) -> Result<()>; - - /// ๅœๆญขๆต - pub async fn stop(&self) -> Result<()>; - - /// ๅˆ‡ๆขๆตๆจกๅผ (MJPEG โ†” WebRTC) - pub async fn set_mode(&self, mode: StreamMode) -> Result<()>; - - /// ่Žทๅ–ๅฝ“ๅ‰ๆจกๅผ - pub fn mode(&self) -> StreamMode; - - /// ่Žทๅ– Streamer (MJPEG) - pub fn streamer(&self) -> Arc; - - /// ่Žทๅ– WebRtcStreamer - pub fn webrtc_streamer(&self) -> Arc; - - /// ่ฎพ็ฝฎไบ‹ไปถๆ€ป็บฟ - pub async fn set_event_bus(&self, events: Arc); - - /// ่ฎพ็ฝฎ้…็ฝฎๅญ˜ๅ‚จ - pub async fn set_config_store(&self, config_store: ConfigStore); -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StreamMode { - Mjpeg, // MJPEG over HTTP - Webrtc, // H264/H265/VP8/VP9 over WebRTC -} -``` - ---- - -## 4. ็ผ–็ ๅ™จ็ณป็ปŸ - -### 4.1 BitratePreset (encoder/traits.rs) - -็ ็އ้ข„่ฎพ็ฎ€ๅŒ–้…็ฝฎ๏ผŒๆไพ›ไธ‰ไธชๅธธ็”จๆกฃไฝๅ’Œ่‡ชๅฎšไน‰้€‰้กนใ€‚ - -```rust -#[typeshare] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum BitratePreset { - /// ้€Ÿๅบฆไผ˜ๅ…ˆ: 1 Mbps, ๆœ€ไฝŽๅปถ่ฟŸ, ๆ›ดๅฐ็š„ GOP - /// ้€‚็”จไบŽ: ๆ…ข้€Ÿ็ฝ‘็ปœ, ่ฟœ็จ‹็ฎก็†, ไฝŽๅธฆๅฎฝๅœบๆ™ฏ - Speed, - - /// ๅนณ่กก: 4 Mbps, ่ดจ้‡/ๅปถ่ฟŸๅ‡่กก (ๆŽจ่้ป˜่ฎค) - /// ้€‚็”จไบŽ: ๅธธ่ง„ไฝฟ็”จ - Balanced, - - /// ่ดจ้‡ไผ˜ๅ…ˆ: 8 Mbps, ๆœ€ไฝณ่ง†่ง‰่ดจ้‡ - /// ้€‚็”จไบŽ: ๆœฌๅœฐ็ฝ‘็ปœ, ้ซ˜ๅธฆๅฎฝๅœบๆ™ฏ, ่ฏฆ็ป†ๅทฅไฝœ - Quality, - - /// ่‡ชๅฎšไน‰็ ็އ (kbps, ้ซ˜็บง็”จๆˆท) - Custom(u32), -} - -impl BitratePreset { - /// ่Žทๅ–็ ็އๅ€ผ (kbps) - pub fn bitrate_kbps(&self) -> u32; - - /// ่Žทๅ–ๆŽจ่ GOP ๅคงๅฐ (ๅŸบไบŽๅธง็އ) - pub fn gop_size(&self, fps: u32) -> u32; - - /// ่Žทๅ–่ดจ้‡็บงๅˆซ ("low" | "medium" | "high") - pub fn quality_level(&self) -> &'static str; - - /// ไปŽ kbps ๅ€ผๅˆ›ๅปบ (่‡ชๅŠจๆ˜ ๅฐ„ๅˆฐๆœ€่ฟ‘้ข„่ฎพๆˆ– Custom) - pub fn from_kbps(kbps: u32) -> Self; -} -``` - -### 4.2 VideoEncoder Trait (encoder/traits.rs) - -ๆ‰€ๆœ‰็ผ–็ ๅ™จ็š„้€š็”จๆŽฅๅฃ (hwcodec ็ผ–็ ๅ™จ็š„ๅฐ่ฃ…)ใ€‚ - -```rust -pub trait VideoEncoder: Send + Sync { - /// ็ผ–็ ไธ€ๅธง (่พ“ๅ…ฅ NV12, ่พ“ๅ‡บๅŽ‹็ผฉๆ•ฐๆฎ) - fn encode(&mut self, yuv: &[u8], ms: i64) -> Result; - - /// ่Žทๅ–็ผ–็ ๅ™จ็ฑปๅž‹ - fn encoder_type(&self) -> VideoEncoderType; - - /// ่ฎพ็ฝฎ็ ็އ (kbps) - fn set_bitrate(&mut self, bitrate_kbps: u32) -> Result<()>; - - /// ่ฏทๆฑ‚ๅ…ณ้”ฎๅธง - fn request_keyframe(&mut self); - - /// ่Žทๅ–็ผ–็ ๅ™จไฟกๆฏ - fn info(&self) -> EncoderInfo; -} - -/// ็ผ–็ ๅŽ็š„่ง†้ข‘ๅธง -#[derive(Debug, Clone)] -pub struct EncodedVideoFrame { - /// ็ผ–็ ๆ•ฐๆฎ (Bytes ๅผ•็”จ่ฎกๆ•ฐ๏ผŒ้›ถๆ‹ท่ด) - pub data: Bytes, - /// ๅ‘ˆ็Žฐๆ—ถ้—ดๆˆณ (ๆฏซ็ง’) - pub pts_ms: i64, - /// ๆ˜ฏๅฆๅ…ณ้”ฎๅธง - pub is_keyframe: bool, - /// ๅธงๅบๅท - pub sequence: u64, - /// ๅธงๆ—ถ้•ฟ - pub duration: Duration, - /// ็ผ–็ ็ฑปๅž‹ - pub codec: VideoEncoderType, -} -``` - -### 4.3 VideoEncoderType & EncoderBackend (encoder/registry.rs) - -็ผ–็ ๅ™จ็ฑปๅž‹ๅ’Œ็กฌไปถๅŽ็ซฏๅฎšไน‰ใ€‚ - -```rust -/// ่ง†้ข‘็ผ–็ ๅ™จ็ฑปๅž‹ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum VideoEncoderType { - H264, // H.264/AVC - H265, // H.265/HEVC - VP8, // VP8 - VP9, // VP9 -} - -impl VideoEncoderType { - /// ๆ˜ฏๅฆไป…ๆ”ฏๆŒ็กฌไปถ็ผ–็  (ๆ— ่ฝฏไปถๅ›ž้€€) - pub fn hardware_only(&self) -> bool { - match self { - VideoEncoderType::H264 => false, // x264 ่ฝฏไปถๅ›ž้€€ - VideoEncoderType::H265 => true, // ไป…็กฌไปถ - VideoEncoderType::VP8 => true, // ไป…็กฌไปถ - VideoEncoderType::VP9 => true, // ไป…็กฌไปถ - } - } -} - -/// ็ผ–็ ๅ™จ็กฌไปถๅŽ็ซฏ -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum EncoderBackend { - Vaapi, // Intel/AMD VAAPI (Linux) - Nvenc, // NVIDIA NVENC - Qsv, // Intel Quick Sync - Amf, // AMD AMF - Rkmpp, // Rockchip MPP - V4l2M2m, // V4L2 Memory-to-Memory - Software, // x264/x265/libvpx (่ฝฏไปถ) -} -``` - -### 4.4 EncoderRegistry (encoder/registry.rs) - -ๅ…จๅฑ€็ผ–็ ๅ™จๆณจๅ†Œ่กจ๏ผŒ่‡ชๅŠจๆฃ€ๆต‹็กฌไปถๅนถ้€‰ๆ‹ฉๆœ€ไฝณ็ผ–็ ๅ™จใ€‚ - -```rust -/// ็ผ–็ ๅ™จๆณจๅ†Œ่กจ (ๅ…จๅฑ€ๅ•ไพ‹) -pub struct EncoderRegistry { - /// ๅฏ็”จ็ผ–็ ๅ™จๆ˜ ๅฐ„ - available_encoders: HashMap>, -} - -impl EncoderRegistry { - /// ่Žทๅ–ๅ…จๅฑ€ๅฎžไพ‹ - pub fn global() -> &'static EncoderRegistry; - - /// ๅˆ—ๅ‡บๅฏ็”จ็ผ–็ ๅ™จ - pub fn list_available(&self, codec: VideoEncoderType) -> &[EncoderBackend]; - - /// ๆฃ€ๆŸฅ็ผ–็ ๅ™จๆ˜ฏๅฆๅฏ็”จ - pub fn is_available(&self, codec: VideoEncoderType, backend: EncoderBackend) -> bool; - - /// ่Žทๅ–ๆœ€ไฝณ็ผ–็ ๅ™จๅŽ็ซฏ (่‡ชๅŠจ้€‰ๆ‹ฉ) - pub fn get_best_backend(&self, codec: VideoEncoderType) -> Option; - - /// ๅˆ›ๅปบ็ผ–็ ๅ™จๅฎžไพ‹ - pub fn create_encoder( - &self, - codec: VideoEncoderType, - config: EncoderConfig, - backend: Option, - ) -> Result>; -} -``` - -### 4.5 ็ผ–็ ๅ™จไผ˜ๅ…ˆ็บง - -ๅฎž้™…็š„็กฌไปถๆฃ€ๆต‹้กบๅบ (ๅŸบไบŽ hwcodec ๅบ“)๏ผš - -``` -H264 ็ผ–็ ๅ™จ้€‰ๆ‹ฉ้กบๅบ: -1. VAAPI (Intel/AMD GPU - ไผ˜ๅ…ˆ) -2. Rkmpp (Rockchip ๅนณๅฐ) -3. V4L2 M2M (้€š็”จ Linux) -4. x264 (่ฝฏไปถๅ›ž้€€) - -H265 ็ผ–็ ๅ™จ้€‰ๆ‹ฉ้กบๅบ: -1. VAAPI -2. Rkmpp -(ๆ— ่ฝฏไปถๅ›ž้€€) - -VP8/VP9 ็ผ–็ ๅ™จ: -1. VAAPI only -(ๆ— ่ฝฏไปถๅ›ž้€€) -``` - ---- - -## 5. ๆ ผๅผ่ฝฌๆขไธŽ่งฃ็  - -### 5.1 MJPEG ่งฃ็ ๅ™จ (decoder/mjpeg.rs) - -ๆ”ฏๆŒ็กฌไปถๅ’Œ่ฝฏไปถไธค็ง MJPEG ่งฃ็ ๆ–นๅผใ€‚ - -```rust -/// MJPEG ่งฃ็ ๅ™จ trait -pub trait MjpegDecoder: Send + Sync { - /// ่งฃ็  MJPEG ๅˆฐ YUV420P - fn decode(&mut self, jpeg_data: &[u8]) -> Result; - - /// ่Žทๅ–่งฃ็ ๅ™จ็ฑปๅž‹ - fn decoder_type(&self) -> &str; -} - -/// MJPEG TurboJPEG ่ฝฏไปถ่งฃ็ ๅ™จ -pub struct MjpegTurboDecoder { - decompressor: Decompressor, - output_buffer: Vec, -} - -/// MJPEG VAAPI ็กฌไปถ่งฃ็ ๅ™จ (่พ“ๅ‡บ NV12) -pub struct MjpegVaapiDecoder { - decoder: VaapiDecoder, - config: MjpegVaapiDecoderConfig, -} - -impl MjpegVaapiDecoder { - /// ๅˆ›ๅปบ VAAPI ่งฃ็ ๅ™จ - pub fn new(config: MjpegVaapiDecoderConfig) -> Result; - - /// ่งฃ็  MJPEG ๅˆฐ NV12 (็กฌไปถๅŠ ้€Ÿ) - pub fn decode_to_nv12(&mut self, jpeg_data: &[u8]) -> Result>; -} -``` - -### 5.2 ๅƒ็ด ่ฝฌๆขๅ™จ (convert.rs) - -ไฝฟ็”จ libyuv SIMD ๅŠ ้€Ÿ็š„ๆ ผๅผ่ฝฌๆขใ€‚ - -```rust -/// NV12 ่ฝฌๆขๅ™จ (YUYV/RGB โ†’ NV12) -pub struct Nv12Converter { - input_format: PixelFormat, - resolution: Resolution, - nv12_buffer: Nv12Buffer, -} - -impl Nv12Converter { - /// ๅˆ›ๅปบ่ฝฌๆขๅ™จ - pub fn new(input_format: PixelFormat, resolution: Resolution) -> Self; - - /// ่ฝฌๆขๅˆฐ NV12 - pub fn convert(&mut self, input: &[u8]) -> Result<&[u8]>; -} - -/// YUV420P ็ผ“ๅ†ฒๅŒบ -pub struct Yuv420pBuffer { - data: Vec, - width: u32, - height: u32, - y_offset: usize, - u_offset: usize, - v_offset: usize, -} - -impl Yuv420pBuffer { - /// ่Žทๅ– Y ๅนณ้ข - pub fn y_plane(&self) -> &[u8]; - - /// ่Žทๅ– U ๅนณ้ข - pub fn u_plane(&self) -> &[u8]; - - /// ่Žทๅ– V ๅนณ้ข - pub fn v_plane(&self) -> &[u8]; -} - -/// ๅƒ็ด ่ฝฌๆขๅ™จ (้€š็”จๆŽฅๅฃ) -pub trait PixelConverter: Send + Sync { - /// YUYV โ†’ YUV420P - fn yuyv_to_yuv420p(src: &[u8], width: u32, height: u32) -> Yuv420pBuffer; - - /// NV12 โ†’ YUV420P - fn nv12_to_yuv420p(src: &[u8], width: u32, height: u32) -> Yuv420pBuffer; - - /// RGB24 โ†’ YUV420P - fn rgb24_to_yuv420p(src: &[u8], width: u32, height: u32) -> Yuv420pBuffer; -} -``` - ---- - -## 6. ้…็ฝฎ่ฏดๆ˜Ž - -### 6.1 ่ง†้ข‘้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct VideoConfig { - /// ่ฎพๅค‡่ทฏๅพ„ (None = ่‡ชๅŠจๆฃ€ๆต‹) - pub device: Option, - - /// ๅƒ็ด ๆ ผๅผ (None = ่‡ชๅŠจ้€‰ๆ‹ฉ: MJPEG > YUYV > NV12) - pub format: Option, - - /// ๅฎฝๅบฆ - pub width: u32, - - /// ้ซ˜ๅบฆ - pub height: u32, - - /// ๅธง็އ - pub fps: u32, - - /// JPEG ่ดจ้‡ (1-100, ไป… MJPEG) - pub quality: u32, -} - -impl Default for VideoConfig { - fn default() -> Self { - Self { - device: None, - format: None, - width: 1920, - height: 1080, - fps: 30, - quality: 80, - } - } -} -``` - -### 6.2 WebRTC ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct WebRtcConfig { - /// ็ ็އ้ข„่ฎพ - pub bitrate_preset: BitratePreset, - - /// ้ฆ–้€‰็ผ–็ ๅ™จ (H264/H265/VP8/VP9) - pub preferred_codec: String, - - /// STUN ๆœๅŠกๅ™จ - pub stun_server: Option, - - /// TURN ๆœๅŠกๅ™จ - pub turn_server: Option, - - /// TURN ็”จๆˆทๅ - pub turn_username: Option, - - /// TURN ๅฏ†็  - pub turn_password: Option, -} - -impl Default for WebRtcConfig { - fn default() -> Self { - Self { - bitrate_preset: BitratePreset::Balanced, - preferred_codec: "H264".to_string(), - stun_server: Some("stun:stun.l.google.com:19302".to_string()), - turn_server: None, - turn_username: None, - turn_password: None, - } - } -} -``` - ---- - -## 7. API ็ซฏ็‚น - -### 7.1 ่ง†้ข‘ๆตๆŽงๅˆถ (็”จๆˆทๆƒ้™) - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/stream/status` | GET | ่Žทๅ–ๆต็Šถๆ€ | -| `/stream/start` | POST | ๅฏๅŠจๆต | -| `/stream/stop` | POST | ๅœๆญขๆต | -| `/stream/mode` | GET | ่Žทๅ–ๆตๆจกๅผ (MJPEG/WebRTC) | -| `/stream/mode` | POST | ่ฎพ็ฝฎๆตๆจกๅผ | -| `/stream/bitrate` | POST | ่ฎพ็ฝฎ็ ็އ (WebRTC) | -| `/stream/codecs` | GET | ๅˆ—ๅ‡บๅฏ็”จ็ผ–็ ๅ™จ | - -### 7.2 WebRTC ็ซฏ็‚น (็”จๆˆทๆƒ้™) - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/webrtc/session` | POST | ๅˆ›ๅปบ WebRTC ไผš่ฏ | -| `/webrtc/offer` | POST | ๅ‘้€ SDP offer | -| `/webrtc/ice` | POST | ๅ‘้€ ICE candidate | -| `/webrtc/ice-servers` | GET | ่Žทๅ– STUN/TURN ้…็ฝฎ | -| `/webrtc/status` | GET | ่Žทๅ– WebRTC ็Šถๆ€ | -| `/webrtc/close` | POST | ๅ…ณ้—ญไผš่ฏ | - -### 7.3 ่ฎพๅค‡็ฎก็† (็”จๆˆทๆƒ้™) - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/devices` | GET | ๅˆ—ๅ‡บๆ‰€ๆœ‰่ง†้ข‘่ฎพๅค‡ | - -### 7.4 ้…็ฝฎ็ฎก็† (็ฎก็†ๅ‘˜ๆƒ้™) - -| ็ซฏ็‚น | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/config/video` | GET | ่Žทๅ–่ง†้ข‘้…็ฝฎ | -| `/config/video` | PATCH | ๆ›ดๆ–ฐ่ง†้ข‘้…็ฝฎ | -| `/config/stream` | GET | ่Žทๅ–ๆต้…็ฝฎ | -| `/config/stream` | PATCH | ๆ›ดๆ–ฐๆต้…็ฝฎ | - -### 7.5 ๅ“ๅบ”ๆ ผๅผ - -```json -// GET /stream/status -{ - "state": "streaming", - "device": "/dev/video0", - "resolution": { "width": 1920, "height": 1080 }, - "format": "MJPEG", - "fps": 30.0, - "mode": "mjpeg" -} - -// GET /devices -{ - "devices": [ - { - "path": "/dev/video0", - "name": "USB Capture HDMI", - "driver": "uvcvideo", - "bus_info": "usb-0000:00:14.0-1", - "formats": ["MJPEG", "YUYV"], - "is_capture_card": true, - "priority": 100 - } - ] -} - -// GET /stream/codecs -{ - "codecs": [ - { - "codec": "H264", - "backends": ["VAAPI", "x264"] - }, - { - "codec": "H265", - "backends": ["VAAPI"] - } - ] -} -``` - ---- - -## 8. ไบ‹ไปถ็ณป็ปŸ - -่ง†้ข‘ๆจกๅ—้€š่ฟ‡ EventBus ๅ‘ๅธƒ็š„ๅฎžๆ—ถไบ‹ไปถ (้€š่ฟ‡ WebSocket `/ws` ๆŽจ้€ๅˆฐๅ‰็ซฏ)๏ผš - -```rust -pub enum SystemEvent { - /// ๆต็Šถๆ€ๅ˜ๅŒ– - StreamStateChanged { - state: String, // "uninitialized" | "ready" | "streaming" | "no_signal" | "error" | "device_lost" | "recovering" - device: Option, - resolution: Option, - fps: Option, - mode: String, // "mjpeg" | "webrtc" - }, - - /// ่ง†้ข‘่ฎพๅค‡ๆ’ๆ‹”ไบ‹ไปถ - VideoDeviceChanged { - added: Vec, - removed: Vec, - }, - - /// WebRTC ไผš่ฏ็Šถๆ€ๅ˜ๅŒ– - WebRtcSessionChanged { - session_id: String, - state: String, // "created" | "active" | "paused" | "closing" | "closed" - codec: String, - }, - - /// ็ผ–็ ๅ™จๅ˜ๅŒ– (็กฌไปถ/่ฝฏไปถๅˆ‡ๆข) - EncoderChanged { - codec: String, - backend: String, // "VAAPI" | "RKMPP" | "x264" | ... - hardware: bool, - }, -} -``` - -ๅ‰็ซฏ่ฎข้˜…็คบไพ‹๏ผš - -```typescript -const ws = new WebSocket('ws://localhost:8080/ws'); -ws.onmessage = (event) => { - const systemEvent = JSON.parse(event.data); - if (systemEvent.type === 'StreamStateChanged') { - console.log('Stream state:', systemEvent.state); - } -}; -``` - ---- - -## 9. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum VideoError { - #[error("Device not found: {0}")] - DeviceNotFound(String), - - #[error("Device busy: {0}")] - DeviceBusy(String), - - #[error("Format not supported: {0:?}")] - FormatNotSupported(PixelFormat), - - #[error("Resolution not supported: {0}x{1}")] - ResolutionNotSupported(u32, u32), - - #[error("Capture error: {0}")] - CaptureError(String), - - #[error("Encoder error: {0}")] - EncoderError(String), - - #[error("No signal")] - NoSignal, - - #[error("Device lost")] - DeviceLost, -} -``` - ---- - -## 10. ๆ€ง่ƒฝไผ˜ๅŒ– - -### 10.1 ้›ถๆ‹ท่ดๆžถๆž„ - -- `Arc` ๅ…ฑไบซๅธงๆ•ฐๆฎ๏ผŒ้ฟๅ…ๅ†…ๅญ˜ๆ‹ท่ด -- ๅผ•็”จ่ฎกๆ•ฐๅคšๆ’ญ๏ผŒๅ•ๆฌก้‡‡้›†ๅคšไธชๆถˆ่ดน่€… -- `broadcast::Sender` ้ซ˜ๆ•ˆๅˆ†ๅ‘ๅธงๅˆฐๅคšไธช่ฎข้˜…่€… - -### 10.2 ๅธงๅŽป้‡ (Frame Deduplication) - -- xxHash64 ๅฟซ้€Ÿๅ“ˆๅธŒ่ฎก็ฎ— (ๆ‡’ๅŠ ่ฝฝ) -- ็›ธๅŒๅธง่ทณ่ฟ‡็ผ–็ ๏ผŒ้™ไฝŽ CPU ไฝฟ็”จ -- ้€‚็”จไบŽ้™ๆ€็”ป้ขๅœบๆ™ฏ - -### 10.3 ็กฌไปถๅŠ ้€Ÿไผ˜ๅ…ˆ - -็ผ–็ ๅ™จ่‡ชๅŠจ้€‰ๆ‹ฉไผ˜ๅ…ˆ็บง๏ผš -1. **VAAPI** (Intel/AMD GPU) - ๆœ€ไผ˜ๅ…ˆ -2. **Rkmpp** (Rockchip ๅนณๅฐ) -3. **V4L2 M2M** (้€š็”จ Linux) -4. **Software** (x264) - ไป… H264 ๆœ‰่ฝฏไปถๅ›ž้€€ - -่งฃ็ ๅ™จไผ˜ๅ…ˆ็บง๏ผš -1. **VAAPI** (็กฌไปถ MJPEG ่งฃ็  โ†’ NV12) -2. **TurboJPEG** (่ฝฏไปถ MJPEG ่งฃ็  โ†’ YUV420P) - -### 10.4 SIMD ๅŠ ้€Ÿ - -- libyuv ๅบ“ๆไพ› NEON/SSE ไผ˜ๅŒ–็š„ๅƒ็ด ่ฝฌๆข -- ่‡ชๅŠจๆฃ€ๆต‹ CPU ๆŒ‡ไปค้›†ๅนถไฝฟ็”จๆœ€ๅฟซ่ทฏๅพ„ -- YUYV โ†’ NV12 ่ฝฌๆขๆ€ง่ƒฝๆๅ‡ 3-4 ๅ€ - -### 10.5 ไฝŽๅปถ่ฟŸไผ˜ๅŒ– - -- ็ผ“ๅ†ฒๅŒบๆ•ฐ้‡ๅ‡ๅฐ‘่‡ณ 2 (้™ไฝŽ้‡‡้›†ๅปถ่ฟŸ) -- WebRTC ๆจกๅผไธ‹็›ดๆŽฅ RTP ๅฐ่ฃ…๏ผŒๆ— ้ขๅค–็ผ“ๅ†ฒ -- GOP ๅคงๅฐๅฏ่ฐƒ (Speed ้ข„่ฎพ: 0.5s, Balanced: 1s, Quality: 2s) - ---- - -## 11. ๅธธ่ง้—ฎ้ข˜ - -### Q: ๅฆ‚ไฝ•ๆทปๅŠ ๆ–ฐ็š„่ง†้ข‘ๆ ผๅผๆ”ฏๆŒ? - -1. ๅœจ `format.rs` ๆทปๅŠ  `PixelFormat` ๆžšไธพๅ€ผ -2. ๅฎž็Žฐ `to_fourcc()` ๅ’Œ `from_fourcc()` ๆ˜ ๅฐ„ -3. ๅœจ `convert.rs` ๆทปๅŠ ่ฝฌๆขๅ‡ฝๆ•ฐ (ๅฆ‚ๆžœ้œ€่ฆ่ฝฌไธบ NV12/YUV420P) -4. ๆ›ดๆ–ฐ `Nv12Converter` ๆˆ– `PixelConverter` - -### Q: ๅฆ‚ไฝ•ๆทปๅŠ ๆ–ฐ็š„็ผ–็ ๅ™จๅŽ็ซฏ? - -1. ๅœจ `encoder/registry.rs` ๆทปๅŠ  `EncoderBackend` ๆžšไธพๅ€ผ -2. ๅœจๅฏนๅบ”็ผ–็ ๅ™จ (ๅฆ‚ `h264.rs`) ไธญๅฎž็Žฐๆ–ฐๅŽ็ซฏ -3. ๆ›ดๆ–ฐ `EncoderRegistry::create_encoder()` ็š„ๅŽ็ซฏ้€‰ๆ‹ฉ้€ป่พ‘ -4. ๆทปๅŠ ็กฌไปถๆŽขๆต‹ไปฃ็  - -### Q: ๅธง็އไธ็จณๅฎšๆˆ–ไธขๅธงๆ€ŽไนˆๅŠž? - -**่ฏŠๆ–ญๆญฅ้ชค๏ผš** -1. ๆฃ€ๆŸฅ `/stream/status` API๏ผŒๆŸฅ็œ‹ๅฎž้™… FPS -2. ๆฃ€ๆŸฅ USB ๅธฆๅฎฝๆ˜ฏๅฆๅ……่ถณ (ไฝฟ็”จ `lsusb -t`) -3. ๆฃ€ๆŸฅ CPU ไฝฟ็”จ็އ๏ผŒ็กฎ่ฎค็ผ–็ ๅ™จ่ดŸ่ฝฝ - -**่งฃๅ†ณๆ–นๆกˆ๏ผš** -- **้™ไฝŽๅˆ†่พจ็އ**: 1080p โ†’ 720p -- **ไฝฟ็”จ MJPEG ๆ ผๅผ**: ๅ‡ๅฐ‘ไธปๆœบไพง่งฃ็ ่ดŸๆ‹… -- **ๅฏ็”จ็กฌไปถ็ผ–็ **: ๆฃ€ๆŸฅ `/stream/codecs` ็กฎ่ฎคๆœ‰ VAAPI/Rkmpp -- **้™ไฝŽ็ ็އ้ข„่ฎพ**: Quality โ†’ Balanced โ†’ Speed -- **ๅ…ณ้—ญๅธงๅŽป้‡**: ๅฆ‚ๆžœ็”ป้ข้ซ˜ๅบฆๅŠจๆ€ - -### Q: WebRTC ๆ— ๆณ•่ฟžๆŽฅ? - -1. ๆฃ€ๆŸฅ STUN/TURN ๆœๅŠกๅ™จ้…็ฝฎ (`/webrtc/ice-servers`) -2. ็กฎ่ฎค้˜ฒ็ซๅข™ๅ…่ฎธ UDP ๆต้‡ -3. ๆฃ€ๆŸฅๆต่งˆๅ™จๆŽงๅˆถๅฐ ICE ่ฟžๆŽฅ็Šถๆ€ -4. ๅฐ่ฏ•ไฝฟ็”จๅ…ฌๅ…ฑ STUN ๆœๅŠกๅ™จ: `stun:stun.l.google.com:19302` - -### Q: ๅฆ‚ไฝ•ๅœจ MJPEG ๅ’Œ WebRTC ๆจกๅผไน‹้—ดๅˆ‡ๆข? - -```bash -# ๅˆ‡ๆขๅˆฐ MJPEG ๆจกๅผ (้ซ˜ๅ…ผๅฎนๆ€ง) -curl -X POST http://localhost:8080/stream/mode \ - -H "Content-Type: application/json" \ - -d '{"mode": "mjpeg"}' - -# ๅˆ‡ๆขๅˆฐ WebRTC ๆจกๅผ (ไฝŽๅปถ่ฟŸ) -curl -X POST http://localhost:8080/stream/mode \ - -H "Content-Type: application/json" \ - -d '{"mode": "webrtc"}' -``` - -### Q: ๆ”ฏๆŒๅŒๆ—ถๅคšไธช WebRTC ่ฟžๆŽฅๅ—? - -ๆ˜ฏ็š„๏ผŒ`VideoSessionManager` ๆ”ฏๆŒๆœ€ๅคš 8 ไธชๅนถๅ‘ WebRTC ไผš่ฏใ€‚ๆฏไธชไผš่ฏๅ…ฑไบซๅŒไธ€ไธช่ง†้ข‘้‡‡้›†ๆบ๏ผŒไฝ†ๅฏไปฅไฝฟ็”จไธๅŒ็š„็ผ–็ ๅ™จ (H264/H265/VP8/VP9)ใ€‚ - -### Q: ๅฆ‚ไฝ•ๆŸฅ็œ‹ๅฝ“ๅ‰ไฝฟ็”จ็š„็ผ–็ ๅ™จๅŽ็ซฏ? - -็›‘ๅฌ `EncoderChanged` ไบ‹ไปถ (้€š่ฟ‡ WebSocket)๏ผŒๆˆ–ๆŸฅ็œ‹ๆ—ฅๅฟ—ไธญ็š„็ผ–็ ๅ™จๅˆๅง‹ๅŒ–ไฟกๆฏใ€‚ - ---- - -## 12. ๆžถๆž„ไบฎ็‚น - -### 12.1 ๅ•ไธ€ๅ…ฅๅฃ่ฎพ่ฎก - -`VideoStreamManager` ๆ˜ฏๆ‰€ๆœ‰่ง†้ข‘ๆ“ไฝœ็š„ๅ”ฏไธ€ๅ…ฅๅฃ๏ผŒๅฐ่ฃ…ไบ† MJPEG ๅ’Œ WebRTC ไธค็งๆจกๅผ็š„ๅคๆ‚ๆ€งใ€‚ - -### 12.2 ๆจกๅผ้š”็ฆป - -MJPEG ๅ’Œ WebRTC ๆจกๅผๅฎŒๅ…จๅˆ†็ฆป๏ผŒ้ฟๅ…็›ธไบ’ๅนฒๆ‰ฐใ€‚ๅˆ‡ๆขๆจกๅผๆ—ถไผšๅฎŒๅ…จๅœๆญขๆ—งๆจกๅผๅ†ๅฏๅŠจๆ–ฐๆจกๅผใ€‚ - -### 12.3 ็กฌไปถ่‡ช้€‚ๅบ” - -้€š่ฟ‡ `EncoderRegistry` ่‡ชๅŠจๆฃ€ๆต‹็กฌไปถ่ƒฝๅŠ›๏ผŒไผ˜ๅ…ˆไฝฟ็”จ็กฌไปถๅŠ ้€Ÿ๏ผŒๆ— ้œ€ๆ‰‹ๅŠจ้…็ฝฎใ€‚ - -### 12.4 ๅคš็ผ–่งฃ็ ๅ™จๆ”ฏๆŒ - -WebRTC ๆจกๅผๆ”ฏๆŒ H264/H265/VP8/VP9 ๅ››็ง็ผ–็ ๅ™จ๏ผŒๅฏๆ นๆฎๅฎขๆˆท็ซฏ่ƒฝๅŠ›ๅๅ•†ๆœ€ไฝณ็ผ–็ ๅ™จใ€‚ - -### 12.5 ้›ถ้…็ฝฎ่ฎพๅค‡ๅ‘็Žฐ - -่‡ชๅŠจๆ‰ซๆ `/dev/video*`๏ผŒ่ฏ†ๅˆซ HDMI ้‡‡้›†ๅกๅนถ่ฎก็ฎ—ไผ˜ๅ…ˆ็บง๏ผŒไผ˜ๅ…ˆ้€‰ๆ‹ฉๆœ€ไฝณ่ฎพๅค‡ใ€‚ diff --git a/docs/modules/web.md b/docs/modules/web.md deleted file mode 100644 index f45ed87f..00000000 --- a/docs/modules/web.md +++ /dev/null @@ -1,428 +0,0 @@ -# Web ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -Web ๆจกๅ—ๆไพ› HTTP API ๅ’Œ้™ๆ€ๆ–‡ไปถๆœๅŠกใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- REST API -- WebSocket -- ้™ๆ€ๆ–‡ไปถๆœๅŠก -- ่ฎค่ฏไธญ้—ดไปถ -- CORS ๆ”ฏๆŒ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/web/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ routes.rs # ่ทฏ็”ฑๅฎšไน‰ (9KB) -โ”œโ”€โ”€ ws.rs # WebSocket (8KB) -โ”œโ”€โ”€ audio_ws.rs # ้Ÿณ้ข‘ WebSocket (8KB) -โ”œโ”€โ”€ static_files.rs # ้™ๆ€ๆ–‡ไปถ (6KB) -โ””โ”€โ”€ handlers/ # API ๅค„็†ๅ™จ - โ”œโ”€โ”€ mod.rs - โ””โ”€โ”€ config/ - โ”œโ”€โ”€ mod.rs - โ”œโ”€โ”€ apply.rs - โ”œโ”€โ”€ types.rs - โ””โ”€โ”€ rustdesk.rs -``` - ---- - -## 2. ่ทฏ็”ฑ็ป“ๆž„ - -### 2.1 ๅ…ฌๅ…ฑ่ทฏ็”ฑ (ๆ— ่ฎค่ฏ) - -| ่ทฏ็”ฑ | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/health` | GET | ๅฅๅบทๆฃ€ๆŸฅ | -| `/auth/login` | POST | ็™ปๅฝ• | -| `/setup` | GET | ่Žทๅ–่ฎพ็ฝฎ็Šถๆ€ | -| `/setup/init` | POST | ๅˆๅง‹ๅŒ–่ฎพ็ฝฎ | - -### 2.2 ็”จๆˆท่ทฏ็”ฑ (้œ€่ฎค่ฏ) - -| ่ทฏ็”ฑ | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/info` | GET | ็ณป็ปŸไฟกๆฏ | -| `/devices` | GET | ่ฎพๅค‡ๅˆ—่กจ | -| `/stream/*` | * | ๆตๆŽงๅˆถ | -| `/webrtc/*` | * | WebRTC ไฟกไปค | -| `/hid/*` | * | HID ๆŽงๅˆถ | -| `/audio/*` | * | ้Ÿณ้ข‘ๆŽงๅˆถ | -| `/ws` | WS | ไบ‹ไปถ WebSocket | -| `/ws/audio` | WS | ้Ÿณ้ข‘ WebSocket | - -### 2.3 ็ฎก็†ๅ‘˜่ทฏ็”ฑ (้œ€ Admin) - -| ่ทฏ็”ฑ | ๆ–นๆณ• | ๆ่ฟฐ | -|------|------|------| -| `/config/*` | * | ้…็ฝฎ็ฎก็† | -| `/msd/*` | * | MSD ๆ“ไฝœ | -| `/atx/*` | * | ATX ๆŽงๅˆถ | -| `/extensions/*` | * | ๆ‰ฉๅฑ•็ฎก็† | -| `/rustdesk/*` | * | RustDesk | -| `/users/*` | * | ็”จๆˆท็ฎก็† | - ---- - -## 3. ่ทฏ็”ฑๅฎšไน‰ - -```rust -pub fn create_router(state: Arc) -> Router { - Router::new() - // ๅ…ฌๅ…ฑ่ทฏ็”ฑ - .route("/health", get(handlers::health)) - .route("/auth/login", post(handlers::login)) - .route("/setup", get(handlers::setup_status)) - .route("/setup/init", post(handlers::setup_init)) - - // ็”จๆˆท่ทฏ็”ฑ - .nest("/api", user_routes()) - - // ็ฎก็†ๅ‘˜่ทฏ็”ฑ - .nest("/api/admin", admin_routes()) - - // ้™ๆ€ๆ–‡ไปถ - .fallback(static_files::serve) - - // ไธญ้—ดไปถ - .layer(CorsLayer::permissive()) - .layer(CompressionLayer::new()) - .layer(TraceLayer::new_for_http()) - - // ็Šถๆ€ - .with_state(state) -} - -fn user_routes() -> Router { - Router::new() - .route("/info", get(handlers::system_info)) - .route("/devices", get(handlers::list_devices)) - - // ๆตๆŽงๅˆถ - .route("/stream/status", get(handlers::stream_status)) - .route("/stream/start", post(handlers::stream_start)) - .route("/stream/stop", post(handlers::stream_stop)) - .route("/stream/mjpeg", get(handlers::mjpeg_stream)) - - // WebRTC - .route("/webrtc/session", post(handlers::webrtc_create_session)) - .route("/webrtc/offer", post(handlers::webrtc_offer)) - .route("/webrtc/ice", post(handlers::webrtc_ice)) - .route("/webrtc/close", post(handlers::webrtc_close)) - - // HID - .route("/hid/status", get(handlers::hid_status)) - .route("/hid/reset", post(handlers::hid_reset)) - - // WebSocket - .route("/ws", get(handlers::ws_handler)) - .route("/ws/audio", get(handlers::audio_ws_handler)) - - // ่ฎค่ฏไธญ้—ดไปถ - .layer(middleware::from_fn(auth_middleware)) -} - -fn admin_routes() -> Router { - Router::new() - // ้…็ฝฎ - .route("/config", get(handlers::config::get_config)) - .route("/config", patch(handlers::config::update_config)) - - // MSD - .route("/msd/status", get(handlers::msd_status)) - .route("/msd/connect", post(handlers::msd_connect)) - - // ATX - .route("/atx/status", get(handlers::atx_status)) - .route("/atx/power/short", post(handlers::atx_power_short)) - - // ่ฎค่ฏไธญ้—ดไปถ - .layer(middleware::from_fn(auth_middleware)) - .layer(middleware::from_fn(admin_middleware)) -} -``` - ---- - -## 4. ้™ๆ€ๆ–‡ไปถๆœๅŠก - -```rust -#[derive(RustEmbed)] -#[folder = "web/dist"] -#[include = "*.html"] -#[include = "*.js"] -#[include = "*.css"] -#[include = "assets/*"] -struct Assets; - -pub async fn serve(uri: Uri) -> impl IntoResponse { - let path = uri.path().trim_start_matches('/'); - - // ๅฐ่ฏ•่Žทๅ–ๆ–‡ไปถ - if let Some(content) = Assets::get(path) { - let mime = mime_guess::from_path(path) - .first_or_octet_stream(); - - return ( - [(header::CONTENT_TYPE, mime.as_ref())], - content.data.into_owned(), - ).into_response(); - } - - // SPA ๅ›ž้€€ๅˆฐ index.html - if let Some(content) = Assets::get("index.html") { - return ( - [(header::CONTENT_TYPE, "text/html")], - content.data.into_owned(), - ).into_response(); - } - - StatusCode::NOT_FOUND.into_response() -} -``` - ---- - -## 5. WebSocket ๅค„็† - -### 5.1 ไบ‹ไปถ WebSocket (ws.rs) - -```rust -pub async fn ws_handler( - ws: WebSocketUpgrade, - State(state): State>, -) -> impl IntoResponse { - ws.on_upgrade(|socket| handle_ws(socket, state)) -} - -async fn handle_ws(mut socket: WebSocket, state: Arc) { - // ๅ‘้€ๅˆๅง‹่ฎพๅค‡ไฟกๆฏ - let device_info = state.get_device_info().await; - let json = serde_json::to_string(&device_info).unwrap(); - let _ = socket.send(Message::Text(json)).await; - - // ่ฎข้˜…ไบ‹ไปถ - let mut rx = state.events.subscribe(); - - loop { - tokio::select! { - // ๅ‘้€ไบ‹ไปถ - result = rx.recv() => { - if let Ok(event) = result { - let json = serde_json::to_string(&event).unwrap(); - if socket.send(Message::Text(json)).await.is_err() { - break; - } - } - } - - // ๆŽฅๆ”ถๆถˆๆฏ (ๅฟƒ่ทณ/ๅ…ณ้—ญ) - msg = socket.recv() => { - match msg { - Some(Ok(Message::Ping(data))) => { - let _ = socket.send(Message::Pong(data)).await; - } - Some(Ok(Message::Close(_))) | None => break, - _ => {} - } - } - } - } -} -``` - -### 5.2 ้Ÿณ้ข‘ WebSocket (audio_ws.rs) - -```rust -pub async fn audio_ws_handler( - ws: WebSocketUpgrade, - State(state): State>, -) -> impl IntoResponse { - ws.on_upgrade(|socket| handle_audio_ws(socket, state)) -} - -async fn handle_audio_ws(mut socket: WebSocket, state: Arc) { - // ่ฎข้˜…้Ÿณ้ข‘ๅธง - let mut rx = state.audio.subscribe(); - - loop { - tokio::select! { - // ๅ‘้€้Ÿณ้ข‘ๅธง - result = rx.recv() => { - if let Ok(frame) = result { - if socket.send(Message::Binary(frame.data.to_vec())).await.is_err() { - break; - } - } - } - - // ๅค„็†ๅ…ณ้—ญ - msg = socket.recv() => { - match msg { - Some(Ok(Message::Close(_))) | None => break, - _ => {} - } - } - } - } -} -``` - ---- - -## 6. MJPEG ๆต - -```rust -pub async fn mjpeg_stream( - State(state): State>, -) -> impl IntoResponse { - let boundary = "frame"; - - // ่ฎข้˜…่ง†้ข‘ๅธง - let rx = state.stream_manager.subscribe_mjpeg(); - - // ๅˆ›ๅปบๆต - let stream = async_stream::stream! { - let mut rx = rx; - while let Ok(frame) = rx.recv().await { - let header = format!( - "--{}\r\nContent-Type: image/jpeg\r\nContent-Length: {}\r\n\r\n", - boundary, - frame.data.len() - ); - yield Ok::<_, std::io::Error>(Bytes::from(header)); - yield Ok(frame.data.clone()); - yield Ok(Bytes::from("\r\n")); - } - }; - - ( - [( - header::CONTENT_TYPE, - format!("multipart/x-mixed-replace; boundary={}", boundary), - )], - Body::from_stream(stream), - ) -} -``` - ---- - -## 7. ้”™่ฏฏๅค„็† - -```rust -impl IntoResponse for AppError { - fn into_response(self) -> Response { - let (status, message) = match self { - AppError::AuthError => (StatusCode::UNAUTHORIZED, "Authentication failed"), - AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"), - AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden"), - AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.as_str()), - AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.as_str()), - AppError::Internal(err) => { - tracing::error!("Internal error: {:?}", err); - (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error") - } - // ... - }; - - (status, Json(json!({ "error": message }))).into_response() - } -} -``` - ---- - -## 8. ่ฏทๆฑ‚ๆๅ–ๅ™จ - -```rust -// ไปŽ Cookie ่Žทๅ–ไผš่ฏ -pub struct AuthUser(pub Session); - -#[async_trait] -impl FromRequestParts for AuthUser -where - S: Send + Sync, -{ - type Rejection = AppError; - - async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - let cookies = Cookies::from_request_parts(parts, state).await?; - let token = cookies - .get("session_id") - .map(|c| c.value().to_string()) - .ok_or(AppError::Unauthorized)?; - - let state = parts.extensions.get::>().unwrap(); - let session = state.sessions - .get_session(&token) - .ok_or(AppError::Unauthorized)?; - - Ok(AuthUser(session)) - } -} -``` - ---- - -## 9. ไธญ้—ดไปถ - -### 9.1 ่ฎค่ฏไธญ้—ดไปถ - -```rust -pub async fn auth_middleware( - State(state): State>, - cookies: Cookies, - mut request: Request, - next: Next, -) -> Response { - let token = cookies - .get("session_id") - .map(|c| c.value().to_string()); - - if let Some(session) = token.and_then(|t| state.sessions.get_session(&t)) { - request.extensions_mut().insert(session); - next.run(request).await - } else { - StatusCode::UNAUTHORIZED.into_response() - } -} -``` - -### 9.2 Admin ไธญ้—ดไปถ - -```rust -pub async fn admin_middleware( - Extension(session): Extension, - request: Request, - next: Next, -) -> Response { - if session.role == UserRole::Admin { - next.run(request).await - } else { - StatusCode::FORBIDDEN.into_response() - } -} -``` - ---- - -## 10. HTTPS ๆ”ฏๆŒ - -```rust -// ไฝฟ็”จ axum-server ๆไพ› TLS -let tls_config = RustlsConfig::from_pem_file(cert_path, key_path).await?; - -axum_server::bind_rustls(addr, tls_config) - .serve(app.into_make_service()) - .await?; - -// ๆˆ–่‡ชๅŠจ็”Ÿๆˆ่‡ช็ญพๅ่ฏไนฆ -let (cert, key) = generate_self_signed_cert()?; -let tls_config = RustlsConfig::from_pem(cert, key).await?; -``` diff --git a/docs/modules/webrtc.md b/docs/modules/webrtc.md deleted file mode 100644 index 932a6d49..00000000 --- a/docs/modules/webrtc.md +++ /dev/null @@ -1,789 +0,0 @@ -# WebRTC ๆจกๅ—ๆ–‡ๆกฃ - -## 1. ๆจกๅ—ๆฆ‚่ฟฐ - -WebRTC ๆจกๅ—ๆไพ›ไฝŽๅปถ่ฟŸ็š„ๅฎžๆ—ถ้Ÿณ่ง†้ข‘ๆตไผ ่พ“๏ผŒๆ”ฏๆŒๅคš็ง่ง†้ข‘็ผ–็ ๆ ผๅผๅ’Œ DataChannel HID ๆŽงๅˆถใ€‚ - -### 1.1 ไธป่ฆๅŠŸ่ƒฝ - -- WebRTC ไผš่ฏ็ฎก็† -- ๅคš็ผ–็ ๅ™จๆ”ฏๆŒ (H264/H265/VP8/VP9) -- ้Ÿณ้ข‘่ฝจ้“ (Opus) -- DataChannel HID -- ICE/STUN/TURN ๆ”ฏๆŒ - -### 1.2 ๆ–‡ไปถ็ป“ๆž„ - -``` -src/webrtc/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ webrtc_streamer.rs # ็ปŸไธ€็ฎก็†ๅ™จ (35KB) -โ”œโ”€โ”€ universal_session.rs # ไผš่ฏ็ฎก็† (32KB) -โ”œโ”€โ”€ unified_video_track.rs # ็ปŸไธ€่ง†้ข‘่ฝจ้“ (15KB) -โ”œโ”€โ”€ video_track.rs # ่ง†้ข‘่ฝจ้“ (19KB) -โ”œโ”€โ”€ rtp.rs # RTP ๆ‰“ๅŒ… (24KB) -โ”œโ”€โ”€ h265_payloader.rs # H265 RTP (15KB) -โ”œโ”€โ”€ peer.rs # PeerConnection (17KB) -โ”œโ”€โ”€ config.rs # ้…็ฝฎ (3KB) -โ”œโ”€โ”€ signaling.rs # ไฟกไปค (5KB) -โ”œโ”€โ”€ session.rs # ไผš่ฏๅŸบ็ฑป (8KB) -โ””โ”€โ”€ track.rs # ่ฝจ้“ๅŸบ็ฑป (11KB) -``` - ---- - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ๆ•ดไฝ“ๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ WebRTC Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - Browser - โ”‚ - โ”‚ HTTP Signaling - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ WebRtcStreamer โ”‚ - โ”‚(webrtc_streamer)โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚Session โ”‚ โ”‚Session โ”‚ โ”‚Session โ”‚ -โ”‚ 1 โ”‚ โ”‚ 2 โ”‚ โ”‚ N โ”‚ -โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ SharedVideoPipeline โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚H264 โ”‚ โ”‚H265 โ”‚ โ”‚VP8 โ”‚ โ”‚VP9 โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ VideoCapturer โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ไผš่ฏ็”Ÿๅ‘ฝๅ‘จๆœŸ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Session Lifecycle โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -1. ๅˆ›ๅปบไผš่ฏ - POST /webrtc/session - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Create Session โ”‚ - โ”‚ Generate ID โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - { session_id: "..." } - -2. ๅ‘้€ Offer - POST /webrtc/offer - { session_id, codec, offer_sdp } - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Process Offer โ”‚ - โ”‚ Create Answer โ”‚ - โ”‚ Setup Tracks โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - { answer_sdp, ice_candidates } - -3. ICE ๅ€™้€‰ - POST /webrtc/ice - { session_id, candidate } - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Add ICE โ”‚ - โ”‚ Candidate โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -4. ่ฟžๆŽฅๅปบ็ซ‹ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ DTLS Handshake โ”‚ - โ”‚ SRTP Setup โ”‚ - โ”‚ DataChannel โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - ๅผ€ๅง‹ไผ ่พ“่ง†้ข‘/้Ÿณ้ข‘ - -5. ๅ…ณ้—ญไผš่ฏ - POST /webrtc/close - { session_id } - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Cleanup โ”‚ - โ”‚ Release โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆ ธๅฟƒ็ป„ไปถ - -### 3.1 WebRtcStreamer (webrtc_streamer.rs) - -WebRTC ๆœๅŠกไธป็ฑปใ€‚ - -```rust -pub struct WebRtcStreamer { - /// ไผš่ฏๆ˜ ๅฐ„ - sessions: Arc>>>, - - /// ๅ…ฑไบซ่ง†้ข‘็ฎก้“ - video_pipeline: Arc, - - /// ๅ…ฑไบซ้Ÿณ้ข‘็ฎก้“ - audio_pipeline: Arc, - - /// HID ๆŽงๅˆถๅ™จ - hid: Arc, - - /// ้…็ฝฎ - config: WebRtcConfig, - - /// ไบ‹ไปถๆ€ป็บฟ - events: Arc, -} - -impl WebRtcStreamer { - /// ๅˆ›ๅปบๆตๆœๅŠก - pub async fn new( - video_pipeline: Arc, - audio_pipeline: Arc, - hid: Arc, - config: WebRtcConfig, - events: Arc, - ) -> Result; - - /// ๅˆ›ๅปบไผš่ฏ - pub async fn create_session(&self) -> Result; - - /// ๅค„็† Offer - pub async fn process_offer( - &self, - session_id: &str, - offer: &str, - codec: VideoCodec, - ) -> Result; - - /// ๆทปๅŠ  ICE ๅ€™้€‰ - pub async fn add_ice_candidate( - &self, - session_id: &str, - candidate: &str, - ) -> Result<()>; - - /// ๅ…ณ้—ญไผš่ฏ - pub async fn close_session(&self, session_id: &str) -> Result<()>; - - /// ่Žทๅ–ไผš่ฏๅˆ—่กจ - pub fn list_sessions(&self) -> Vec; - - /// ่Žทๅ–็ปŸ่ฎกไฟกๆฏ - pub fn get_stats(&self) -> WebRtcStats; -} - -pub struct OfferResponse { - pub answer_sdp: String, - pub ice_candidates: Vec, -} - -pub struct WebRtcStats { - pub active_sessions: usize, - pub total_bytes_sent: u64, - pub avg_bitrate: u32, -} -``` - -### 3.2 UniversalSession (universal_session.rs) - -ๅ•ไธช WebRTC ไผš่ฏใ€‚ - -```rust -pub struct UniversalSession { - /// ไผš่ฏ ID - id: String, - - /// PeerConnection - peer: Arc, - - /// ่ง†้ข‘่ฝจ้“ - video_track: Arc, - - /// ้Ÿณ้ข‘่ฝจ้“ - audio_track: Option>, - - /// HID DataChannel - hid_channel: Arc>>>, - - /// HID ๅค„็†ๅ™จ - hid_handler: Arc, - - /// ็Šถๆ€ - state: Arc>, - - /// ็ผ–็ ๅ™จ็ฑปๅž‹ - codec: VideoCodec, -} - -impl UniversalSession { - /// ๅˆ›ๅปบไผš่ฏ - pub async fn new( - id: String, - config: &WebRtcConfig, - video_pipeline: Arc, - audio_pipeline: Arc, - hid_handler: Arc, - codec: VideoCodec, - ) -> Result; - - /// ๅค„็† Offer SDP - pub async fn handle_offer(&self, offer_sdp: &str) -> Result; - - /// ๆทปๅŠ  ICE ๅ€™้€‰ - pub async fn add_ice_candidate(&self, candidate: &str) -> Result<()>; - - /// ่Žทๅ– ICE ๅ€™้€‰ - pub fn get_ice_candidates(&self) -> Vec; - - /// ๅ…ณ้—ญไผš่ฏ - pub async fn close(&self) -> Result<()>; - - /// ่Žทๅ–็Šถๆ€ - pub fn state(&self) -> SessionState; - - /// ่Žทๅ–็ปŸ่ฎก - pub fn stats(&self) -> SessionStats; -} - -pub enum SessionState { - New, - Connecting, - Connected, - Disconnected, - Failed, - Closed, -} - -pub struct SessionStats { - pub bytes_sent: u64, - pub packets_sent: u64, - pub bitrate: u32, - pub frame_rate: f32, - pub round_trip_time: Duration, -} -``` - -### 3.3 VideoTrack (video_track.rs) - -่ง†้ข‘่ฝจ้“ๅฐ่ฃ…ใ€‚ - -```rust -pub struct UniversalVideoTrack { - /// ่ฝจ้“ ID - id: String, - - /// ็ผ–็ ็ฑปๅž‹ - codec: VideoCodec, - - /// RTP ๅ‘้€ๅ™จ - rtp_sender: Arc, - - /// ๅธง่ฎกๆ•ฐ - frame_count: AtomicU64, - - /// ็ปŸ่ฎก - stats: Arc>, -} - -impl UniversalVideoTrack { - /// ๅˆ›ๅปบ่ฝจ้“ - pub fn new(id: &str, codec: VideoCodec) -> Result; - - /// ๅ‘้€็ผ–็ ๅธง - pub async fn send_frame(&self, frame: &EncodedFrame) -> Result<()>; - - /// ่Žทๅ– RTP ๅ‚ๆ•ฐ - pub fn rtp_params(&self) -> RtpParameters; - - /// ่Žทๅ–็ปŸ่ฎก - pub fn stats(&self) -> TrackStats; -} - -pub struct TrackStats { - pub frames_sent: u64, - pub bytes_sent: u64, - pub packets_sent: u64, - pub packet_loss: f32, -} -``` - -### 3.4 RTP ๆ‰“ๅŒ… (rtp.rs) - -RTP ๅ่ฎฎๅฎž็Žฐใ€‚ - -```rust -pub struct RtpPacketizer { - /// SSRC - ssrc: u32, - - /// ๅบๅˆ—ๅท - sequence: u16, - - /// ๆ—ถ้—ดๆˆณ - timestamp: u32, - - /// ่ดŸ่ฝฝ็ฑปๅž‹ - payload_type: u8, - - /// ๆ—ถ้’Ÿ้ข‘็އ - clock_rate: u32, -} - -impl RtpPacketizer { - /// ๅˆ›ๅปบๆ‰“ๅŒ…ๅ™จ - pub fn new(codec: VideoCodec) -> Self; - - /// ๆ‰“ๅŒ… H264 ๅธง - pub fn packetize_h264(&mut self, frame: &[u8], keyframe: bool) -> Vec>; - - /// ๆ‰“ๅŒ… VP8 ๅธง - pub fn packetize_vp8(&mut self, frame: &[u8], keyframe: bool) -> Vec>; - - /// ๆ‰“ๅŒ… VP9 ๅธง - pub fn packetize_vp9(&mut self, frame: &[u8], keyframe: bool) -> Vec>; - - /// ๆ‰“ๅŒ… Opus ๅธง - pub fn packetize_opus(&mut self, frame: &[u8]) -> Vec; -} - -/// H264 NAL ๅ•ๅ…ƒๅˆ†็‰‡ -pub struct H264Fragmenter; - -impl H264Fragmenter { - /// ๅˆ†็‰‡ๅคงไบŽ MTU ็š„ NAL - pub fn fragment(nal: &[u8], mtu: usize) -> Vec>; - - /// ๅˆ›ๅปบ STAP-A ่šๅˆ - pub fn aggregate(nals: &[&[u8]]) -> Vec; -} -``` - -### 3.5 H265 ๆ‰“ๅŒ…ๅ™จ (h265_payloader.rs) - -H265/HEVC RTP ๆ‰“ๅŒ…ใ€‚ - -```rust -pub struct H265Payloader { - /// MTU ๅคงๅฐ - mtu: usize, -} - -impl H265Payloader { - /// ๅˆ›ๅปบๆ‰“ๅŒ…ๅ™จ - pub fn new(mtu: usize) -> Self; - - /// ๆ‰“ๅŒ… H265 ๅธง - pub fn packetize(&self, frame: &[u8]) -> Vec>; - - /// ๅˆ†ๆž NAL ๅ•ๅ…ƒ็ฑปๅž‹ - fn get_nal_type(nal: &[u8]) -> u8; - - /// ๆ˜ฏๅฆ้œ€่ฆๅˆ†็‰‡ - fn needs_fragmentation(&self, nal: &[u8]) -> bool; -} -``` - ---- - -## 4. ไฟกไปคๅ่ฎฎ - -### 4.1 ๅˆ›ๅปบไผš่ฏ - -``` -POST /api/webrtc/session -Content-Type: application/json - -{} - -Response: -{ - "session_id": "abc123-def456" -} -``` - -### 4.2 ๅ‘้€ Offer - -``` -POST /api/webrtc/offer -Content-Type: application/json - -{ - "session_id": "abc123-def456", - "video_codec": "h264", - "enable_audio": true, - "offer_sdp": "v=0\r\no=- ..." -} - -Response: -{ - "answer_sdp": "v=0\r\no=- ...", - "ice_candidates": [ - "candidate:1 1 UDP ...", - "candidate:2 1 TCP ..." - ] -} -``` - -### 4.3 ICE ๅ€™้€‰ - -``` -POST /api/webrtc/ice -Content-Type: application/json - -{ - "session_id": "abc123-def456", - "candidate": "candidate:1 1 UDP ..." -} - -Response: -{ - "success": true -} -``` - -### 4.4 ๅ…ณ้—ญไผš่ฏ - -``` -POST /api/webrtc/close -Content-Type: application/json - -{ - "session_id": "abc123-def456" -} - -Response: -{ - "success": true -} -``` - ---- - -## 5. ้…็ฝฎ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct WebRtcConfig { - /// STUN ๆœๅŠกๅ™จ - pub stun_servers: Vec, - - /// TURN ๆœๅŠกๅ™จ - pub turn_servers: Vec, - - /// ้ป˜่ฎค็ผ–็ ๅ™จ - pub default_codec: VideoCodec, - - /// ็ ็އ (kbps) - pub bitrate_kbps: u32, - - /// GOP ๅคงๅฐ - pub gop_size: u32, - - /// ๅฏ็”จ้Ÿณ้ข‘ - pub enable_audio: bool, - - /// ๅฏ็”จ DataChannel HID - pub enable_datachannel_hid: bool, -} - -pub struct TurnServer { - pub url: String, - pub username: String, - pub password: String, -} - -impl Default for WebRtcConfig { - fn default() -> Self { - Self { - stun_servers: vec!["stun:stun.l.google.com:19302".to_string()], - turn_servers: vec![], - default_codec: VideoCodec::H264, - bitrate_kbps: 2000, - gop_size: 60, - enable_audio: true, - enable_datachannel_hid: true, - } - } -} -``` - ---- - -## 6. DataChannel HID - -### 6.1 ๆถˆๆฏๆ ผๅผ - -```javascript -// ้”ฎ็›˜ไบ‹ไปถ -{ - "type": "keyboard", - "keys": ["KeyA", "KeyB"], - "modifiers": { - "ctrl": false, - "shift": true, - "alt": false, - "meta": false - } -} - -// ้ผ ๆ ‡ไบ‹ไปถ -{ - "type": "mouse", - "x": 16384, - "y": 16384, - "button": "left", - "event": "press" -} - -// ้ผ ๆ ‡ๆจกๅผ -{ - "type": "mouse_mode", - "mode": "absolute" -} -``` - -### 6.2 ๅค„็†ๆต็จ‹ - -``` -DataChannel Message - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚Parse JSON Event โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚HidDataChannel โ”‚ -โ”‚ Handler โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HidController โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - USB/Serial -``` - ---- - -## 7. ๆ”ฏๆŒ็š„็ผ–็ ๅ™จ - -| ็ผ–็ ๅ™จ | RTP ่ดŸ่ฝฝ็ฑปๅž‹ | ๆ—ถ้’Ÿ้ข‘็އ | ็กฌไปถๅŠ ้€Ÿ | -|--------|-------------|---------|---------| -| H264 | 96 (ๅŠจๆ€) | 90000 | VAAPI/RKMPP/V4L2 | -| H265 | 97 (ๅŠจๆ€) | 90000 | VAAPI | -| VP8 | 98 (ๅŠจๆ€) | 90000 | VAAPI | -| VP9 | 99 (ๅŠจๆ€) | 90000 | VAAPI | -| Opus | 111 (ๅŠจๆ€) | 48000 | ๆ—  (่ฝฏไปถ) | - ---- - -## 8. ้”™่ฏฏๅค„็† - -```rust -#[derive(Debug, thiserror::Error)] -pub enum WebRtcError { - #[error("Session not found: {0}")] - SessionNotFound(String), - - #[error("Session already exists")] - SessionExists, - - #[error("Invalid SDP: {0}")] - InvalidSdp(String), - - #[error("Codec not supported: {0}")] - CodecNotSupported(String), - - #[error("ICE failed")] - IceFailed, - - #[error("DTLS failed")] - DtlsFailed, - - #[error("Track error: {0}")] - TrackError(String), - - #[error("Connection closed")] - ConnectionClosed, -} -``` - ---- - -## 9. ไฝฟ็”จ็คบไพ‹ - -### 9.1 ๅˆ›ๅปบไผš่ฏ - -```rust -let streamer = WebRtcStreamer::new( - video_pipeline, - audio_pipeline, - hid, - WebRtcConfig::default(), - events, -).await?; - -// ๅˆ›ๅปบไผš่ฏ -let session_id = streamer.create_session().await?; - -// ๅค„็† Offer -let response = streamer.process_offer( - &session_id, - &offer_sdp, - VideoCodec::H264, -).await?; - -println!("Answer: {}", response.answer_sdp); -``` - -### 9.2 ๅ‰็ซฏ่ฟžๆŽฅ - -```javascript -// ๅˆ›ๅปบ PeerConnection -const pc = new RTCPeerConnection({ - iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] -}); - -// ๅˆ›ๅปบ DataChannel -const hidChannel = pc.createDataChannel('hid'); - -// ๅˆ›ๅปบ Offer -const offer = await pc.createOffer(); -await pc.setLocalDescription(offer); - -// ๅ‘้€ๅˆฐๆœๅŠกๅ™จ -const response = await fetch('/api/webrtc/offer', { - method: 'POST', - body: JSON.stringify({ - session_id, - video_codec: 'h264', - offer_sdp: offer.sdp - }) -}); - -const { answer_sdp, ice_candidates } = await response.json(); - -// ่ฎพ็ฝฎ Answer -await pc.setRemoteDescription({ type: 'answer', sdp: answer_sdp }); - -// ๆทปๅŠ  ICE ๅ€™้€‰ -for (const candidate of ice_candidates) { - await pc.addIceCandidate({ candidate }); -} -``` - ---- - -## 10. ็ฎก้“้‡ๅฏๆœบๅˆถ - -ๅฝ“็ ็އๆˆ–็ผ–็ ๅ™จ้…็ฝฎๅ˜ๆ›ดๆ—ถ๏ผŒ่ง†้ข‘็ฎก้“้œ€่ฆ้‡ๅฏใ€‚WebRTC ๆจกๅ—ๅฎž็Žฐไบ†่‡ชๅŠจ้‡่ฟžๆœบๅˆถ๏ผš - -### 10.1 ้‡ๅฏๆต็จ‹ - -``` -็”จๆˆทไฟฎๆ”น็ ็އ/็ผ–็ ๅ™จ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ set_bitrate_preset โ”‚ -โ”‚ 1. ไฟๅญ˜ frame_tx โ”‚ โ† ๅ…ณ้”ฎ๏ผšๅœจๅœๆญขๅ‰ไฟๅญ˜ -โ”‚ 2. ๅœๆญขๆ—ง็ฎก้“ โ”‚ -โ”‚ 3. ็ญ‰ๅพ…ๆธ…็† โ”‚ -โ”‚ 4. ๆขๅค frame_tx โ”‚ -โ”‚ 5. ๅˆ›ๅปบๆ–ฐ็ฎก้“ โ”‚ -โ”‚ 6. ้‡่ฟžๆ‰€ๆœ‰ไผš่ฏ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -ๆ‰€ๆœ‰ WebRTC ไผš่ฏ่‡ชๅŠจๆขๅค -``` - -### 10.2 ๅ…ณ้”ฎไปฃ็  - -```rust -pub async fn set_bitrate_preset(self: &Arc, preset: BitratePreset) -> Result<()> { - // ไฟๅญ˜ frame_tx (็›‘ๆŽงไปปๅŠกไผšๅœจ็ฎก้“ๅœๆญขๅŽๆธ…้™คๅฎƒ) - let saved_frame_tx = self.video_frame_tx.read().await.clone(); - - // ๅœๆญข็ฎก้“ - pipeline.stop(); - tokio::time::sleep(Duration::from_millis(100)).await; - - // ๆขๅค frame_tx ๅนถ้‡ๅปบ็ฎก้“ - if let Some(tx) = saved_frame_tx { - *self.video_frame_tx.write().await = Some(tx.clone()); - let pipeline = self.ensure_video_pipeline(tx).await?; - - // ้‡่ฟžๆ‰€ๆœ‰ไผš่ฏ - for session in sessions { - session.start_from_video_pipeline(pipeline.subscribe(), ...).await; - } - } -} -``` - ---- - -## 11. ๅธธ่ง้—ฎ้ข˜ - -### Q: ่ฟžๆŽฅ่ถ…ๆ—ถ? - -1. ๆฃ€ๆŸฅ STUN/TURN ้…็ฝฎ -2. ๆฃ€ๆŸฅ้˜ฒ็ซๅข™่ฎพ็ฝฎ -3. ๅฐ่ฏ•ไฝฟ็”จ TURN ไธญ็ปง - -### Q: ่ง†้ข‘ๅก้กฟ? - -1. ้™ไฝŽๅˆ†่พจ็އ/็ ็އ -2. ๆฃ€ๆŸฅ็ฝ‘็ปœๅธฆๅฎฝ -3. ไฝฟ็”จ็กฌไปถ็ผ–็  - -### Q: ้Ÿณ้ข‘ไธๅŒๆญฅ? - -1. ๆฃ€ๆŸฅๆ—ถ้—ดๆˆณๅŒๆญฅ -2. ่ฐƒๆ•ด็ผ“ๅ†ฒๅŒบๅคงๅฐ -3. ไฝฟ็”จ NTP ๅŒๆญฅ - -### Q: ๅˆ‡ๆข็ ็އๅŽ่ง†้ข‘้™ๆญข? - -1. ๆฃ€ๆŸฅ็ฎก้“้‡ๅฏ้€ป่พ‘ๆ˜ฏๅฆๆญฃ็กฎไฟๅญ˜ไบ† `video_frame_tx` -2. ็กฎ่ฎคไผš่ฏ้‡่ฟžๆˆๅŠŸ -3. ๆŸฅ็œ‹ๆ—ฅๅฟ—ไธญๆ˜ฏๅฆๆœ‰ "Reconnecting session" ไฟกๆฏ diff --git a/docs/report/hwcodec/00-architecture.md b/docs/report/hwcodec/00-architecture.md deleted file mode 100644 index 4f2ce235..00000000 --- a/docs/report/hwcodec/00-architecture.md +++ /dev/null @@ -1,477 +0,0 @@ -# hwcodec ๆŠ€ๆœฏๆžถๆž„ๆŠฅๅ‘Š - -## 1. ้กน็›ฎๆฆ‚่ฟฐ - -hwcodec ๆ˜ฏไธ€ไธชๅŸบไบŽ FFmpeg ็š„็กฌไปถ่ง†้ข‘็ผ–่งฃ็ ๅบ“๏ผŒๆฅๆบไบŽ RustDesk ้กน็›ฎๅนถ้’ˆๅฏน One-KVM ่ฟ›่กŒไบ†ๆทฑๅบฆๅฎšๅˆถไผ˜ๅŒ–ใ€‚่ฏฅๅบ“ไธ“ๆณจไบŽ IP-KVM ๅœบๆ™ฏ๏ผŒๆไพ› Windows ๅ’Œ Linux ๅนณๅฐ็š„ GPU ๅŠ ้€Ÿ่ง†้ข‘็ผ–็ ่ƒฝๅŠ›ใ€‚ - -### 1.1 ้กน็›ฎไฝ็ฝฎ - -``` -libs/hwcodec/ -โ”œโ”€โ”€ src/ # Rust ๆบไปฃ็  -โ””โ”€โ”€ cpp/ # C++ ๆบไปฃ็  -``` - -### 1.2 ๆ ธๅฟƒ็‰นๆ€ง - -- **ๅคš็ผ–่งฃ็ ๆ ผๅผๆ”ฏๆŒ**: H.264, H.265 (HEVC), VP8, VP9, MJPEG -- **็กฌไปถๅŠ ้€Ÿ**: NVENC, AMF, Intel QSV (Windows), VAAPI, RKMPP, V4L2 M2M (Linux) -- **่ทจๅนณๅฐ**: Windows, Linux (x86_64, ARM64, ARMv7) -- **ไฝŽๅปถ่ฟŸไผ˜ๅŒ–**: ไธ“ไธบๅฎžๆ—ถๆตๅช’ไฝ“ๅœบๆ™ฏ่ฎพ่ฎก -- **Rust/C++ ๆททๅˆๆžถๆž„**: Rust ๆไพ›ๅฎ‰ๅ…จ็š„ไธŠๅฑ‚ API๏ผŒC++ ๅฎž็Žฐๅบ•ๅฑ‚็ผ–่งฃ็ ้€ป่พ‘ -- **IP-KVM ไธ“็”จ**: ่งฃ็ ไป…ๆ”ฏๆŒ MJPEG๏ผˆ้‡‡้›†ๅก่พ“ๅ‡บๆ ผๅผ๏ผ‰๏ผŒ็ผ–็ ๆ”ฏๆŒๅคš็ง็กฌไปถๅŠ ้€Ÿ - -## 2. ๆžถๆž„่ฎพ่ฎก - -### 2.1 ๆ•ดไฝ“ๆžถๆž„ๅ›พ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Rust API Layer โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ -โ”‚ โ”‚ ffmpeg_ram module โ”‚โ”‚ -โ”‚ โ”‚ (encode.rs + decode.rs) โ”‚โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”‚ โ”‚ -โ”‚ FFI Bindings (bindgen) โ”‚ -โ”‚ โ–ผ โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ C++ Core Layer โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ -โ”‚ โ”‚ ffmpeg_ram (encode/decode) โ”‚โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ FFmpeg Libraries โ”‚ โ”‚ -โ”‚ โ”‚ libavcodec โ”‚ libavutil โ”‚ libavformat โ”‚ libswscale โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Hardware Acceleration Backends โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ NVENC โ”‚ โ”‚ AMF โ”‚ โ”‚ QSV โ”‚ โ”‚ VAAPI โ”‚ โ”‚ RKMPP โ”‚ โ”‚V4L2M2Mโ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ๆจกๅ—่Œ่ดฃ - -| ๆจกๅ— | ่Œ่ดฃ | ๅ…ณ้”ฎๆ–‡ไปถ | -|------|------|----------| -| `ffmpeg_ram` | ๅŸบไบŽ RAM ็š„่ฝฏไปถ/็กฌไปถ็ผ–่งฃ็  | `src/ffmpeg_ram/` | -| `common` | ๅ…ฌๅ…ฑๅฎšไน‰ๅ’Œ GPU ๆฃ€ๆต‹ | `src/common.rs` | -| `ffmpeg` | FFmpeg ๆ—ฅๅฟ—ๅ’Œๅˆๅง‹ๅŒ– | `src/ffmpeg.rs` | - -## 3. ๆจกๅ—่ฏฆ็ป†ๅˆ†ๆž - -### 3.1 ๅบ“ๅ…ฅๅฃ (lib.rs) - -```rust -// libs/hwcodec/src/lib.rs -pub mod common; -pub mod ffmpeg; -pub mod ffmpeg_ram; -``` - -**ๅŠŸ่ƒฝ**: -- ๅฏผๅ‡บๆ‰€ๆœ‰ๅญๆจกๅ— -- ๆไพ› C ๆ—ฅๅฟ—ๅ›ž่ฐƒๅ‡ฝๆ•ฐ - -### 3.2 ๅ…ฌๅ…ฑๆจกๅ— (common.rs) - -**ๆ ธๅฟƒ็ฑปๅž‹**: - -```rust -pub enum Driver { - NV, // NVIDIA - AMF, // AMD - MFX, // Intel - FFMPEG, // ่ฝฏไปถ็ผ–็  -} -``` - -**GPU ๆฃ€ๆต‹ๅ‡ฝๆ•ฐ**: - -| ๅนณๅฐ | ๆฃ€ๆต‹ๅ‡ฝๆ•ฐ | ๆฃ€ๆต‹ๆ–นๅผ | -|------|----------|----------| -| Linux | `linux_support_nv()` | ๅŠ ่ฝฝ libcuda.so + libnvidia-encode.so | -| Linux | `linux_support_amd()` | ๆฃ€ๆŸฅ `libamfrt64.so.1` | -| Linux | `linux_support_intel()` | ๆฃ€ๆŸฅ `libvpl.so`/`libmfx.so` | -| Linux | `linux_support_rkmpp()` | ๆฃ€ๆŸฅ `/dev/mpp_service` | -| Linux | `linux_support_v4l2m2m()` | ๆฃ€ๆŸฅ `/dev/video*` ่ฎพๅค‡ | - -### 3.3 FFmpeg RAM ็ผ–็ ๆจกๅ— - -#### 3.3.1 Rust ๅฑ‚ (src/ffmpeg_ram/) - -**CodecInfo ็ป“ๆž„ไฝ“**: - -```rust -pub struct CodecInfo { - pub name: String, // ็ผ–็ ๅ™จๅ็งฐๅฆ‚ "h264_nvenc" - pub mc_name: Option, // MediaCodec ๅ็งฐ (Android) - pub format: DataFormat, // H264/H265/VP8/VP9/MJPEG - pub priority: i32, // ไผ˜ๅ…ˆ็บง (Best=0, Good=1, Normal=2, Soft=3, Bad=4) - pub hwdevice: AVHWDeviceType, // ็กฌไปถ่ฎพๅค‡็ฑปๅž‹ -} -``` - -**EncodeContext ็ป“ๆž„ไฝ“**: - -```rust -pub struct EncodeContext { - pub name: String, // ็ผ–็ ๅ™จๅ็งฐ - pub width: i32, // ่ง†้ข‘ๅฎฝๅบฆ - pub height: i32, // ่ง†้ข‘้ซ˜ๅบฆ - pub pixfmt: AVPixelFormat, // ๅƒ็ด ๆ ผๅผ (NV12/YUV420P) - pub align: i32, // ๅ†…ๅญ˜ๅฏน้ฝ - pub fps: i32, // ๅธง็އ - pub gop: i32, // GOP ๅคงๅฐ - pub rc: RateControl, // ็ ็އๆŽงๅˆถๆจกๅผ - pub quality: Quality, // ่ดจ้‡็บงๅˆซ - pub kbs: i32, // ็›ฎๆ ‡็ ็އ (kbps) - pub q: i32, // ้‡ๅŒ–ๅ‚ๆ•ฐ - pub thread_count: i32, // ็บฟ็จ‹ๆ•ฐ -} -``` - -**Encoder ็ฑป**: - -```rust -pub struct Encoder { - codec: *mut c_void, // C++ ็ผ–็ ๅ™จๆŒ‡้’ˆ - frames: *mut Vec, // ็ผ–็ ่พ“ๅ‡บๅธง - pub ctx: EncodeContext, - pub linesize: Vec, // ่กŒๅคงๅฐ - pub offset: Vec, // ๅนณ้ขๅ็งป - pub length: i32, // ๆ€ปๆ•ฐๆฎ้•ฟๅบฆ -} -``` - -**ๆ ธๅฟƒๆ–นๆณ•**: - -| ๆ–นๆณ• | ๅŠŸ่ƒฝ | -|------|------| -| `Encoder::new()` | ๅˆ›ๅปบ็ผ–็ ๅ™จๅฎžไพ‹ | -| `Encoder::encode()` | ็ผ–็ ไธ€ๅธง YUV ๆ•ฐๆฎ | -| `Encoder::set_bitrate()` | ๅŠจๆ€่ฐƒๆ•ด็ ็އ | -| `Encoder::request_keyframe()` | ่ฏทๆฑ‚ไธ‹ไธ€ๅธงไธบๅ…ณ้”ฎๅธง | -| `Encoder::available_encoders()` | ๆฃ€ๆต‹็ณป็ปŸๅฏ็”จ็ผ–็ ๅ™จ | - -#### 3.3.2 C++ ๅฑ‚ (cpp/ffmpeg_ram/) - -**FFmpegRamEncoder ็ฑป** (ffmpeg_ram_encode.cpp): - -```cpp -class FFmpegRamEncoder { - AVCodecContext *c_ = NULL; // FFmpeg ็ผ–็ ไธŠไธ‹ๆ–‡ - AVFrame *frame_ = NULL; // ่พ“ๅ…ฅๅธง - AVPacket *pkt_ = NULL; // ็ผ–็ ่พ“ๅ‡บๅŒ… - AVBufferRef *hw_device_ctx_; // ็กฌไปถ่ฎพๅค‡ไธŠไธ‹ๆ–‡ - AVFrame *hw_frame_ = NULL; // ็กฌไปถๅธง - bool force_keyframe_ = false; // ๅผบๅˆถๅ…ณ้”ฎๅธงๆ ‡ๅฟ— - - // ไธป่ฆๆ–นๆณ• - bool init(int *linesize, int *offset, int *length); - int encode(const uint8_t *data, int length, const void *obj, uint64_t ms); - int do_encode(AVFrame *frame, const void *obj, int64_t ms); - int set_hwframe_ctx(); // ่ฎพ็ฝฎ็กฌไปถๅธงไธŠไธ‹ๆ–‡ -}; -``` - -**็ผ–็ ๆต็จ‹**: - -``` -่พ“ๅ…ฅ YUV ๆ•ฐๆฎ - โ”‚ - โ–ผ -fill_frame() - ๅกซๅ…… AVFrame ๆ•ฐๆฎๆŒ‡้’ˆ - โ”‚ - โ”œโ”€โ”€โ–ถ (่ฝฏไปถ็ผ–็ ) ็›ดๆŽฅไฝฟ็”จ frame_ - โ”‚ - โ””โ”€โ”€โ–ถ (็กฌไปถ็ผ–็ ) av_hwframe_transfer_data() ไผ ่พ“ๅˆฐ GPU - โ”‚ - โ–ผ - ไฝฟ็”จ hw_frame_ - โ”‚ - โ–ผ - avcodec_send_frame() - ๅ‘้€ๅธงๅˆฐ็ผ–็ ๅ™จ - โ”‚ - โ–ผ - avcodec_receive_packet() - ่Žทๅ–็ผ–็ ๆ•ฐๆฎ - โ”‚ - โ–ผ - callback() - ๅ›ž่ฐƒ่พ“ๅ‡บ -``` - -### 3.4 FFmpeg RAM ่งฃ็ ๆจกๅ— - -**IP-KVM ไธ“็”จ่ฎพ่ฎก**: ่งฃ็ ๅ™จไป…ๆ”ฏๆŒ MJPEG ่ฝฏไปถ่งฃ็ ๏ผŒๅ› ไธบ IP-KVM ๅœบๆ™ฏไธญ่ง†้ข‘้‡‡้›†ๅก่พ“ๅ‡บ็š„ๆ˜ฏ MJPEG ๆ ผๅผใ€‚ - -**Decoder ็ฑป**: - -```rust -pub struct Decoder { - codec: *mut c_void, - frames: *mut Vec, - pub ctx: DecodeContext, -} - -pub struct DecodeFrame { - pub pixfmt: AVPixelFormat, - pub width: i32, - pub height: i32, - pub data: Vec>, // Y, U, V ๅนณ้ขๆ•ฐๆฎ - pub linesize: Vec, - pub key: bool, -} -``` - -**available_decoders()**: ไป…่ฟ”ๅ›ž MJPEG ่ฝฏไปถ่งฃ็ ๅ™จ - -```rust -pub fn available_decoders() -> Vec { - vec![CodecInfo { - name: "mjpeg".to_owned(), - format: MJPEG, - hwdevice: AV_HWDEVICE_TYPE_NONE, - priority: Priority::Best as _, - ..Default::default() - }] -} -``` - -**C++ ๅฎž็Žฐ** (ffmpeg_ram_decode.cpp): - -```cpp -class FFmpegRamDecoder { - AVCodecContext *c_ = NULL; - AVFrame *frame_ = NULL; // ่งฃ็ ่พ“ๅ‡บๅธง - AVPacket *pkt_ = NULL; - - int do_decode(const void *obj); -}; -``` - -**่งฃ็ ๆต็จ‹**: - -``` -่พ“ๅ…ฅ MJPEG ๆ•ฐๆฎ - โ”‚ - โ–ผ -avcodec_send_packet() - ๅ‘้€ๆ•ฐๆฎๅˆฐ่งฃ็ ๅ™จ - โ”‚ - โ–ผ -avcodec_receive_frame() - ่Žทๅ–่งฃ็ ๅธง (YUV420P) - โ”‚ - โ–ผ -callback() - ๅ›ž่ฐƒ่พ“ๅ‡บ -``` - -## 4. ็กฌไปถๅŠ ้€Ÿๆ”ฏๆŒ - -### 4.1 ๆ”ฏๆŒ็š„็กฌไปถๅŠ ้€ŸๅŽ็ซฏ - -| ๅŽ็ซฏ | ๅŽ‚ๅ•† | ๅนณๅฐ | ็ผ–็ ๅ™จๅ็งฐ | -|------|------|------|-----------| -| NVENC | NVIDIA | Windows/Linux | h264_nvenc, hevc_nvenc | -| AMF | AMD | Windows/Linux | h264_amf, hevc_amf | -| QSV | Intel | Windows | h264_qsv, hevc_qsv | -| VAAPI | ้€š็”จ | Linux | h264_vaapi, hevc_vaapi, vp8_vaapi, vp9_vaapi | -| RKMPP | Rockchip | Linux | h264_rkmpp, hevc_rkmpp | -| V4L2 M2M | ARM SoC | Linux | h264_v4l2m2m, hevc_v4l2m2m | - -### 4.2 ็กฌไปถๆฃ€ๆต‹้€ป่พ‘ (Linux) - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -// NVIDIA ๆฃ€ๆต‹ - ็ฎ€ๅŒ–็š„ๅŠจๆ€ๅบ“ๆฃ€ๆต‹ -int linux_support_nv() { - void *handle = dlopen("libcuda.so.1", RTLD_LAZY); - if (!handle) handle = dlopen("libcuda.so", RTLD_LAZY); - if (!handle) return -1; - dlclose(handle); - - handle = dlopen("libnvidia-encode.so.1", RTLD_LAZY); - if (!handle) handle = dlopen("libnvidia-encode.so", RTLD_LAZY); - if (!handle) return -1; - dlclose(handle); - return 0; -} - -// AMD ๆฃ€ๆต‹ - ๆฃ€ๆŸฅ AMF ่ฟ่กŒๆ—ถๅบ“ -int linux_support_amd() { - void *handle = dlopen("libamfrt64.so.1", RTLD_LAZY); - if (!handle) return -1; - dlclose(handle); - return 0; -} - -// Intel ๆฃ€ๆต‹ - ๆฃ€ๆŸฅ VPL/MFX ๅบ“ -int linux_support_intel() { - const char *libs[] = {"libvpl.so", "libmfx.so", ...}; - // ไปปไธ€ๆˆๅŠŸๅŠ ่ฝฝๅˆ™่ฟ”ๅ›ž 0 -} - -// Rockchip MPP ๆฃ€ๆต‹ - ๆฃ€ๆŸฅ่ฎพๅค‡่Š‚็‚น -int linux_support_rkmpp() { - if (access("/dev/mpp_service", F_OK) == 0) return 0; - if (access("/dev/rga", F_OK) == 0) return 0; - return -1; -} - -// V4L2 M2M ๆฃ€ๆต‹ - ๆฃ€ๆŸฅ่ง†้ข‘่ฎพๅค‡ -int linux_support_v4l2m2m() { - const char *devices[] = {"/dev/video10", "/dev/video11", ...}; - // ไปปไธ€่ฎพๅค‡ๅฏๆ‰“ๅผ€ๅˆ™่ฟ”ๅ›ž 0 -} -``` - -### 4.3 ็ผ–็ ๅ™จไผ˜ๅ…ˆ็บง็ณป็ปŸ - -```rust -pub enum Priority { - Best = 0, // ๆœ€้ซ˜ไผ˜ๅ…ˆ็บง (็กฌไปถๅŠ ้€Ÿ) - Good = 1, // ่‰ฏๅฅฝ (VAAPI, ้ƒจๅˆ†็กฌไปถ) - Normal = 2, // ๆ™ฎ้€š - Soft = 3, // ่ฝฏไปถ็ผ–็  - Bad = 4, // ๆœ€ไฝŽไผ˜ๅ…ˆ็บง -} -``` - -**ไผ˜ๅ…ˆ็บงๅˆ†้…**: - -| ็ผ–็ ๅ™จ | ไผ˜ๅ…ˆ็บง | -|--------|--------| -| h264_nvenc, hevc_nvenc | Best (0) | -| h264_amf, hevc_amf | Best (0) | -| h264_qsv, hevc_qsv | Best (0) | -| h264_rkmpp, hevc_rkmpp | Best (0) | -| h264_vaapi, hevc_vaapi | Good (1) | -| h264_v4l2m2m, hevc_v4l2m2m | Good (1) | -| h264 (x264), hevc (x265) | Soft (3) | - -### 4.4 ไฝŽๅปถ่ฟŸไผ˜ๅŒ–้…็ฝฎ - -```cpp -// libs/hwcodec/cpp/common/util.cpp - -bool set_lantency_free(void *priv_data, const std::string &name) { - // NVENC: ็ฆ็”จๅปถ่ฟŸ็ผ“ๅ†ฒ - if (name.find("nvenc") != std::string::npos) { - av_opt_set(priv_data, "delay", "0", 0); - } - // AMF: ่ฎพ็ฝฎๆŸฅ่ฏข่ถ…ๆ—ถ - if (name.find("amf") != std::string::npos) { - av_opt_set(priv_data, "query_timeout", "1000", 0); - } - // QSV/VAAPI: ่ฎพ็ฝฎๅผ‚ๆญฅๆทฑๅบฆไธบ 1 - if (name.find("qsv") != std::string::npos || - name.find("vaapi") != std::string::npos) { - av_opt_set(priv_data, "async_depth", "1", 0); - } - // libvpx: ๅฎžๆ—ถๆจกๅผ - if (name.find("libvpx") != std::string::npos) { - av_opt_set(priv_data, "deadline", "realtime", 0); - av_opt_set_int(priv_data, "cpu-used", 6, 0); - av_opt_set_int(priv_data, "lag-in-frames", 0, 0); - } - return true; -} -``` - -## 5. ๆž„ๅปบ็ณป็ปŸ - -### 5.1 Cargo.toml ้…็ฝฎ - -```toml -[package] -name = "hwcodec" -version = "0.8.0" -edition = "2021" -description = "Hardware video codec for IP-KVM (Windows/Linux)" - -[features] -default = [] - -[dependencies] -log = "0.4" -serde_derive = "1.0" -serde = "1.0" -serde_json = "1.0" - -[build-dependencies] -cc = "1.0" # C++ ็ผ–่ฏ‘ -bindgen = "0.59" # FFI ็ป‘ๅฎš็”Ÿๆˆ -``` - -### 5.2 ๆž„ๅปบๆต็จ‹ (build.rs) - -``` -build.rs - โ”‚ - โ”œโ”€โ”€ build_common() - โ”‚ โ”œโ”€โ”€ ็”Ÿๆˆ common_ffi.rs (bindgen) - โ”‚ โ”œโ”€โ”€ ็ผ–่ฏ‘ๅนณๅฐ็›ธๅ…ณ C++ ไปฃ็  - โ”‚ โ””โ”€โ”€ ้“พๆŽฅ็ณป็ปŸๅบ“ (stdc++) - โ”‚ - โ””โ”€โ”€ ffmpeg::build_ffmpeg() - โ”œโ”€โ”€ ็”Ÿๆˆ ffmpeg_ffi.rs - โ”œโ”€โ”€ ้“พๆŽฅ FFmpeg ๅบ“ (VCPKG ๆˆ– pkg-config) - โ””โ”€โ”€ build_ffmpeg_ram() - โ””โ”€โ”€ ็ผ–่ฏ‘ ffmpeg_ram_encode.cpp, ffmpeg_ram_decode.cpp -``` - -### 5.3 FFmpeg ้“พๆŽฅๆ–นๅผ - -| ๆ–นๅผ | ๅนณๅฐ | ๆกไปถ | -|------|------|------| -| VCPKG ้™ๆ€้“พๆŽฅ | ่ทจๅนณๅฐ | ่ฎพ็ฝฎ `VCPKG_ROOT` ็Žฏๅขƒๅ˜้‡ | -| pkg-config ๅŠจๆ€้“พๆŽฅ | Linux | ้ป˜่ฎคๆ–นๅผ | - -## 6. ไธŽๅŽŸ็‰ˆ hwcodec ็š„ๅŒบๅˆซ - -้’ˆๅฏน One-KVM IP-KVM ๅœบๆ™ฏ๏ผŒๅฏนๅŽŸ็‰ˆ RustDesk hwcodec ่ฟ›่กŒไบ†ไปฅไธ‹็ฎ€ๅŒ–๏ผš - -### 6.1 ็งป้™ค็š„ๅŠŸ่ƒฝ - -| ็งป้™ค้กน | ๅŽŸๅ›  | -|--------|------| -| VRAM ๆจกๅ— | IP-KVM ไธ้œ€่ฆ GPU ๆ˜พๅญ˜็›ดๆŽฅ็ผ–่งฃ็  | -| Mux ๆจกๅ— | IP-KVM ไธ้œ€่ฆๅฝ•ๅˆถๅˆฐๆ–‡ไปถ | -| macOS ๆ”ฏๆŒ | IP-KVM ็›ฎๆ ‡ๅนณๅฐไธๅŒ…ๅซ macOS | -| Android ๆ”ฏๆŒ | IP-KVM ็›ฎๆ ‡ๅนณๅฐไธๅŒ…ๅซ Android | -| ๅค–้ƒจ SDK | ็ฎ€ๅŒ–ๆž„ๅปบ๏ผŒๅ‡ๅฐ‘ไพ่ต– | -| ๅคšๆ ผๅผ่งฃ็  | IP-KVM ไป…้œ€ MJPEG ่งฃ็  | - -### 6.2 ไฟ็•™็š„ๅŠŸ่ƒฝ - -| ไฟ็•™้กน | ็”จ้€” | -|--------|------| -| FFmpeg RAM ็ผ–็  | WebRTC ่ง†้ข‘็ผ–็  | -| FFmpeg RAM ่งฃ็  | MJPEG ้‡‡้›†ๅก่งฃ็  | -| ็กฌไปถๅŠ ้€Ÿ็ผ–็  | ไฝŽๅปถ่ฟŸ้ซ˜ๆ•ˆ็ผ–็  | -| ่ฝฏไปถ็ผ–็ ๅŽๅค‡ | ๆ— ็กฌไปถๅŠ ้€Ÿๆ—ถ็š„ๅ…œๅบ•ๆ–นๆกˆ | - -### 6.3 ไปฃ็ ้‡ๅฏนๆฏ” - -| ๆŒ‡ๆ ‡ | ๅŽŸ็‰ˆ | ็ฎ€ๅŒ–็‰ˆ | ๅ‡ๅฐ‘ | -|------|------|--------|------| -| ๅค–้ƒจ SDK | ~9MB | 0 | 100% | -| C++ ๆ–‡ไปถ | ~30 | ~10 | ~67% | -| Rust ๆจกๅ— | 6 | 3 | 50% | - -## 7. ๆ€ป็ป“ - -hwcodec ๅบ“้€š่ฟ‡ Rust/C++ ๆททๅˆๆžถๆž„๏ผŒๅœจไฟ่ฏๅ†…ๅญ˜ๅฎ‰ๅ…จ็š„ๅŒๆ—ถๅฎž็Žฐไบ†้ซ˜ๆ€ง่ƒฝ็š„่ง†้ข‘็ผ–่งฃ็ ใ€‚้’ˆๅฏน One-KVM IP-KVM ๅœบๆ™ฏ็š„ไผ˜ๅŒ–่ฎพ่ฎก็‰น็‚นๅŒ…ๆ‹ฌ: - -1. **็ฒพ็ฎ€็š„็ผ–่งฃ็ ๅ™จ API**: ่งฃ็ ไป…ๆ”ฏๆŒ MJPEG๏ผŒ็ผ–็ ๆ”ฏๆŒๅคš็ง็กฌไปถๅŠ ้€Ÿ -2. **่‡ชๅŠจ็กฌไปถๆฃ€ๆต‹**: ่ฟ่กŒๆ—ถ่‡ชๅŠจๆฃ€ๆต‹ๅนถ้€‰ๆ‹ฉๆœ€ไผ˜็š„็กฌไปถๅŠ ้€ŸๅŽ็ซฏ -3. **ไผ˜ๅ…ˆ็บง็ณป็ปŸ**: ๅŸบไบŽ่ดจ้‡ๅ’Œๆ€ง่ƒฝไธบไธๅŒ็ผ–็ ๅ™จๅˆ†้…ไผ˜ๅ…ˆ็บง -4. **ไฝŽๅปถ่ฟŸไผ˜ๅŒ–**: ้’ˆๅฏนๅฎžๆ—ถๆตๅช’ไฝ“ๅœบๆ™ฏ่ฟ›่กŒไบ†ไธ“้—จไผ˜ๅŒ– -5. **็ฎ€ๅŒ–็š„ๆž„ๅปบ็ณป็ปŸ**: ๆ— ้œ€ๅค–้ƒจ SDK๏ผŒไป…ไพ่ต–็ณป็ปŸ FFmpeg -6. **Windows/Linux ่ทจๅนณๅฐ**: ๆ”ฏๆŒ x86_64ใ€ARM64ใ€ARMv7 ๆžถๆž„ diff --git a/docs/report/hwcodec/01-api-reference.md b/docs/report/hwcodec/01-api-reference.md deleted file mode 100644 index 742f4128..00000000 --- a/docs/report/hwcodec/01-api-reference.md +++ /dev/null @@ -1,481 +0,0 @@ -# hwcodec ็ผ–่งฃ็ ๅ™จ API ่ฏฆ่งฃ - -## 1. ็ผ–็ ๅ™จ API - -### 1.1 ็ผ–็ ๅ™จๅˆๅง‹ๅŒ– - -#### EncodeContext ๅ‚ๆ•ฐ - -```rust -pub struct EncodeContext { - pub name: String, // ็ผ–็ ๅ™จๅ็งฐ - pub mc_name: Option, // MediaCodec ๅ็งฐ (ไฟ็•™ๅญ—ๆฎต) - pub width: i32, // ่ง†้ข‘ๅฎฝๅบฆ (ๅฟ…้กปไธบๅถๆ•ฐ) - pub height: i32, // ่ง†้ข‘้ซ˜ๅบฆ (ๅฟ…้กปไธบๅถๆ•ฐ) - pub pixfmt: AVPixelFormat, // ๅƒ็ด ๆ ผๅผ - pub align: i32, // ๅ†…ๅญ˜ๅฏน้ฝ (้€šๅธธไธบ 0 ๆˆ– 32) - pub fps: i32, // ๅธง็އ - pub gop: i32, // GOP ๅคงๅฐ (ๅ…ณ้”ฎๅธง้—ด้š”) - pub rc: RateControl, // ็ ็އๆŽงๅˆถๆจกๅผ - pub quality: Quality, // ็ผ–็ ่ดจ้‡ - pub kbs: i32, // ็›ฎๆ ‡็ ็އ (kbps) - pub q: i32, // ้‡ๅŒ–ๅ‚ๆ•ฐ (CQ ๆจกๅผ) - pub thread_count: i32, // ็ผ–็ ็บฟ็จ‹ๆ•ฐ -} -``` - -#### ๅ‚ๆ•ฐ่ฏดๆ˜Ž - -| ๅ‚ๆ•ฐ | ็ฑปๅž‹ | ่ฏดๆ˜Ž | ๆŽจ่ๅ€ผ | -|------|------|------|--------| -| `name` | String | FFmpeg ็ผ–็ ๅ™จๅ็งฐ | ่งไธ‹่กจ | -| `width` | i32 | ่ง†้ข‘ๅฎฝๅบฆ | 1920 | -| `height` | i32 | ่ง†้ข‘้ซ˜ๅบฆ | 1080 | -| `pixfmt` | AVPixelFormat | ๅƒ็ด ๆ ผๅผ | NV12 / YUV420P | -| `align` | i32 | ๅ†…ๅญ˜ๅฏน้ฝ | 0 (่‡ชๅŠจ) | -| `fps` | i32 | ๅธง็އ | 30 | -| `gop` | i32 | GOP ๅคงๅฐ | 30 (1็ง’) | -| `rc` | RateControl | ็ ็އๆŽงๅˆถ | CBR / VBR | -| `quality` | Quality | ่ดจ้‡็บงๅˆซ | Medium | -| `kbs` | i32 | ็ ็އ (kbps) | 2000-8000 | -| `thread_count` | i32 | ็บฟ็จ‹ๆ•ฐ | 4 | - -#### ็ผ–็ ๅ™จๅ็งฐๅฏน็…ง่กจ - -| ๅ็งฐ | ๆ ผๅผ | ๅŠ ้€Ÿ | ๅนณๅฐ | -|------|------|------|------| -| `h264_nvenc` | H.264 | NVIDIA GPU | Windows/Linux | -| `hevc_nvenc` | H.265 | NVIDIA GPU | Windows/Linux | -| `h264_amf` | H.264 | AMD GPU | Windows/Linux | -| `hevc_amf` | H.265 | AMD GPU | Windows/Linux | -| `h264_qsv` | H.264 | Intel QSV | Windows | -| `hevc_qsv` | H.265 | Intel QSV | Windows | -| `h264_vaapi` | H.264 | VAAPI | Linux | -| `hevc_vaapi` | H.265 | VAAPI | Linux | -| `vp8_vaapi` | VP8 | VAAPI | Linux | -| `vp9_vaapi` | VP9 | VAAPI | Linux | -| `h264_rkmpp` | H.264 | Rockchip MPP | Linux | -| `hevc_rkmpp` | H.265 | Rockchip MPP | Linux | -| `h264_v4l2m2m` | H.264 | V4L2 M2M | Linux | -| `hevc_v4l2m2m` | H.265 | V4L2 M2M | Linux | -| `h264` | H.264 | ่ฝฏไปถ (x264) | ๅ…จๅนณๅฐ | -| `hevc` | H.265 | ่ฝฏไปถ (x265) | ๅ…จๅนณๅฐ | -| `libvpx` | VP8 | ่ฝฏไปถ | ๅ…จๅนณๅฐ | -| `libvpx-vp9` | VP9 | ่ฝฏไปถ | ๅ…จๅนณๅฐ | -| `mjpeg` | MJPEG | ่ฝฏไปถ | ๅ…จๅนณๅฐ | - -### 1.2 ๅˆ›ๅปบ็ผ–็ ๅ™จ - -```rust -use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; -use hwcodec::ffmpeg::{AVPixelFormat}; -use hwcodec::common::{RateControl, Quality}; - -let ctx = EncodeContext { - name: "h264_vaapi".to_string(), - mc_name: None, - width: 1920, - height: 1080, - pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, - align: 0, - fps: 30, - gop: 30, - rc: RateControl::RC_CBR, - quality: Quality::Quality_Medium, - kbs: 4000, - q: 0, - thread_count: 4, -}; - -let encoder = Encoder::new(ctx)?; -println!("Linesize: {:?}", encoder.linesize); -println!("Offset: {:?}", encoder.offset); -println!("Buffer length: {}", encoder.length); -``` - -### 1.3 ็ผ–็ ๅธง - -```rust -// ๅ‡†ๅค‡ YUV ๆ•ฐๆฎ -let yuv_data: Vec = prepare_yuv_frame(); - -// ็ผ–็  -let pts_ms: i64 = 0; // ๆ—ถ้—ดๆˆณ (ๆฏซ็ง’) -match encoder.encode(&yuv_data, pts_ms) { - Ok(frames) => { - for frame in frames.iter() { - println!("Encoded: {} bytes, pts={}, key={}", - frame.data.len(), frame.pts, frame.key); - // ๅ‘้€ frame.data - } - } - Err(code) => { - eprintln!("Encode error: {}", code); - } -} -``` - -### 1.4 ๅŠจๆ€่ฐƒๆ•ด็ ็އ - -```rust -// ๅŠจๆ€่ฐƒๆ•ดๅˆฐ 6000 kbps -encoder.set_bitrate(6000)?; -``` - -### 1.5 ่ฏทๆฑ‚ๅ…ณ้”ฎๅธง - -```rust -// ไธ‹ไธ€ๅธงๅผบๅˆถ็ผ–็ ไธบ IDR ๅธง -encoder.request_keyframe(); -``` - -### 1.6 ๆฃ€ๆต‹ๅฏ็”จ็ผ–็ ๅ™จ - -```rust -use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; -use hwcodec::ffmpeg_ram::CodecInfo; - -let ctx = EncodeContext { - name: String::new(), - mc_name: None, - width: 1920, - height: 1080, - pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, - align: 0, - fps: 30, - gop: 30, - rc: RateControl::RC_DEFAULT, - quality: Quality::Quality_Default, - kbs: 4000, - q: 0, - thread_count: 4, -}; - -let available_encoders = Encoder::available_encoders(ctx, None); -for encoder in available_encoders { - println!("Available: {} (format: {:?}, priority: {})", - encoder.name, encoder.format, encoder.priority); -} -``` - -## 2. ่งฃ็ ๅ™จ API - -### 2.1 IP-KVM ไธ“็”จ่ฎพ่ฎก - -ๅœจ One-KVM IP-KVM ๅœบๆ™ฏไธญ๏ผŒ่งฃ็ ๅ™จไป…ๆ”ฏๆŒ MJPEG ่ฝฏไปถ่งฃ็ ใ€‚่ฟ™ๆ˜ฏๅ› ไธบ่ง†้ข‘้‡‡้›†ๅก่พ“ๅ‡บ็š„ๆ ผๅผๆ˜ฏ MJPEG๏ผŒไธ้œ€่ฆๅ…ถไป–ๆ ผๅผ็š„็กฌไปถ่งฃ็ ๆ”ฏๆŒใ€‚ - -### 2.2 ่งฃ็ ๅ™จๅˆๅง‹ๅŒ– - -#### DecodeContext ๅ‚ๆ•ฐ - -```rust -pub struct DecodeContext { - pub name: String, // ่งฃ็ ๅ™จๅ็งฐ ("mjpeg") - pub device_type: AVHWDeviceType, // ็กฌไปถ่ฎพๅค‡็ฑปๅž‹ (NONE) - pub thread_count: i32, // ่งฃ็ ็บฟ็จ‹ๆ•ฐ -} -``` - -### 2.3 ๅˆ›ๅปบ่งฃ็ ๅ™จ - -```rust -use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext}; -use hwcodec::ffmpeg::AVHWDeviceType; - -let ctx = DecodeContext { - name: "mjpeg".to_string(), - device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE, - thread_count: 4, -}; - -let decoder = Decoder::new(ctx)?; -``` - -### 2.4 ่งฃ็ ๅธง - -```rust -// ่พ“ๅ…ฅ MJPEG ็ผ–็ ๆ•ฐๆฎ -let mjpeg_data: Vec = receive_mjpeg_frame(); - -match decoder.decode(&mjpeg_data) { - Ok(frames) => { - for frame in frames.iter() { - println!("Decoded: {}x{}, format={:?}, key={}", - frame.width, frame.height, frame.pixfmt, frame.key); - - // ่ฎฟ้—ฎ YUV ๆ•ฐๆฎ - let y_plane = &frame.data[0]; - let u_plane = &frame.data[1]; - let v_plane = &frame.data[2]; - } - } - Err(code) => { - eprintln!("Decode error: {}", code); - } -} -``` - -### 2.5 DecodeFrame ็ป“ๆž„ไฝ“ - -```rust -pub struct DecodeFrame { - pub pixfmt: AVPixelFormat, // ่พ“ๅ‡บๅƒ็ด ๆ ผๅผ - pub width: i32, // ๅธงๅฎฝๅบฆ - pub height: i32, // ๅธง้ซ˜ๅบฆ - pub data: Vec>, // ๅนณ้ขๆ•ฐๆฎ [Y, U, V] ๆˆ– [Y, UV] - pub linesize: Vec, // ๆฏไธชๅนณ้ข็š„่กŒๅญ—่Š‚ๆ•ฐ - pub key: bool, // ๆ˜ฏๅฆไธบๅ…ณ้”ฎๅธง -} -``` - -#### ๅƒ็ด ๆ ผๅผไธŽๅนณ้ขๅธƒๅฑ€ - -| ๅƒ็ด ๆ ผๅผ | ๅนณ้ขๆ•ฐ | data[0] | data[1] | data[2] | -|----------|--------|---------|---------|---------| -| `YUV420P` | 3 | Y | U | V | -| `YUVJ420P` | 3 | Y | U | V | -| `YUV422P` | 3 | Y | U | V | -| `NV12` | 2 | Y | UV (ไบค้”™) | - | -| `NV21` | 2 | Y | VU (ไบค้”™) | - | - -### 2.6 ่Žทๅ–ๅฏ็”จ่งฃ็ ๅ™จ - -```rust -use hwcodec::ffmpeg_ram::decode::Decoder; - -let available_decoders = Decoder::available_decoders(); -for decoder in available_decoders { - println!("Available: {} (format: {:?}, hwdevice: {:?})", - decoder.name, decoder.format, decoder.hwdevice); -} - -// ่พ“ๅ‡บ: -// Available: mjpeg (format: MJPEG, hwdevice: AV_HWDEVICE_TYPE_NONE) -``` - -## 3. ็ ็އๆŽงๅˆถๆจกๅผ - -### 3.1 RateControl ๆžšไธพ - -```rust -pub enum RateControl { - RC_DEFAULT, // ไฝฟ็”จ็ผ–็ ๅ™จ้ป˜่ฎค - RC_CBR, // ๆ’ๅฎš็ ็އ - RC_VBR, // ๅฏๅ˜็ ็އ - RC_CQ, // ๆ’ๅฎš่ดจ้‡ (้œ€่ฎพ็ฝฎ q ๅ‚ๆ•ฐ) -} -``` - -### 3.2 ๆจกๅผ่ฏดๆ˜Ž - -| ๆจกๅผ | ่ฏดๆ˜Ž | ้€‚็”จๅœบๆ™ฏ | -|------|------|----------| -| `RC_CBR` | ็ ็އๆ’ๅฎš๏ผŒ่ดจ้‡้šๅœบๆ™ฏๅ˜ๅŒ– | ็ฝ‘็ปœๅธฆๅฎฝๅ—้™ | -| `RC_VBR` | ่ดจ้‡ไผ˜ๅ…ˆ๏ผŒ็ ็އๆณขๅŠจ | ๆœฌๅœฐๅญ˜ๅ‚จ | -| `RC_CQ` | ๆ’ๅฎš่ดจ้‡๏ผŒ็ ็އๆณขๅŠจๅคง | ่ดจ้‡ๆ•ๆ„Ÿๅœบๆ™ฏ | - -### 3.3 ๅ„็ผ–็ ๅ™จๆ”ฏๆŒๆƒ…ๅ†ต - -| ็ผ–็ ๅ™จ | CBR | VBR | CQ | -|--------|-----|-----|-----| -| nvenc | โœ“ | โœ“ | โœ“ | -| amf | โœ“ | โœ“ (ไฝŽๅปถ่ฟŸ) | โœ— | -| qsv | โœ“ | โœ“ | โœ— | -| vaapi | โœ“ | โœ“ | โœ— | - -## 4. ่ดจ้‡็ญ‰็บง - -### 4.1 Quality ๆžšไธพ - -```rust -pub enum Quality { - Quality_Default, // ไฝฟ็”จ็ผ–็ ๅ™จ้ป˜่ฎค - Quality_High, // ้ซ˜่ดจ้‡ (ๆ…ข้€Ÿ) - Quality_Medium, // ไธญ็ญ‰่ดจ้‡ (ๅนณ่กก) - Quality_Low, // ไฝŽ่ดจ้‡ (ๅฟซ้€Ÿ) -} -``` - -### 4.2 ็ผ–็ ๅ™จ้ข„่ฎพๆ˜ ๅฐ„ - -| ่ดจ้‡ | nvenc | amf | qsv | -|------|-------|-----|-----| -| High | - | quality | veryslow | -| Medium | p4 | balanced | medium | -| Low | p1 | speed | veryfast | - -## 5. ้”™่ฏฏๅค„็† - -### 5.1 ้”™่ฏฏ็  - -| ้”™่ฏฏ็  | ๅธธ้‡ | ่ฏดๆ˜Ž | -|--------|------|------| -| 0 | `HWCODEC_SUCCESS` | ๆˆๅŠŸ | -| -1 | `HWCODEC_ERR_COMMON` | ้€š็”จ้”™่ฏฏ | -| -2 | `HWCODEC_ERR_HEVC_COULD_NOT_FIND_POC` | HEVC ่งฃ็ ๅ‚่€ƒๅธงไธขๅคฑ | - -### 5.2 ๅธธ่ง้”™่ฏฏๅค„็† - -```rust -match encoder.encode(&yuv_data, pts) { - Ok(frames) => { - // ๅค„็†็ผ–็ ๅธง - } - Err(-1) => { - eprintln!("็ผ–็ ๅคฑ่ดฅ๏ผŒๅฏ่ƒฝๆ˜ฏ่พ“ๅ…ฅๆ•ฐๆฎๆ ผๅผ้”™่ฏฏ"); - } - Err(code) => { - eprintln!("ๆœช็Ÿฅ้”™่ฏฏ: {}", code); - } -} -``` - -## 6. ๆœ€ไฝณๅฎž่ทต - -### 6.1 ็ผ–็ ๅ™จ้€‰ๆ‹ฉ็ญ–็•ฅ - -```rust -fn select_best_encoder( - width: i32, - height: i32, - format: DataFormat -) -> Option { - let ctx = EncodeContext { - width, - height, - pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, - // ... ๅ…ถไป–ๅ‚ๆ•ฐ - }; - - let encoders = Encoder::available_encoders(ctx, None); - - // ๆŒ‰ไผ˜ๅ…ˆ็บงๆŽ’ๅบ๏ผŒ้€‰ๆ‹ฉๆœ€ไฝณ - encoders.into_iter() - .filter(|e| e.format == format) - .min_by_key(|e| e.priority) - .map(|e| e.name) -} -``` - -### 6.2 ๅธงๅ†…ๅญ˜ๅธƒๅฑ€ - -```rust -// ่Žทๅ– NV12 ๅธงๅธƒๅฑ€ไฟกๆฏ -let (linesize, offset, length) = ffmpeg_linesize_offset_length( - AVPixelFormat::AV_PIX_FMT_NV12, - 1920, - 1080, - 0, // align -)?; - -// ๅˆ†้…็ผ“ๅ†ฒๅŒบ -let mut buffer = vec![0u8; length as usize]; - -// ๅกซๅ…… Y ๅนณ้ข: buffer[0..offset[0]] -// ๅกซๅ…… UV ๅนณ้ข: buffer[offset[0]..length] -``` - -### 6.3 ๅ…ณ้”ฎๅธงๆŽงๅˆถ - -```rust -let mut frame_count = 0; - -loop { - // ๆฏ 30 ๅธงๅผบๅˆถไธ€ไธชๅ…ณ้”ฎๅธง - if frame_count % 30 == 0 { - encoder.request_keyframe(); - } - - encoder.encode(&yuv_data, pts)?; - frame_count += 1; -} -``` - -### 6.4 ็บฟ็จ‹ๅฎ‰ๅ…จ - -```rust -// Decoder ๅฎž็Žฐไบ† Send + Sync -unsafe impl Send for Decoder {} -unsafe impl Sync for Decoder {} - -// ๅฏไปฅๅฎ‰ๅ…จๅœฐๅœจๅคš็บฟ็จ‹้—ดไผ ้€’ -let decoder = Arc::new(Mutex::new(Decoder::new(ctx)?)); -``` - -## 7. IP-KVM ๅ…ธๅž‹ไฝฟ็”จๅœบๆ™ฏ - -### 7.1 ่ง†้ข‘้‡‡้›†ๅ’Œ่ฝฌ็ ๆต็จ‹ - -``` -USB ้‡‡้›†ๅก (MJPEG) - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MJPEG Decoder โ”‚ โ—„โ”€โ”€ Decoder::new("mjpeg") -โ”‚ (่ฝฏไปถ่งฃ็ ) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ YUV420P - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ H264 Encoder โ”‚ โ—„โ”€โ”€ Encoder::new("h264_vaapi") -โ”‚ (็กฌไปถๅŠ ้€Ÿ) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ H264 NAL - โ–ผ - WebRTC ไผ ่พ“ -``` - -### 7.2 ๅฎŒๆ•ด็คบไพ‹ - -```rust -use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext}; -use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; -use hwcodec::ffmpeg::AVHWDeviceType; - -// ๅˆ›ๅปบ MJPEG ่งฃ็ ๅ™จ -let decode_ctx = DecodeContext { - name: "mjpeg".to_string(), - device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE, - thread_count: 4, -}; -let mut decoder = Decoder::new(decode_ctx)?; - -// ๆฃ€ๆต‹ๅนถ้€‰ๆ‹ฉๆœ€ไฝณ็ผ–็ ๅ™จ -let encode_ctx = EncodeContext { - name: String::new(), - width: 1920, - height: 1080, - // ... -}; -let available = Encoder::available_encoders(encode_ctx.clone(), None); -let best_h264 = available.iter() - .filter(|e| e.format == DataFormat::H264) - .min_by_key(|e| e.priority) - .expect("No H264 encoder available"); - -// ไฝฟ็”จๆœ€ไฝณ็ผ–็ ๅ™จๅˆ›ๅปบๅฎžไพ‹ -let encode_ctx = EncodeContext { - name: best_h264.name.clone(), - ..encode_ctx -}; -let mut encoder = Encoder::new(encode_ctx)?; - -// ๅค„็†ๅพช็Žฏ -loop { - let mjpeg_frame = capture_frame(); - - // ่งฃ็  MJPEG -> YUV - let decoded = decoder.decode(&mjpeg_frame)?; - - // ็ผ–็  YUV -> H264 - for frame in decoded { - let yuv_data = frame.data.concat(); - let encoded = encoder.encode(&yuv_data, pts)?; - - // ๅ‘้€็ผ–็ ๆ•ฐๆฎ - for packet in encoded { - send_to_webrtc(packet.data); - } - } -} -``` diff --git a/docs/report/hwcodec/02-hardware-acceleration.md b/docs/report/hwcodec/02-hardware-acceleration.md deleted file mode 100644 index 6e5bd39f..00000000 --- a/docs/report/hwcodec/02-hardware-acceleration.md +++ /dev/null @@ -1,561 +0,0 @@ -# hwcodec ็กฌไปถๅŠ ้€Ÿ่ฏฆ่งฃ - -## 1. ็กฌไปถๅŠ ้€Ÿๆžถๆž„ - -### 1.1 ๆ•ดไฝ“ๆต็จ‹ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ๅบ”็”จๅฑ‚ (Rust) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ -โ”‚ โ”‚ Encoder::available_encoders() โ†’ ่‡ชๅŠจๆฃ€ๆต‹ๅฏ็”จ็กฌไปถ็ผ–็ ๅ™จ โ”‚โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ็กฌไปถๆฃ€ๆต‹ๅฑ‚ (C++) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ -โ”‚ โ”‚linux_ โ”‚ โ”‚linux_ โ”‚ โ”‚linux_ โ”‚ โ”‚linux_support_ โ”‚โ”‚ -โ”‚ โ”‚support_nvโ”‚ โ”‚support_ โ”‚ โ”‚support_ โ”‚ โ”‚rkmpp/v4l2m2m โ”‚โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚amd โ”‚ โ”‚intel โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ CUDA/ โ”‚ โ”‚ AMF โ”‚ โ”‚ VPL/MFX โ”‚ โ”‚ ่ฎพๅค‡่Š‚็‚นๆฃ€ๆต‹ โ”‚ -โ”‚ NVENC โ”‚ โ”‚ Runtime โ”‚ โ”‚ Library โ”‚ โ”‚ /dev/mpp_service โ”‚ -โ”‚ ๅŠจๆ€ๅบ“ โ”‚ โ”‚ ๅŠจๆ€ๅบ“ โ”‚ โ”‚ ๅŠจๆ€ๅบ“ โ”‚ โ”‚ /dev/video* โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 1.2 ็ผ–็ ๅ™จๆต‹่ฏ•้ชŒ่ฏ - -ๆฏไธชๆฃ€ๆต‹ๅˆฐ็š„็กฌไปถ็ผ–็ ๅ™จ้ƒฝไผš่ฟ›่กŒๅฎž้™…็ผ–็ ๆต‹่ฏ•๏ผš - -```rust -// libs/hwcodec/src/ffmpeg_ram/encode.rs - -// ็”Ÿๆˆๆต‹่ฏ•็”จ YUV ๆ•ฐๆฎ -let yuv = Encoder::dummy_yuv(ctx.clone())?; - -// ๅฐ่ฏ•ๅˆ›ๅปบ็ผ–็ ๅ™จๅนถ็ผ–็ ๆต‹่ฏ•ๅธง -match Encoder::new(c) { - Ok(mut encoder) => { - let start = std::time::Instant::now(); - match encoder.encode(&yuv, 0) { - Ok(frames) => { - let elapsed = start.elapsed().as_millis(); - // ้ชŒ่ฏ: ๅฟ…้กปไบง็”Ÿ 1 ๅธงไธ”ไธบๅ…ณ้”ฎๅธง๏ผŒไธ”ๅœจ่ถ…ๆ—ถๆ—ถ้—ดๅ†…ๅฎŒๆˆ - if frames.len() == 1 && frames[0].key == 1 - && elapsed < TEST_TIMEOUT_MS { - res.push(codec); - } - } - Err(_) => { /* ็ผ–็ ๅคฑ่ดฅ๏ผŒ่ทณ่ฟ‡ */ } - } - } - Err(_) => { /* ๅˆ›ๅปบๅคฑ่ดฅ๏ผŒ่ทณ่ฟ‡ */ } -} -``` - -## 2. NVIDIA NVENC/NVDEC - -### 2.1 ๆฃ€ๆต‹ๆœบๅˆถ (Linux) - -ไฝฟ็”จ็ฎ€ๅŒ–็š„ๅŠจๆ€ๅบ“ๆฃ€ๆต‹ๆ–นๆณ•๏ผŒๆ— ้œ€ CUDA SDK ไพ่ต–๏ผš - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -int linux_support_nv() { - // ๆฃ€ๆต‹ CUDA ่ฟ่กŒๆ—ถๅบ“ - void *handle = dlopen("libcuda.so.1", RTLD_LAZY); - if (!handle) { - handle = dlopen("libcuda.so", RTLD_LAZY); - } - if (!handle) { - LOG_TRACE("NVIDIA: libcuda.so not found"); - return -1; - } - dlclose(handle); - - // ๆฃ€ๆต‹ NVENC ็ผ–็ ๅบ“ - handle = dlopen("libnvidia-encode.so.1", RTLD_LAZY); - if (!handle) { - handle = dlopen("libnvidia-encode.so", RTLD_LAZY); - } - if (!handle) { - LOG_TRACE("NVIDIA: libnvidia-encode.so not found"); - return -1; - } - dlclose(handle); - - LOG_TRACE("NVIDIA: driver support detected"); - return 0; -} -``` - -### 2.2 ็ผ–็ ้…็ฝฎ - -```cpp -// libs/hwcodec/cpp/common/util.cpp - -// NVENC ไฝŽๅปถ่ฟŸ้…็ฝฎ -if (name.find("nvenc") != std::string::npos) { - // ็ฆ็”จ็ผ–็ ๅปถ่ฟŸ - av_opt_set(priv_data, "delay", "0", 0); -} - -// GPU ้€‰ๆ‹ฉ -if (name.find("nvenc") != std::string::npos) { - av_opt_set_int(priv_data, "gpu", gpu_index, 0); -} - -// ่ดจ้‡้ข„่ฎพ -switch (quality) { - case Quality_Medium: - av_opt_set(priv_data, "preset", "p4", 0); - break; - case Quality_Low: - av_opt_set(priv_data, "preset", "p1", 0); - break; -} - -// ็ ็އๆŽงๅˆถ -av_opt_set(priv_data, "rc", "cbr", 0); // ๆˆ– "vbr" -``` - -### 2.3 ็Žฏๅขƒๅ˜้‡ - -| ๅ˜้‡ | ่ฏดๆ˜Ž | -|------|------| -| `RUSTDESK_HWCODEC_NVENC_GPU` | ๆŒ‡ๅฎšไฝฟ็”จ็š„ GPU ็ดขๅผ• (-1 = ่‡ชๅŠจ) | - -### 2.4 ไพ่ต–ๅบ“ - -- `libcuda.so` / `libcuda.so.1` - CUDA ่ฟ่กŒๆ—ถ -- `libnvidia-encode.so` / `libnvidia-encode.so.1` - NVENC ็ผ–็ ๅ™จ - -## 3. AMD AMF - -### 3.1 ๆฃ€ๆต‹ๆœบๅˆถ (Linux) - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -int linux_support_amd() { -#if defined(__x86_64__) || defined(__aarch64__) - #define AMF_DLL_NAMEA "libamfrt64.so.1" -#else - #define AMF_DLL_NAMEA "libamfrt32.so.1" -#endif - - void *handle = dlopen(AMF_DLL_NAMEA, RTLD_LAZY); - if (!handle) { - return -1; // AMF ไธๅฏ็”จ - } - dlclose(handle); - return 0; // AMF ๅฏ็”จ -} -``` - -### 3.2 ็ผ–็ ้…็ฝฎ - -```cpp -// libs/hwcodec/cpp/common/util.cpp - -// AMF ไฝŽๅปถ่ฟŸ้…็ฝฎ -if (name.find("amf") != std::string::npos) { - av_opt_set(priv_data, "query_timeout", "1000", 0); -} - -// ่ดจ้‡้ข„่ฎพ -switch (quality) { - case Quality_High: - av_opt_set(priv_data, "quality", "quality", 0); - break; - case Quality_Medium: - av_opt_set(priv_data, "quality", "balanced", 0); - break; - case Quality_Low: - av_opt_set(priv_data, "quality", "speed", 0); - break; -} - -// ็ ็އๆŽงๅˆถ -av_opt_set(priv_data, "rc", "cbr", 0); // ๆ’ๅฎš็ ็އ -av_opt_set(priv_data, "rc", "vbr_latency", 0); // ไฝŽๅปถ่ฟŸ VBR -``` - -### 3.3 ไพ่ต–ๅบ“ - -- `libamfrt64.so.1` (64ไฝ) ๆˆ– `libamfrt32.so.1` (32ไฝ) - -## 4. Intel QSV/MFX - -### 4.1 ๆฃ€ๆต‹ๆœบๅˆถ (Linux) - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -int linux_support_intel() { - const char *libs[] = { - "libvpl.so", // oneVPL (ๆ–ฐ็‰ˆ) - "libmfx.so", // Media SDK - "libmfx-gen.so.1.2", // ๆ–ฐ้ฉฑๅŠจ - "libmfxhw64.so.1" // ๆ—ง็‰ˆ้ฉฑๅŠจ - }; - - for (size_t i = 0; i < sizeof(libs) / sizeof(libs[0]); i++) { - void *handle = dlopen(libs[i], RTLD_LAZY); - if (handle) { - dlclose(handle); - return 0; // ๆ‰พๅˆฐๅฏ็”จๅบ“ - } - } - return -1; // Intel MFX ไธๅฏ็”จ -} -``` - -### 4.2 ็ผ–็ ้…็ฝฎ - -```cpp -// libs/hwcodec/cpp/common/util.cpp - -// QSV ไฝŽๅปถ่ฟŸ้…็ฝฎ -if (name.find("qsv") != std::string::npos) { - av_opt_set(priv_data, "async_depth", "1", 0); -} - -// QSV ็‰นๆฎŠ็ ็އ้…็ฝฎ -if (name.find("qsv") != std::string::npos) { - c->rc_max_rate = c->bit_rate; - c->bit_rate--; // ๅฎž็Žฐ CBR ๆ•ˆๆžœ -} - -// ่ดจ้‡้ข„่ฎพ -switch (quality) { - case Quality_High: - av_opt_set(priv_data, "preset", "veryslow", 0); - break; - case Quality_Medium: - av_opt_set(priv_data, "preset", "medium", 0); - break; - case Quality_Low: - av_opt_set(priv_data, "preset", "veryfast", 0); - break; -} - -// ไธฅๆ ผๆ ‡ๅ‡†ๅ…ผๅฎนๆ€ง (็”จไบŽๆŸไบ›็‰นๆฎŠ่ฎพ็ฝฎ) -c->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; -``` - -### 4.3 ้™ๅˆถ - -- QSV ไธๆ”ฏๆŒ `YUV420P` ๅƒ็ด ๆ ผๅผ๏ผŒๅฟ…้กปไฝฟ็”จ `NV12` -- ๅœจ One-KVM ็ฎ€ๅŒ–็‰ˆไธญไป… Windows ๅนณๅฐๅฎŒๅ…จๆ”ฏๆŒ - -## 5. VAAPI (Linux) - -### 5.1 ๅทฅไฝœๅŽŸ็† - -VAAPI (Video Acceleration API) ๆ˜ฏ Linux ไธŠ็š„้€š็”จ็กฌไปถ่ง†้ข‘ๅŠ ้€ŸๆŽฅๅฃ๏ผš - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Application โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ FFmpeg libavcodec โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ VAAPI (libva) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Intel i965 โ”‚ Intel iHD โ”‚ AMD radeonsi โ”‚ NVIDIA VDPAU โ”‚ -โ”‚ (Gen8-) โ”‚ (Gen9+) โ”‚ โ”‚ (via wrapper) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Kernel DRM Driver โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ i915 โ”‚ amdgpu โ”‚ nvidia โ”‚ ... โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 5.2 ็ผ–็ ้…็ฝฎ - -```cpp -// libs/hwcodec/cpp/common/util.cpp - -// VAAPI ไฝŽๅปถ่ฟŸ้…็ฝฎ -if (name.find("vaapi") != std::string::npos) { - av_opt_set(priv_data, "async_depth", "1", 0); -} -``` - -### 5.3 ็กฌไปถไธŠไธ‹ๆ–‡ๅˆๅง‹ๅŒ– - -```cpp -// libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_encode.cpp - -// ๆฃ€ๆต‹ VAAPI ็ผ–็ ๅ™จ -if (name_.find("vaapi") != std::string::npos) { - hw_device_type_ = AV_HWDEVICE_TYPE_VAAPI; - hw_pixfmt_ = AV_PIX_FMT_VAAPI; -} - -// ๅˆ›ๅปบ็กฌไปถ่ฎพๅค‡ไธŠไธ‹ๆ–‡ -ret = av_hwdevice_ctx_create(&hw_device_ctx_, hw_device_type_, - NULL, // ไฝฟ็”จ้ป˜่ฎค่ฎพๅค‡ - NULL, 0); - -// ่ฎพ็ฝฎ็กฌไปถๅธงไธŠไธ‹ๆ–‡ -set_hwframe_ctx(); - -// ๅˆ†้…็กฌไปถๅธง -hw_frame_ = av_frame_alloc(); -av_hwframe_get_buffer(c_->hw_frames_ctx, hw_frame_, 0); -``` - -### 5.4 ็ผ–็ ๆต็จ‹ - -``` -่พ“ๅ…ฅ YUV (CPU ๅ†…ๅญ˜) - โ”‚ - โ–ผ -av_hwframe_transfer_data(hw_frame_, frame_, 0) // CPU โ†’ GPU - โ”‚ - โ–ผ -avcodec_send_frame(c_, hw_frame_) // ๅ‘้€ GPU ๅธง - โ”‚ - โ–ผ -avcodec_receive_packet(c_, pkt_) // ่Žทๅ–็ผ–็ ๆ•ฐๆฎ - โ”‚ - โ–ผ -็ผ–็ ๆ•ฐๆฎ (CPU ๅ†…ๅญ˜) -``` - -### 5.5 ไพ่ต–ๅบ“ - -- `libva.so` - VAAPI ๆ ธๅฟƒๅบ“ -- `libva-drm.so` - DRM ๅŽ็ซฏ -- `libva-x11.so` - X11 ๅŽ็ซฏ (ๅฏ้€‰) - -## 6. Rockchip MPP - -### 6.1 ๆฃ€ๆต‹ๆœบๅˆถ - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -int linux_support_rkmpp() { - // ๆฃ€ๆต‹ MPP ๆœๅŠก่ฎพๅค‡ - if (access("/dev/mpp_service", F_OK) == 0) { - LOG_TRACE("RKMPP: Found /dev/mpp_service"); - return 0; // MPP ๅฏ็”จ - } - // ๅค‡็”จ: ๆฃ€ๆต‹ RGA ่ฎพๅค‡ - if (access("/dev/rga", F_OK) == 0) { - LOG_TRACE("RKMPP: Found /dev/rga"); - return 0; // MPP ๅฏ่ƒฝๅฏ็”จ - } - LOG_TRACE("RKMPP: No Rockchip MPP device found"); - return -1; // MPP ไธๅฏ็”จ -} -``` - -### 6.2 ๆ”ฏๆŒ็š„็ผ–็ ๅ™จ - -| ็ผ–็ ๅ™จ | ไผ˜ๅ…ˆ็บง | ่ฏดๆ˜Ž | -|--------|--------|------| -| `h264_rkmpp` | Best (0) | H.264 ็กฌไปถ็ผ–็  | -| `hevc_rkmpp` | Best (0) | H.265 ็กฌไปถ็ผ–็  | - -### 6.3 ้€‚็”จ่ฎพๅค‡ - -- Rockchip RK3328 (Onecloud, Chainedbox) -- Rockchip RK3399/RK3588 ็ณปๅˆ— -- ๅ…ถไป– Rockchip SoC - -## 7. V4L2 M2M - -### 7.1 ๆฃ€ๆต‹ๆœบๅˆถ - -```cpp -// libs/hwcodec/cpp/common/platform/linux/linux.cpp - -int linux_support_v4l2m2m() { - const char *m2m_devices[] = { - "/dev/video10", // ๅธธ่ง M2M ็ผ–็ ่ฎพๅค‡ - "/dev/video11", // ๅธธ่ง M2M ่งฃ็ ่ฎพๅค‡ - "/dev/video0", // ๆŸไบ› SoC ไฝฟ็”จ - }; - - for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) { - if (access(m2m_devices[i], F_OK) == 0) { - int fd = open(m2m_devices[i], O_RDWR | O_NONBLOCK); - if (fd >= 0) { - close(fd); - LOG_TRACE("V4L2 M2M: Found device " + m2m_devices[i]); - return 0; // V4L2 M2M ๅฏ็”จ - } - } - } - LOG_TRACE("V4L2 M2M: No M2M device found"); - return -1; -} -``` - -### 7.2 ๆ”ฏๆŒ็š„็ผ–็ ๅ™จ - -| ็ผ–็ ๅ™จ | ไผ˜ๅ…ˆ็บง | ่ฏดๆ˜Ž | -|--------|--------|------| -| `h264_v4l2m2m` | Good (1) | H.264 V4L2 ็ผ–็  | -| `hevc_v4l2m2m` | Good (1) | H.265 V4L2 ็ผ–็  | - -### 7.3 ้€‚็”จ่ฎพๅค‡ - -- ้€š็”จ ARM SoC (Allwinner, Amlogic ็ญ‰) -- ๆ”ฏๆŒ V4L2 M2M API ็š„่ฎพๅค‡ - -## 8. ็กฌไปถๅŠ ้€Ÿไผ˜ๅ…ˆ็บง - -### 8.1 ไผ˜ๅ…ˆ็บงๅฎšไน‰ - -```rust -pub enum Priority { - Best = 0, // ไธ“็”จ็กฌไปถ็ผ–็ ๅ™จ - Good = 1, // ้€š็”จ็กฌไปถๅŠ ้€Ÿ - Normal = 2, // ๅŸบๆœฌ็กฌไปถๆ”ฏๆŒ - Soft = 3, // ่ฝฏไปถ็ผ–็  - Bad = 4, // ๆœ€ไฝŽไผ˜ๅ…ˆ็บง -} -``` - -### 8.2 ๅ„็ผ–็ ๅ™จไผ˜ๅ…ˆ็บง - -| ไผ˜ๅ…ˆ็บง | ็ผ–็ ๅ™จ | -|--------|--------| -| Best (0) | nvenc, amf, qsv, rkmpp | -| Good (1) | vaapi, v4l2m2m | -| Soft (3) | x264, x265, libvpx | - -### 8.3 ้€‰ๆ‹ฉ็ญ–็•ฅ - -```rust -// libs/hwcodec/src/ffmpeg_ram/mod.rs - -pub fn prioritized(coders: Vec) -> CodecInfos { - // ๅฏนไบŽๆฏ็งๆ ผๅผ๏ผŒ้€‰ๆ‹ฉไผ˜ๅ…ˆ็บงๆœ€้ซ˜็š„็ผ–็ ๅ™จ - for coder in coders { - match coder.format { - DataFormat::H264 => { - if h264.is_none() || h264.priority > coder.priority { - h264 = Some(coder); - } - } - // ... ๅ…ถไป–ๆ ผๅผ็ฑปไผผ - } - } -} -``` - -## 9. ๆ•…้šœๆŽ’้™ค - -### 9.1 NVIDIA - -```bash -# ๆฃ€ๆŸฅ NVIDIA ้ฉฑๅŠจ -nvidia-smi - -# ๆฃ€ๆŸฅ NVENC ๆ”ฏๆŒ -ls /dev/nvidia* - -# ๆฃ€ๆŸฅ CUDA ๅบ“ -ldconfig -p | grep cuda -ldconfig -p | grep nvidia-encode -``` - -### 9.2 AMD - -```bash -# ๆฃ€ๆŸฅ AMD ้ฉฑๅŠจ -lspci | grep AMD - -# ๆฃ€ๆŸฅ AMF ๅบ“ -ldconfig -p | grep amf -``` - -### 9.3 Intel - -```bash -# ๆฃ€ๆŸฅ Intel ้ฉฑๅŠจ -vainfo - -# ๆฃ€ๆŸฅ MFX ๅบ“ -ldconfig -p | grep mfx -ldconfig -p | grep vpl -``` - -### 9.4 VAAPI - -```bash -# ๅฎ‰่ฃ… vainfo -sudo apt install vainfo - -# ๆฃ€ๆŸฅ VAAPI ๆ”ฏๆŒ -vainfo - -# ่พ“ๅ‡บ็คบไพ‹: -# libva info: VA-API version 1.14.0 -# libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so -# vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics -# vainfo: Supported profile and entrypoints -# VAProfileH264Main : VAEntrypointVLD -# VAProfileH264Main : VAEntrypointEncSlice -# ... -``` - -### 9.5 Rockchip MPP - -```bash -# ๆฃ€ๆŸฅ MPP ่ฎพๅค‡ -ls -la /dev/mpp_service -ls -la /dev/rga - -# ๆฃ€ๆŸฅ MPP ๅบ“ -ldconfig -p | grep rockchip_mpp -``` - -### 9.6 V4L2 M2M - -```bash -# ๅˆ—ๅ‡บ V4L2 ่ฎพๅค‡ -v4l2-ctl --list-devices - -# ๆฃ€ๆŸฅ่ฎพๅค‡่ƒฝๅŠ› -v4l2-ctl -d /dev/video10 --all -``` - -## 10. ๆ€ง่ƒฝไผ˜ๅŒ–ๅปบ่ฎฎ - -### 10.1 ็ผ–็ ๅ™จ้€‰ๆ‹ฉ - -1. **ไผ˜ๅ…ˆไฝฟ็”จ็กฌไปถ็ผ–็ **: NVENC > AMF > QSV > VAAPI > V4L2 M2M > ่ฝฏไปถ -2. **ARM ่ฎพๅค‡**: ไผ˜ๅ…ˆๆฃ€ๆต‹ RKMPP๏ผŒๅ…ถๆฌก V4L2 M2M -3. **x86 ่ฎพๅค‡**: ๆ นๆฎ GPU ๅŽ‚ๅ•†่‡ชๅŠจ้€‰ๆ‹ฉ - -### 10.2 ไฝŽๅปถ่ฟŸ้…็ฝฎ - -ๆ‰€ๆœ‰็กฌไปถ็ผ–็ ๅ™จ้ƒฝๅฏ็”จไบ†ไฝŽๅปถ่ฟŸไผ˜ๅŒ–๏ผš - -| ็ผ–็ ๅ™จ | ้…็ฝฎ | -|--------|------| -| NVENC | `delay=0` | -| AMF | `query_timeout=1000` | -| QSV | `async_depth=1` | -| VAAPI | `async_depth=1` | -| libvpx | `deadline=realtime`, `cpu-used=6` | - -### 10.3 ็ ็އๆŽงๅˆถ - -- **ๅฎžๆ—ถๆต**: ๆŽจ่ CBR ๆจกๅผ๏ผŒไฟ่ฏ็จณๅฎš็ ็އ -- **GOP ๅคงๅฐ**: ๅปบ่ฎฎ 30-60 ๅธง (1-2็ง’)๏ผŒๅนณ่กกๅปถ่ฟŸๅ’ŒๅŽ‹็ผฉๆ•ˆ็އ diff --git a/docs/report/hwcodec/03-build-integration.md b/docs/report/hwcodec/03-build-integration.md deleted file mode 100644 index 9fd13a33..00000000 --- a/docs/report/hwcodec/03-build-integration.md +++ /dev/null @@ -1,477 +0,0 @@ -# hwcodec ๆž„ๅปบ็ณป็ปŸไธŽ้›†ๆˆๆŒ‡ๅ— - -## 1. ้กน็›ฎ็ป“ๆž„ - -``` -libs/hwcodec/ -โ”œโ”€โ”€ Cargo.toml # ๅŒ…้…็ฝฎ -โ”œโ”€โ”€ Cargo.lock # ไพ่ต–้”ๅฎš -โ”œโ”€โ”€ build.rs # ๆž„ๅปบ่„šๆœฌ -โ””โ”€โ”€ src/ # Rust ๆบ็  - โ”œโ”€โ”€ lib.rs # ๅบ“ๅ…ฅๅฃ - โ”œโ”€โ”€ common.rs # ๅ…ฌๅ…ฑๅฎšไน‰ - โ”œโ”€โ”€ ffmpeg.rs # FFmpeg ้›†ๆˆ - โ””โ”€โ”€ ffmpeg_ram/ # RAM ็ผ–่งฃ็  - โ”œโ”€โ”€ mod.rs - โ”œโ”€โ”€ encode.rs - โ””โ”€โ”€ decode.rs -โ””โ”€โ”€ cpp/ # C++ ๆบ็  - โ”œโ”€โ”€ common/ # ๅ…ฌๅ…ฑไปฃ็  - โ”‚ โ”œโ”€โ”€ log.cpp - โ”‚ โ”œโ”€โ”€ log.h - โ”‚ โ”œโ”€โ”€ util.cpp - โ”‚ โ”œโ”€โ”€ util.h - โ”‚ โ”œโ”€โ”€ callback.h - โ”‚ โ”œโ”€โ”€ common.h - โ”‚ โ””โ”€โ”€ platform/ - โ”‚ โ”œโ”€โ”€ linux/ - โ”‚ โ”‚ โ”œโ”€โ”€ linux.cpp - โ”‚ โ”‚ โ””โ”€โ”€ linux.h - โ”‚ โ””โ”€โ”€ win/ - โ”‚ โ”œโ”€โ”€ win.cpp - โ”‚ โ””โ”€โ”€ win.h - โ”œโ”€โ”€ ffmpeg_ram/ # FFmpeg RAM ๅฎž็Žฐ - โ”‚ โ”œโ”€โ”€ ffmpeg_ram_encode.cpp - โ”‚ โ”œโ”€โ”€ ffmpeg_ram_decode.cpp - โ”‚ โ””โ”€โ”€ ffmpeg_ram_ffi.h - โ””โ”€โ”€ yuv/ # YUV ๅค„็† - โ””โ”€โ”€ yuv.cpp -``` - -## 2. Cargo ้…็ฝฎ - -### 2.1 Cargo.toml - -```toml -[package] -name = "hwcodec" -version = "0.8.0" -edition = "2021" -description = "Hardware video codec for IP-KVM (Windows/Linux)" - -[features] -default = [] - -[dependencies] -log = "0.4" # ๆ—ฅๅฟ— -serde_derive = "1.0" # ๅบๅˆ—ๅŒ–ๆดพ็”Ÿๅฎ -serde = "1.0" # ๅบๅˆ—ๅŒ– -serde_json = "1.0" # JSON ๅบๅˆ—ๅŒ– - -[build-dependencies] -cc = "1.0" # C++ ็ผ–่ฏ‘ -bindgen = "0.59" # FFI ็ป‘ๅฎš็”Ÿๆˆ - -[dev-dependencies] -env_logger = "0.10" # ๆ—ฅๅฟ—่พ“ๅ‡บ -``` - -### 2.2 ไธŽๅŽŸ็‰ˆ็š„ๅŒบๅˆซ - -| ็‰นๆ€ง | ๅŽŸ็‰ˆ (RustDesk) | ็ฎ€ๅŒ–็‰ˆ (One-KVM) | -|------|-----------------|------------------| -| `vram` feature | โœ“ | โœ— (ๅทฒ็งป้™ค) | -| ๅค–้ƒจ SDK | ้œ€่ฆ | ไธ้œ€่ฆ | -| ็‰ˆๆœฌๅท | 0.7.1 | 0.8.0 | -| ็›ฎๆ ‡ๅนณๅฐ | Windows/Linux/macOS/Android | Windows/Linux | - -### 2.3 ไฝฟ็”จๆ–นๅผ - -```toml -# ๅœจ One-KVM ้กน็›ฎไธญไฝฟ็”จ -[dependencies] -hwcodec = { path = "libs/hwcodec" } -``` - -## 3. ๆž„ๅปบ่„šๆœฌ่ฏฆ่งฃ (build.rs) - -### 3.1 ไธปๅ…ฅๅฃ - -```rust -fn main() { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let mut builder = Build::new(); - - // 1. ๆž„ๅปบๅ…ฌๅ…ฑๆจกๅ— - build_common(&mut builder); - - // 2. ๆž„ๅปบ FFmpeg ็›ธๅ…ณๆจกๅ— - ffmpeg::build_ffmpeg(&mut builder); - - // 3. ็ผ–่ฏ‘็”Ÿๆˆ้™ๆ€ๅบ“ - builder.static_crt(true).compile("hwcodec"); -} -``` - -### 3.2 ๅ…ฌๅ…ฑๆจกๅ—ๆž„ๅปบ - -```rust -fn build_common(builder: &mut Build) { - let common_dir = manifest_dir.join("cpp").join("common"); - - // ็”Ÿๆˆ FFI ็ป‘ๅฎš - bindgen::builder() - .header(common_dir.join("common.h")) - .header(common_dir.join("callback.h")) - .rustified_enum("*") - .generate() - .write_to_file(OUT_DIR.join("common_ffi.rs")); - - // ๅนณๅฐ็›ธๅ…ณไปฃ็  - #[cfg(windows)] - builder.file(common_dir.join("platform/win/win.cpp")); - - #[cfg(target_os = "linux")] - builder.file(common_dir.join("platform/linux/linux.cpp")); - - // ๅทฅๅ…ทไปฃ็  - builder.files([ - common_dir.join("log.cpp"), - common_dir.join("util.cpp"), - ]); -} -``` - -### 3.3 FFmpeg ๆจกๅ—ๆž„ๅปบ - -```rust -mod ffmpeg { - pub fn build_ffmpeg(builder: &mut Build) { - // ็”Ÿๆˆ FFmpeg FFI ็ป‘ๅฎš - ffmpeg_ffi(); - - // ้“พๆŽฅ FFmpeg ๅบ“ - if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") { - link_vcpkg(builder, vcpkg_root.into()); - } else { - link_system_ffmpeg(builder); // pkg-config - } - - // ้“พๆŽฅ็ณป็ปŸๅบ“ - link_os(); - - // ๆž„ๅปบ FFmpeg RAM ๆจกๅ— - build_ffmpeg_ram(builder); - } -} -``` - -### 3.4 FFmpeg ้“พๆŽฅๆ–นๅผ - -#### VCPKG (่ทจๅนณๅฐ้™ๆ€้“พๆŽฅ) - -```rust -fn link_vcpkg(builder: &mut Build, path: PathBuf) -> PathBuf { - // ็›ฎๆ ‡ๅนณๅฐ่ฏ†ๅˆซ - let target = match (target_os, target_arch) { - ("windows", "x86_64") => "x64-windows-static", - ("linux", arch) => format!("{}-linux", arch), - _ => panic!("unsupported platform"), - }; - - let lib_path = path.join("installed").join(target).join("lib"); - - // ้“พๆŽฅ FFmpeg ้™ๆ€ๅบ“ - println!("cargo:rustc-link-search=native={}", lib_path); - ["avcodec", "avutil", "avformat"].iter() - .for_each(|lib| println!("cargo:rustc-link-lib=static={}", lib)); -} -``` - -#### pkg-config (Linux ๅŠจๆ€้“พๆŽฅ) - -```rust -fn link_system_ffmpeg(builder: &mut Build) { - let libs = ["libavcodec", "libavutil", "libavformat", "libswscale"]; - - for lib in &libs { - // ่Žทๅ–็ผ–่ฏ‘ๆ ‡ๅฟ— - let cflags = Command::new("pkg-config") - .args(["--cflags", lib]) - .output()?; - - // ่Žทๅ–้“พๆŽฅๆ ‡ๅฟ— - let libs = Command::new("pkg-config") - .args(["--libs", lib]) - .output()?; - - // ่งฃๆžๅนถๅบ”็”จ - for flag in libs.split_whitespace() { - if flag.starts_with("-L") { - println!("cargo:rustc-link-search=native={}", &flag[2..]); - } else if flag.starts_with("-l") { - println!("cargo:rustc-link-lib={}", &flag[2..]); - } - } - } -} -``` - -### 3.5 ็ณป็ปŸๅบ“้“พๆŽฅ - -```rust -fn link_os() { - let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); - - let libs: Vec<&str> = match target_os.as_str() { - "windows" => vec!["User32", "bcrypt", "ole32", "advapi32"], - "linux" => vec!["drm", "X11", "stdc++", "z"], - _ => panic!("unsupported os"), - }; - - for lib in libs { - println!("cargo:rustc-link-lib={}", lib); - } -} -``` - -## 4. FFI ็ป‘ๅฎš็”Ÿๆˆ - -### 4.1 bindgen ้…็ฝฎ - -```rust -bindgen::builder() - .header("path/to/header.h") - .rustified_enum("*") // ็”Ÿๆˆ Rust ๆžšไธพ - .parse_callbacks(Box::new(Callbacks)) // ่‡ชๅฎšไน‰ๅ›ž่ฐƒ - .generate() - .write_to_file(OUT_DIR.join("ffi.rs")); -``` - -### 4.2 ่‡ชๅฎšไน‰ๆดพ็”Ÿ - -```rust -#[derive(Debug)] -struct CommonCallbacks; - -impl bindgen::callbacks::ParseCallbacks for CommonCallbacks { - fn add_derives(&self, name: &str) -> Vec { - // ไธบ็‰นๅฎš็ฑปๅž‹ๆทปๅŠ ๅบๅˆ—ๅŒ–ๆ”ฏๆŒ - match name { - "DataFormat" | "SurfaceFormat" | "API" => { - vec!["Serialize".to_string(), "Deserialize".to_string()] - } - _ => vec![], - } - } -} -``` - -### 4.3 ็”Ÿๆˆ็š„ๆ–‡ไปถ - -| ๆ–‡ไปถ | ๆฅๆบ | ๅ†…ๅฎน | -|------|------|------| -| `common_ffi.rs` | `common.h`, `callback.h` | ๆžšไธพใ€ๅธธ้‡ใ€ๅ›ž่ฐƒ็ฑปๅž‹ | -| `ffmpeg_ffi.rs` | `ffmpeg_ffi.h` | FFmpeg ๆ—ฅๅฟ—็บงๅˆซใ€ๅ‡ฝๆ•ฐ | -| `ffmpeg_ram_ffi.rs` | `ffmpeg_ram_ffi.h` | ็ผ–่งฃ็ ๅ™จๅ‡ฝๆ•ฐ | - -## 5. ๅนณๅฐๆž„ๅปบๆŒ‡ๅ— - -### 5.1 Linux ๆž„ๅปบ - -```bash -# ๅฎ‰่ฃ… FFmpeg ๅผ€ๅ‘ๅบ“ -sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev - -# ๅฎ‰่ฃ…ๅ…ถไป–ไพ่ต– -sudo apt install libdrm-dev libx11-dev pkg-config - -# ๅฎ‰่ฃ… clang (bindgen ้œ€่ฆ) -sudo apt install clang libclang-dev - -# ๆž„ๅปบ -cargo build --release -p hwcodec -``` - -### 5.2 Windows ๆž„ๅปบ (VCPKG) - -```powershell -# ๅฎ‰่ฃ… VCPKG -git clone https://github.com/microsoft/vcpkg -cd vcpkg -./bootstrap-vcpkg.bat - -# ๅฎ‰่ฃ… FFmpeg -./vcpkg install ffmpeg:x64-windows-static - -# ่ฎพ็ฝฎ็Žฏๅขƒๅ˜้‡ -$env:VCPKG_ROOT = "C:\path\to\vcpkg" - -# ๆž„ๅปบ -cargo build --release -p hwcodec -``` - -### 5.3 ไบคๅ‰็ผ–่ฏ‘ - -```bash -# ๅฎ‰่ฃ… cross -cargo install cross --git https://github.com/cross-rs/cross - -# ARM64 Linux -cross build --release -p hwcodec --target aarch64-unknown-linux-gnu - -# ARMv7 Linux -cross build --release -p hwcodec --target armv7-unknown-linux-gnueabihf -``` - -## 6. ้›†ๆˆๅˆฐ One-KVM - -### 6.1 ไพ่ต–้…็ฝฎ - -```toml -# Cargo.toml -[dependencies] -hwcodec = { path = "libs/hwcodec" } -``` - -### 6.2 ไฝฟ็”จ็คบไพ‹ - -```rust -use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext}; -use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext}; -use hwcodec::ffmpeg::{AVPixelFormat, AVHWDeviceType}; - -// ๆฃ€ๆต‹ๅฏ็”จ็ผ–็ ๅ™จ -let encoders = Encoder::available_encoders(ctx, None); - -// ๅˆ›ๅปบ็ผ–็ ๅ™จ -let encoder = Encoder::new(EncodeContext { - name: "h264_vaapi".to_string(), - width: 1920, - height: 1080, - pixfmt: AVPixelFormat::AV_PIX_FMT_NV12, - fps: 30, - gop: 30, - kbs: 4000, - // ... -})?; - -// ็ผ–็  -let frames = encoder.encode(&yuv_data, pts_ms)?; - -// ๅˆ›ๅปบ MJPEG ่งฃ็ ๅ™จ (IP-KVM ไธ“็”จ) -let decoder = Decoder::new(DecodeContext { - name: "mjpeg".to_string(), - device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_NONE, - thread_count: 4, -})?; - -// ่งฃ็  -let frames = decoder.decode(&mjpeg_data)?; -``` - -### 6.3 ๆ—ฅๅฟ—้›†ๆˆ - -```rust -// hwcodec ไฝฟ็”จ log crate๏ผŒไธŽ One-KVM ๆ—ฅๅฟ—็ณป็ปŸๅ…ผๅฎน -use log::{debug, info, warn, error}; - -// C++ ๅฑ‚ๆ—ฅๅฟ—้€š่ฟ‡ๅ›ž่ฐƒไผ ้€’ๅˆฐ Rust -#[no_mangle] -pub extern "C" fn hwcodec_av_log_callback(level: i32, message: *const c_char) { - // ่ฝฌๅ‘ๅˆฐ Rust log ็ณป็ปŸ - match level { - AV_LOG_ERROR => error!("{}", message), - AV_LOG_WARNING => warn!("{}", message), - AV_LOG_INFO => info!("{}", message), - AV_LOG_DEBUG => debug!("{}", message), - _ => {} - } -} -``` - -## 7. ๆ•…้šœๆŽ’้™ค - -### 7.1 ็ผ–่ฏ‘้”™่ฏฏ - -**FFmpeg ๆœชๆ‰พๅˆฐ**: -``` -error: pkg-config failed for libavcodec -``` -่งฃๅ†ณ: ๅฎ‰่ฃ… FFmpeg ๅผ€ๅ‘ๅบ“ -```bash -sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev -``` - -**bindgen ้”™่ฏฏ**: -``` -error: failed to run custom build command for `hwcodec` -``` -่งฃๅ†ณ: ๅฎ‰่ฃ… clang -```bash -sudo apt install clang libclang-dev -``` - -### 7.2 ้“พๆŽฅ้”™่ฏฏ - -**็ฌฆๅทๆœชๅฎšไน‰**: -``` -undefined reference to `av_log_set_level' -``` -่งฃๅ†ณ: ๆฃ€ๆŸฅ FFmpeg ๅบ“้“พๆŽฅ้กบๅบ๏ผŒ็กฎไฟ pkg-config ๆญฃ็กฎ้…็ฝฎ - -**ๅŠจๆ€ๅบ“ๆœชๆ‰พๅˆฐ**: -``` -error while loading shared libraries: libavcodec.so.59 -``` -่งฃๅ†ณ: -```bash -sudo ldconfig -# ๆˆ–่ฎพ็ฝฎ LD_LIBRARY_PATH -export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -``` - -### 7.3 ่ฟ่กŒๆ—ถ้”™่ฏฏ - -**็กฌไปถ็ผ–็ ๅ™จไธๅฏ็”จ**: -``` -Encoder h264_vaapi test failed -``` -ๆฃ€ๆŸฅ: -1. ้ฉฑๅŠจๆ˜ฏๅฆๆญฃ็กฎๅฎ‰่ฃ…: `vainfo` -2. ๆƒ้™ๆ˜ฏๅฆ่ถณๅคŸ: `ls -la /dev/dri/` -3. ็”จๆˆทๆ˜ฏๅฆๅœจ video ็ป„: `groups` - -**่งฃ็ ๅคฑ่ดฅ**: -``` -avcodec_receive_frame failed, ret = -11 -``` -่งฃๅ†ณ: ่ฟ™้€šๅธธ่กจ็คบ้œ€่ฆๆ›ดๅคš่พ“ๅ…ฅๆ•ฐๆฎ (EAGAIN)๏ผŒๆ˜ฏๆญฃๅธธ่กŒไธบ - -## 8. ไธŽๅŽŸ็‰ˆ RustDesk hwcodec ็š„ๆž„ๅปบๅทฎๅผ‚ - -### 8.1 ็งป้™ค็š„ๆž„ๅปบๆญฅ้ชค - -| ๆญฅ้ชค | ๅŽŸๅ›  | -|------|------| -| `build_mux()` | ็งป้™คไบ† Mux ๆจกๅ— | -| `build_ffmpeg_vram()` | ็งป้™คไบ† VRAM ๆจกๅ— | -| `sdk::build_sdk()` | ็งป้™คไบ†ๅค–้ƒจ SDK ไพ่ต– | -| macOS ๆก†ๆžถ้“พๆŽฅ | ็งป้™คไบ† macOS ๆ”ฏๆŒ | -| Android NDK ้“พๆŽฅ | ็งป้™คไบ† Android ๆ”ฏๆŒ | - -### 8.2 ็ฎ€ๅŒ–็š„ๆž„ๅปบๆต็จ‹ - -``` -ๅŽŸ็‰ˆๆž„ๅปบๆต็จ‹: -build.rs -โ”œโ”€โ”€ build_common() -โ”œโ”€โ”€ ffmpeg::build_ffmpeg() -โ”‚ โ”œโ”€โ”€ build_ffmpeg_ram() -โ”‚ โ”œโ”€โ”€ build_ffmpeg_vram() [ๅทฒ็งป้™ค] -โ”‚ โ””โ”€โ”€ build_mux() [ๅทฒ็งป้™ค] -โ””โ”€โ”€ sdk::build_sdk() [ๅทฒ็งป้™ค] - -็ฎ€ๅŒ–็‰ˆๆž„ๅปบๆต็จ‹: -build.rs -โ”œโ”€โ”€ build_common() -โ””โ”€โ”€ ffmpeg::build_ffmpeg() - โ””โ”€โ”€ build_ffmpeg_ram() -``` - -### 8.3 ไผ˜ๅŠฟ - -1. **ๆ›ดๅฟซ็š„็ผ–่ฏ‘**: ๆ— ้œ€็ผ–่ฏ‘ๅค–้ƒจ SDK ไปฃ็  -2. **ๆ›ดๅฐ‘็š„ไพ่ต–**: ๆ— ้œ€ไธ‹่ฝฝ ~9MB ็š„ๅค–้ƒจ SDK -3. **ๆ›ด็ฎ€ๅ•็š„็ปดๆŠค**: ไปฃ็ ้‡ๅ‡ๅฐ‘็บฆ 67% -4. **ๆ›ดๅฐ็š„ไบŒ่ฟ›ๅˆถ**: ไธๅŒ…ๅซๆœชไฝฟ็”จ็š„ๅŠŸ่ƒฝ diff --git a/docs/report/rustdesk/00-overview.md b/docs/report/rustdesk/00-overview.md deleted file mode 100644 index 2282aa5b..00000000 --- a/docs/report/rustdesk/00-overview.md +++ /dev/null @@ -1,69 +0,0 @@ -# RustDesk ้€šไฟกๅ่ฎฎๆŠ€ๆœฏๆŠฅๅ‘Š - -## ๆฆ‚่ฟฐ - -ๆœฌๆŠฅๅ‘Š่ฏฆ็ป†ๅˆ†ๆž RustDesk ่ฟœ็จ‹ๆกŒ้ข่ฝฏไปถ็š„ๅฎขๆˆท็ซฏไธŽๆœๅŠกๅ™จไน‹้—ด็š„้€šไฟกๅ่ฎฎ๏ผŒๅŒ…ๆ‹ฌ Rendezvous ๆœๅŠกๅ™จ๏ผˆhbbs๏ผ‰ใ€Relay ๆœๅŠกๅ™จ๏ผˆhbbr๏ผ‰ไปฅๅŠๅฎขๆˆท็ซฏไน‹้—ด็š„ P2P ่ฟžๆŽฅๆœบๅˆถใ€‚ - -## ๆ–‡ๆกฃ็ป“ๆž„ - -| ๆ–‡ๆกฃ | ๅ†…ๅฎน | -|------|------| -| [01-architecture.md](01-architecture.md) | ๆ•ดไฝ“ๆžถๆž„่ฎพ่ฎก | -| [02-rendezvous-protocol.md](02-rendezvous-protocol.md) | Rendezvous ๆœๅŠกๅ™จๅ่ฎฎ | -| [03-relay-protocol.md](03-relay-protocol.md) | Relay ๆœๅŠกๅ™จๅ่ฎฎ | -| [04-p2p-connection.md](04-p2p-connection.md) | P2P ่ฟžๆŽฅๆต็จ‹ | -| [05-message-format.md](05-message-format.md) | ๆถˆๆฏๆ ผๅผๅฎšไน‰ | -| [06-encryption.md](06-encryption.md) | ๅŠ ๅฏ†ๆœบๅˆถ | -| [07-nat-traversal.md](07-nat-traversal.md) | NAT ็ฉฟ้€ๆŠ€ๆœฏ | -| [08-onekvm-comparison.md](08-onekvm-comparison.md) | **One-KVM ๅฎž็Žฐๅฏนๆฏ”ๅˆ†ๆž** | - -## ๆ ธๅฟƒ็ป„ไปถ - -### 1. Rendezvous Server (hbbs) -- **ๅŠŸ่ƒฝ**: ID ๆณจๅ†Œใ€Peer ๅ‘็Žฐใ€NAT ็ฑปๅž‹ๆฃ€ๆต‹ใ€่ฟžๆŽฅๅ่ฐƒ -- **็ซฏๅฃ**: 21116 (TCP/UDP), 21115 (NAT ๆต‹่ฏ•), 21118 (WebSocket) -- **ๆบๆ–‡ไปถ**: `rustdesk-server/src/rendezvous_server.rs` - -### 2. Relay Server (hbbr) -- **ๅŠŸ่ƒฝ**: ๅฝ“ P2P ่ฟžๆŽฅๅคฑ่ดฅๆ—ถๆไพ›ๆ•ฐๆฎไธญ่ฝฌ -- **็ซฏๅฃ**: 21117 (TCP), 21119 (WebSocket) -- **ๆบๆ–‡ไปถ**: `rustdesk-server/src/relay_server.rs` - -### 3. ๅฎขๆˆท็ซฏ (RustDesk) -- **ๅŠŸ่ƒฝ**: ่ฟœ็จ‹ๆกŒ้ขๆŽงๅˆถใ€ๆ–‡ไปถไผ ่พ“ใ€ๅฑๅน•ๅ…ฑไบซ -- **ๆ ธๅฟƒๆจกๅ—**: - - `rendezvous_mediator.rs` - ไธŽ Rendezvous ๆœๅŠกๅ™จ้€šไฟก - - `client.rs` - ๅฎขๆˆท็ซฏ่ฟžๆŽฅ้€ป่พ‘ - - `server/connection.rs` - ่ขซๆŽง็ซฏ่ฟžๆŽฅๅค„็† - -## ๅ่ฎฎๆ ˆ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Application Layer โ”‚ -โ”‚ (Video/Audio/Keyboard/Mouse/File) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Message Layer โ”‚ -โ”‚ (Protobuf Messages) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Security Layer โ”‚ -โ”‚ (Sodium: X25519 + ChaCha20) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Transport Layer โ”‚ -โ”‚ (TCP/UDP/WebSocket/KCP) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๅ…ณ้”ฎๆŠ€ๆœฏ็‰น็‚น - -1. **ๆททๅˆ่ฟžๆŽฅๆจกๅผ**: ไผ˜ๅ…ˆๅฐ่ฏ• P2P ็›ด่ฟž๏ผŒๅคฑ่ดฅๅŽ่‡ชๅŠจๅˆ‡ๆขๅˆฐ Relay ไธญ่ฝฌ -2. **ๅคšๅ่ฎฎๆ”ฏๆŒ**: TCPใ€UDPใ€WebSocketใ€KCP -3. **็ซฏๅˆฐ็ซฏๅŠ ๅฏ†**: ไฝฟ็”จ libsodium ๅฎž็Žฐ็š„ X25519 ๅฏ†้’ฅไบคๆขๅ’Œ ChaCha20-Poly1305 ๅฏน็งฐๅŠ ๅฏ† -4. **NAT ็ฉฟ้€**: ๆ”ฏๆŒ UDP ๆ‰“ๆดžๅ’Œ TCP ๆ‰“ๆดžๆŠ€ๆœฏ -5. **ๆœๅŠกๅ™จ็ญพๅ**: ๅฏ้€‰็š„ๆœๅŠกๅ™จๅ…ฌ้’ฅ็ญพๅ้ชŒ่ฏ๏ผŒ้˜ฒๆญขไธญ้—ดไบบๆ”ปๅ‡ป - -## ็‰ˆๆœฌไฟกๆฏ - -- ๅˆ†ๆžๅŸบไบŽ RustDesk ๆœ€ๆ–ฐ็‰ˆๆœฌๆบ็  -- Protocol Buffer ็‰ˆๆœฌ: proto3 -- ๅŠ ๅฏ†ๅบ“: libsodium (sodiumoxide) diff --git a/docs/report/rustdesk/01-architecture.md b/docs/report/rustdesk/01-architecture.md deleted file mode 100644 index 4569117c..00000000 --- a/docs/report/rustdesk/01-architecture.md +++ /dev/null @@ -1,218 +0,0 @@ -# RustDesk ๆžถๆž„่ฎพ่ฎก - -## ็ณป็ปŸๆžถๆž„ๅ›พ - -``` - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Rendezvous Server โ”‚ - โ”‚ (hbbs) โ”‚ - โ”‚ Port: 21116 โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Client A โ”‚ โ”‚ Client B โ”‚ โ”‚ Client C โ”‚ - โ”‚ (ๆŽงๅˆถ็ซฏ) โ”‚ โ”‚ (่ขซๆŽง็ซฏ) โ”‚ โ”‚ (่ขซๆŽง็ซฏ) โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ”‚ P2P Connection โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ - โ”‚ (ๅฆ‚ๆžœ P2P ๅคฑ่ดฅ) โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ–ผ โ”‚ - โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ””โ”€โ–บโ”‚ Relay Server โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ (hbbr) โ”‚ - โ”‚ Port: 21117 โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๆœๅŠกๅ™จ็ป„ไปถ่ฏฆ่งฃ - -### Rendezvous Server (hbbs) - -**็›‘ๅฌ็ซฏๅฃ๏ผš** -| ็ซฏๅฃ | ๅ่ฎฎ | ็”จ้€” | -|------|------|------| -| 21116 | TCP | ไธป่ฆ้€šไฟก็ซฏๅฃ๏ผŒๅค„็† punch hole ่ฏทๆฑ‚ | -| 21116 | UDP | Peer ๆณจๅ†Œใ€NAT ็ฑปๅž‹ๆฃ€ๆต‹ | -| 21115 | TCP | NAT ๆต‹่ฏ•ไธ“็”จ็ซฏๅฃ | -| 21118 | WebSocket | Web ๅฎขๆˆท็ซฏๆ”ฏๆŒ | - -**ๆ ธๅฟƒๆ•ฐๆฎ็ป“ๆž„๏ผš** - -```rust -// rustdesk-server/src/rendezvous_server.rs:64-83 -pub struct RendezvousServer { - tcp_punch: Arc>>, // TCP punch hole ่ฟžๆŽฅ - pm: PeerMap, // Peer ๆ˜ ๅฐ„่กจ - tx: Sender, // ๆถˆๆฏๅ‘้€้€š้“ - relay_servers: Arc, // ๅฏ็”จ Relay ๆœๅŠกๅ™จๅˆ—่กจ - relay_servers0: Arc, // ๅŽŸๅง‹ Relay ๆœๅŠกๅ™จๅˆ—่กจ - rendezvous_servers: Arc>, // Rendezvous ๆœๅŠกๅ™จๅˆ—่กจ - inner: Arc, // ๅ†…้ƒจ้…็ฝฎ -} - -struct Inner { - serial: i32, // ้…็ฝฎๅบๅˆ—ๅท - version: String, // ่ฝฏไปถ็‰ˆๆœฌ - software_url: String, // ่ฝฏไปถๆ›ดๆ–ฐ URL - mask: Option, // LAN ๆŽฉ็  - local_ip: String, // ๆœฌๅœฐ IP - sk: Option, // ๆœๅŠกๅ™จ็ญพๅๅฏ†้’ฅ -} -``` - -**Peer ๆ•ฐๆฎ็ป“ๆž„๏ผš** - -```rust -// rustdesk-server/src/peer.rs:32-42 -pub struct Peer { - pub socket_addr: SocketAddr, // ๆœ€ๅŽๆณจๅ†Œ็š„ๅœฐๅ€ - pub last_reg_time: Instant, // ๆœ€ๅŽๆณจๅ†Œๆ—ถ้—ด - pub guid: Vec, // ๆ•ฐๆฎๅบ“ GUID - pub uuid: Bytes, // ่ฎพๅค‡ UUID - pub pk: Bytes, // ๅ…ฌ้’ฅ - pub info: PeerInfo, // Peer ไฟกๆฏ - pub reg_pk: (u32, Instant), // ๆณจๅ†Œ้ข‘็އ้™ๅˆถ -} -``` - -### Relay Server (hbbr) - -**็›‘ๅฌ็ซฏๅฃ๏ผš** -| ็ซฏๅฃ | ๅ่ฎฎ | ็”จ้€” | -|------|------|------| -| 21117 | TCP | ไธป่ฆไธญ่ฝฌ็ซฏๅฃ | -| 21119 | WebSocket | Web ๅฎขๆˆท็ซฏๆ”ฏๆŒ | - -**ๆ ธๅฟƒ็‰นๆ€ง๏ผš** - -```rust -// rustdesk-server/src/relay_server.rs:40-44 -static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // ้™็บง้˜ˆๅ€ผ -static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // ๆฃ€ๆต‹ๅผ€ๅง‹ๆ—ถ้—ด(ms) -static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // ้™้€Ÿ(bit/s) -static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// ๆ€ปๅธฆๅฎฝ -static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// ๅ•่ฟžๆŽฅๅธฆๅฎฝ -``` - -## ๅฎขๆˆท็ซฏๆžถๆž„ - -### ๆ ธๅฟƒๆจกๅ— - -``` -rustdesk/src/ -โ”œโ”€โ”€ rendezvous_mediator.rs # Rendezvous ๆœๅŠกๅ™จ้€šไฟก -โ”œโ”€โ”€ client.rs # ๆŽงๅˆถ็ซฏๆ ธๅฟƒ้€ป่พ‘ -โ”œโ”€โ”€ server/ -โ”‚ โ”œโ”€โ”€ mod.rs # ่ขซๆŽง็ซฏๆœๅŠก -โ”‚ โ”œโ”€โ”€ connection.rs # ่ฟžๆŽฅๅค„็† -โ”‚ โ”œโ”€โ”€ video_service.rs # ่ง†้ข‘ๆœๅŠก -โ”‚ โ”œโ”€โ”€ audio_service.rs # ้Ÿณ้ข‘ๆœๅŠก -โ”‚ โ””โ”€โ”€ input_service.rs # ่พ“ๅ…ฅๆœๅŠก -โ”œโ”€โ”€ common.rs # ้€š็”จๅ‡ฝๆ•ฐ๏ผˆๅŠ ๅฏ†ใ€่งฃๅฏ†๏ผ‰ -โ””โ”€โ”€ platform/ # ๅนณๅฐ็‰นๅฎšไปฃ็  -``` - -### RendezvousMediator - -```rust -// rustdesk/src/rendezvous_mediator.rs:44-50 -pub struct RendezvousMediator { - addr: TargetAddr<'static>, // ๆœๅŠกๅ™จๅœฐๅ€ - host: String, // ๆœๅŠกๅ™จไธปๆœบๅ - host_prefix: String, // ไธปๆœบๅ‰็ผ€ - keep_alive: i32, // ไฟๆดป้—ด้š” -} -``` - -**ไธค็ง่ฟžๆŽฅๆจกๅผ๏ผš** - -1. **UDP ๆจกๅผ** (้ป˜่ฎค): - - ็”จไบŽ Peer ๆณจๅ†Œๅ’Œๅฟƒ่ทณ - - ๆ›ดไฝŽๅปถ่ฟŸ - - ๅฏ่ƒฝ่ขซๆŸไบ›้˜ฒ็ซๅข™้˜ปๆญข - -2. **TCP ๆจกๅผ**: - - ็”จไบŽไปฃ็†็Žฏๅขƒ - - WebSocket ๆจกๅผ - - ๆ›ดๅฏ้  - -## ่ฟžๆŽฅๆต็จ‹ๆฆ‚่ฟฐ - -### ่ขซๆŽง็ซฏๅฏๅŠจๆต็จ‹ - -``` -1. ็”Ÿๆˆ่ฎพๅค‡ ID ๅ’Œๅฏ†้’ฅๅฏน -2. ่ฟžๆŽฅ Rendezvous Server -3. ๅ‘้€ RegisterPeer ๆถˆๆฏ -4. ๅฆ‚ๆžœ้œ€่ฆ๏ผŒๅ‘้€ RegisterPk ๆณจๅ†Œๅ…ฌ้’ฅ -5. ๅฎšๆœŸๅ‘้€ๅฟƒ่ทณไฟๆŒๅœจ็บฟ็Šถๆ€ -6. ็ญ‰ๅพ… PunchHole ๆˆ– RequestRelay ่ฏทๆฑ‚ -``` - -### ๆŽงๅˆถ็ซฏ่ฟžๆŽฅๆต็จ‹ - -``` -1. ่พ“ๅ…ฅ็›ฎๆ ‡่ฎพๅค‡ ID -2. ่ฟžๆŽฅ Rendezvous Server -3. ๅ‘้€ PunchHoleRequest ๆถˆๆฏ -4. ๆ นๆฎๅ“ๅบ”ๅ†ณๅฎš่ฟžๆŽฅๆ–นๅผ: - a. ็›ด่ฟž (P2P): ไฝฟ็”จ PunchHole ไฟกๆฏๅฐ่ฏ•ๆ‰“ๆดž - b. ๅฑ€ๅŸŸ็ฝ‘: ไฝฟ็”จ LocalAddr ไฟกๆฏ็›ด่ฟž - c. ไธญ่ฝฌ: ้€š่ฟ‡ Relay Server ่ฟžๆŽฅ -5. ๅปบ็ซ‹ๅฎ‰ๅ…จๅŠ ๅฏ†้€š้“ -6. ๅ‘้€ LoginRequest ่ฟ›่กŒ่บซไปฝ้ชŒ่ฏ -7. ๅผ€ๅง‹่ฟœ็จ‹ๆŽงๅˆถไผš่ฏ -``` - -## ๆ•ฐๆฎๆต - -### ่ง†้ข‘ๆต - -``` -่ขซๆŽง็ซฏ ๆŽงๅˆถ็ซฏ - โ”‚ โ”‚ - โ”‚ VideoFrame (H264/VP9/...) โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ - โ”‚ ๅŠ ๅฏ† โ†’ ไผ ่พ“ โ†’ ่งฃๅฏ† โ†’ ่งฃ็  โ†’ ๆ˜พ็คบ โ”‚ -``` - -### ่พ“ๅ…ฅๆต - -``` -ๆŽงๅˆถ็ซฏ ่ขซๆŽง็ซฏ - โ”‚ โ”‚ - โ”‚ MouseEvent/KeyEvent โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ - โ”‚ ๅŠ ๅฏ† โ†’ ไผ ่พ“ โ†’ ่งฃๅฏ† โ†’ ๆจกๆ‹Ÿ่พ“ๅ…ฅ โ”‚ -``` - -## ้ซ˜ๅฏ็”จ่ฎพ่ฎก - -### ๅคšๆœๅŠกๅ™จๆ”ฏๆŒ - -- ๅฎขๆˆท็ซฏๅฏ้…็ฝฎๅคšไธช Rendezvous Server -- ่‡ชๅŠจ้€‰ๆ‹ฉๅปถ่ฟŸๆœ€ไฝŽ็š„ๆœๅŠกๅ™จ -- ่ฟžๆŽฅๅคฑ่ดฅๆ—ถ่‡ชๅŠจๅˆ‡ๆขๅค‡็”จๆœๅŠกๅ™จ - -### Relay Server ้€‰ๆ‹ฉ - -- ๆ”ฏๆŒ้…็ฝฎๅคšไธช Relay Server -- ่ฝฎ่ฏข็ฎ—ๆณ•ๅˆ†้…่ดŸ่ฝฝ -- ๅฎšๆœŸๆฃ€ๆŸฅ Relay Server ๅฏ็”จๆ€ง - -### ้‡่ฟžๆœบๅˆถ - -```rust -// ่ฟžๆŽฅ่ถ…ๆ—ถๅ’Œ้‡่ฏ•ๅ‚ๆ•ฐ -const REG_INTERVAL: i64 = 12_000; // ๆณจๅ†Œ้—ด้š” 12 ็ง’ -const REG_TIMEOUT: i32 = 30_000; // ๆณจๅ†Œ่ถ…ๆ—ถ 30 ็ง’ -const CONNECT_TIMEOUT: u64 = 18_000; // ่ฟžๆŽฅ่ถ…ๆ—ถ 18 ็ง’ -``` diff --git a/docs/report/rustdesk/02-rendezvous-protocol.md b/docs/report/rustdesk/02-rendezvous-protocol.md deleted file mode 100644 index b07266bb..00000000 --- a/docs/report/rustdesk/02-rendezvous-protocol.md +++ /dev/null @@ -1,438 +0,0 @@ -# Rendezvous ๆœๅŠกๅ™จๅ่ฎฎ - -## ๆฆ‚่ฟฐ - -Rendezvous Server๏ผˆhbbs๏ผ‰ๆ˜ฏ RustDesk ็š„ๆ ธๅฟƒๅ่ฐƒๆœๅŠกๅ™จ๏ผŒ่ดŸ่ดฃ๏ผš -- Peer ID ๆณจๅ†Œๅ’Œๅ‘็Žฐ -- ๅ…ฌ้’ฅๅญ˜ๅ‚จๅ’Œๅˆ†ๅ‘ -- NAT ็ฑปๅž‹ๆฃ€ๆต‹ -- P2P ่ฟžๆŽฅๅ่ฐƒ๏ผˆๆ‰“ๆดž่พ…ๅŠฉ๏ผ‰ -- Relay Server ๅˆ†้… - -## ๅ่ฎฎๆถˆๆฏๅฎšไน‰ - -ๆ‰€ๆœ‰ๆถˆๆฏไฝฟ็”จ Protocol Buffers ๅฎšไน‰ๅœจ `protos/rendezvous.proto`๏ผš - -```protobuf -message RendezvousMessage { - oneof union { - RegisterPeer register_peer = 6; - RegisterPeerResponse register_peer_response = 7; - PunchHoleRequest punch_hole_request = 8; - PunchHole punch_hole = 9; - PunchHoleSent punch_hole_sent = 10; - PunchHoleResponse punch_hole_response = 11; - FetchLocalAddr fetch_local_addr = 12; - LocalAddr local_addr = 13; - ConfigUpdate configure_update = 14; - RegisterPk register_pk = 15; - RegisterPkResponse register_pk_response = 16; - SoftwareUpdate software_update = 17; - RequestRelay request_relay = 18; - RelayResponse relay_response = 19; - TestNatRequest test_nat_request = 20; - TestNatResponse test_nat_response = 21; - PeerDiscovery peer_discovery = 22; - OnlineRequest online_request = 23; - OnlineResponse online_response = 24; - KeyExchange key_exchange = 25; - HealthCheck hc = 26; - } -} -``` - -## ๆ ธๅฟƒๆต็จ‹ - -### 1. Peer ๆณจๅ†Œๆต็จ‹ - -**ๅฎขๆˆท็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšRegisterPeer** - -```protobuf -message RegisterPeer { - string id = 1; // Peer ID (ๅฆ‚ "123456789") - int32 serial = 2; // ้…็ฝฎๅบๅˆ—ๅท -} -``` - -**ๆœๅŠกๅ™จๅค„็†้€ป่พ‘๏ผš** - -```rust -// rustdesk-server/src/rendezvous_server.rs:318-333 -Some(rendezvous_message::Union::RegisterPeer(rp)) => { - if !rp.id.is_empty() { - log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr); - self.update_addr(rp.id, addr, socket).await?; - // ๅฆ‚ๆžœๆœๅŠกๅ™จ้…็ฝฎๆ›ดๆ–ฐ๏ผŒๅ‘้€ ConfigUpdate - if self.inner.serial > rp.serial { - let mut msg_out = RendezvousMessage::new(); - msg_out.set_configure_update(ConfigUpdate { - serial: self.inner.serial, - rendezvous_servers: (*self.rendezvous_servers).clone(), - ..Default::default() - }); - socket.send(&msg_out, addr).await?; - } - } -} -``` - -**ๆœๅŠกๅ™จ โ†’ ๅฎขๆˆท็ซฏ๏ผšRegisterPeerResponse** - -```protobuf -message RegisterPeerResponse { - bool request_pk = 2; // ๆ˜ฏๅฆ้œ€่ฆๆณจๅ†Œๅ…ฌ้’ฅ -} -``` - -### 2. ๅ…ฌ้’ฅๆณจๅ†Œๆต็จ‹ - -ๅฝ“ๆœๅŠกๅ™จๆฃ€ๆต‹ๅˆฐ Peer ็š„ๅ…ฌ้’ฅไธบ็ฉบๆˆ– IP ๅ˜ๅŒ–ๆ—ถ๏ผŒไผš่ฏทๆฑ‚ๆณจๅ†Œๅ…ฌ้’ฅใ€‚ - -**ๅฎขๆˆท็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšRegisterPk** - -```protobuf -message RegisterPk { - string id = 1; // Peer ID - bytes uuid = 2; // ่ฎพๅค‡ UUID - bytes pk = 3; // Ed25519 ๅ…ฌ้’ฅ - string old_id = 4; // ๆ—ง ID๏ผˆๅฆ‚ๆžœๆ›ดๆข๏ผ‰ -} -``` - -**ๆœๅŠกๅ™จๅค„็†้€ป่พ‘๏ผš** - -```rust -// rustdesk-server/src/rendezvous_server.rs:334-418 -Some(rendezvous_message::Union::RegisterPk(rk)) => { - // ้ชŒ่ฏ UUID ๅ’Œๅ…ฌ้’ฅ - if rk.uuid.is_empty() || rk.pk.is_empty() { - return Ok(()); - } - let id = rk.id; - let ip = addr.ip().to_string(); - - // ID ้•ฟๅบฆๆฃ€ๆŸฅ - if id.len() < 6 { - return send_rk_res(socket, addr, UUID_MISMATCH).await; - } - - // IP ๅฐ้”ๆฃ€ๆŸฅ - if !self.check_ip_blocker(&ip, &id).await { - return send_rk_res(socket, addr, TOO_FREQUENT).await; - } - - // UUID ๅŒน้…้ชŒ่ฏ - let peer = self.pm.get_or(&id).await; - // ... UUID ้ชŒ่ฏ้€ป่พ‘ ... - - // ๆ›ดๆ–ฐๆ•ฐๆฎๅบ“ - if changed { - self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await; - } - - // ๅ‘้€ๆˆๅŠŸๅ“ๅบ” - msg_out.set_register_pk_response(RegisterPkResponse { - result: register_pk_response::Result::OK.into(), - ..Default::default() - }); -} -``` - -**ๆœๅŠกๅ™จ โ†’ ๅฎขๆˆท็ซฏ๏ผšRegisterPkResponse** - -```protobuf -message RegisterPkResponse { - enum Result { - OK = 0; - UUID_MISMATCH = 2; - ID_EXISTS = 3; - TOO_FREQUENT = 4; - INVALID_ID_FORMAT = 5; - NOT_SUPPORT = 6; - SERVER_ERROR = 7; - } - Result result = 1; - int32 keep_alive = 2; // ๅฟƒ่ทณ้—ด้š” -} -``` - -### 3. Punch Hole ่ฏทๆฑ‚ๆต็จ‹ - -ๅฝ“ๆŽงๅˆถ็ซฏ่ฆ่ฟžๆŽฅ่ขซๆŽง็ซฏๆ—ถ๏ผŒ้ฆ–ๅ…ˆๅ‘้€ PunchHoleRequestใ€‚ - -**ๆŽงๅˆถ็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšPunchHoleRequest** - -```protobuf -message PunchHoleRequest { - string id = 1; // ็›ฎๆ ‡ Peer ID - NatType nat_type = 2; // ่ฏทๆฑ‚ๆ–น็š„ NAT ็ฑปๅž‹ - string licence_key = 3; // ่ฎธๅฏ่ฏๅฏ†้’ฅ - ConnType conn_type = 4; // ่ฟžๆŽฅ็ฑปๅž‹ - string token = 5; // ่ฎค่ฏไปค็‰Œ - string version = 6; // ๅฎขๆˆท็ซฏ็‰ˆๆœฌ -} - -enum NatType { - UNKNOWN_NAT = 0; - ASYMMETRIC = 1; - SYMMETRIC = 2; -} - -enum ConnType { - DEFAULT_CONN = 0; - FILE_TRANSFER = 1; - PORT_FORWARD = 2; - RDP = 3; - VIEW_CAMERA = 4; -} -``` - -**ๆœๅŠกๅ™จๅค„็†้€ป่พ‘๏ผš** - -```rust -// rustdesk-server/src/rendezvous_server.rs:674-765 -async fn handle_punch_hole_request(...) { - // 1. ้ชŒ่ฏ่ฎธๅฏ่ฏๅฏ†้’ฅ - if !key.is_empty() && ph.licence_key != key { - return Ok((PunchHoleResponse { failure: LICENSE_MISMATCH }, None)); - } - - // 2. ๆŸฅๆ‰พ็›ฎๆ ‡ Peer - if let Some(peer) = self.pm.get(&id).await { - let (elapsed, peer_addr) = peer.read().await; - - // 3. ๆฃ€ๆŸฅๅœจ็บฟ็Šถๆ€ - if elapsed >= REG_TIMEOUT { - return Ok((PunchHoleResponse { failure: OFFLINE }, None)); - } - - // 4. ๅˆคๆ–ญๆ˜ฏๅฆๅŒไธ€ๅฑ€ๅŸŸ็ฝ‘ - let same_intranet = (peer_is_lan && is_lan) || - (peer_addr.ip() == addr.ip()); - - if same_intranet { - // ่ฏทๆฑ‚่Žทๅ–ๆœฌๅœฐๅœฐๅ€ - msg_out.set_fetch_local_addr(FetchLocalAddr { - socket_addr: AddrMangle::encode(addr).into(), - relay_server, - }); - } else { - // ๅ‘้€ Punch Hole ่ฏทๆฑ‚็ป™่ขซๆŽง็ซฏ - msg_out.set_punch_hole(PunchHole { - socket_addr: AddrMangle::encode(addr).into(), - nat_type: ph.nat_type, - relay_server, - }); - } - return Ok((msg_out, Some(peer_addr))); - } - - // Peer ไธๅญ˜ๅœจ - Ok((PunchHoleResponse { failure: ID_NOT_EXIST }, None)) -} -``` - -**ๆœๅŠกๅ™จ โ†’ ่ขซๆŽง็ซฏ๏ผšPunchHole ๆˆ– FetchLocalAddr** - -```protobuf -message PunchHole { - bytes socket_addr = 1; // ๆŽงๅˆถ็ซฏๅœฐๅ€๏ผˆ็ผ–็ ๏ผ‰ - string relay_server = 2; // Relay ๆœๅŠกๅ™จๅœฐๅ€ - NatType nat_type = 3; // ๆŽงๅˆถ็ซฏ NAT ็ฑปๅž‹ -} - -message FetchLocalAddr { - bytes socket_addr = 1; // ๆŽงๅˆถ็ซฏๅœฐๅ€๏ผˆ็ผ–็ ๏ผ‰ - string relay_server = 2; // Relay ๆœๅŠกๅ™จๅœฐๅ€ -} -``` - -### 4. ่ขซๆŽง็ซฏๅ“ๅบ”ๆต็จ‹ - -**่ขซๆŽง็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšPunchHoleSent ๆˆ– LocalAddr** - -```protobuf -message PunchHoleSent { - bytes socket_addr = 1; // ๆŽงๅˆถ็ซฏๅœฐๅ€ - string id = 2; // ่ขซๆŽง็ซฏ ID - string relay_server = 3; // Relay ๆœๅŠกๅ™จ - NatType nat_type = 4; // ่ขซๆŽง็ซฏ NAT ็ฑปๅž‹ - string version = 5; // ๅฎขๆˆท็ซฏ็‰ˆๆœฌ -} - -message LocalAddr { - bytes socket_addr = 1; // ๆŽงๅˆถ็ซฏๅœฐๅ€ - bytes local_addr = 2; // ่ขซๆŽง็ซฏๆœฌๅœฐๅœฐๅ€ - string relay_server = 3; // Relay ๆœๅŠกๅ™จ - string id = 4; // ่ขซๆŽง็ซฏ ID - string version = 5; // ๅฎขๆˆท็ซฏ็‰ˆๆœฌ -} -``` - -**ๆœๅŠกๅ™จ โ†’ ๆŽงๅˆถ็ซฏ๏ผšPunchHoleResponse** - -```protobuf -message PunchHoleResponse { - bytes socket_addr = 1; // ่ขซๆŽง็ซฏๅœฐๅ€ - bytes pk = 2; // ่ขซๆŽง็ซฏๅ…ฌ้’ฅ๏ผˆๅทฒ็ญพๅ๏ผ‰ - enum Failure { - ID_NOT_EXIST = 0; - OFFLINE = 2; - LICENSE_MISMATCH = 3; - LICENSE_OVERUSE = 4; - } - Failure failure = 3; - string relay_server = 4; - oneof union { - NatType nat_type = 5; - bool is_local = 6; // ๆ˜ฏๅฆไธบๅฑ€ๅŸŸ็ฝ‘่ฟžๆŽฅ - } - string other_failure = 7; - int32 feedback = 8; -} -``` - -### 5. Relay ่ฏทๆฑ‚ๆต็จ‹ - -ๅฝ“ P2P ่ฟžๆŽฅๅคฑ่ดฅๆˆ– NAT ็ฑปๅž‹ไธๆ”ฏๆŒๆ‰“ๆดžๆ—ถ๏ผŒไฝฟ็”จ Relayใ€‚ - -**ๅฎขๆˆท็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšRequestRelay** - -```protobuf -message RequestRelay { - string id = 1; // ็›ฎๆ ‡ Peer ID - string uuid = 2; // ่ฟžๆŽฅ UUID๏ผˆ็”จไบŽ้…ๅฏน๏ผ‰ - bytes socket_addr = 3; // ๆœฌ็ซฏๅœฐๅ€ - string relay_server = 4; // ๆŒ‡ๅฎš็š„ Relay ๆœๅŠกๅ™จ - bool secure = 5; // ๆ˜ฏๅฆไฝฟ็”จๅŠ ๅฏ† - string licence_key = 6; // ่ฎธๅฏ่ฏๅฏ†้’ฅ - ConnType conn_type = 7; // ่ฟžๆŽฅ็ฑปๅž‹ - string token = 8; // ่ฎค่ฏไปค็‰Œ -} -``` - -**ๆœๅŠกๅ™จ โ†’ ๅฎขๆˆท็ซฏ๏ผšRelayResponse** - -```protobuf -message RelayResponse { - bytes socket_addr = 1; // ๅฏน็ซฏๅœฐๅ€ - string uuid = 2; // ่ฟžๆŽฅ UUID - string relay_server = 3; // Relay ๆœๅŠกๅ™จๅœฐๅ€ - oneof union { - string id = 4; // ๅฏน็ซฏ ID - bytes pk = 5; // ๅฏน็ซฏๅ…ฌ้’ฅ - } - string refuse_reason = 6; // ๆ‹’็ปๅŽŸๅ›  - string version = 7; // ็‰ˆๆœฌ - int32 feedback = 9; -} -``` - -## NAT ็ฑปๅž‹ๆฃ€ๆต‹ - -**ๅฎขๆˆท็ซฏ โ†’ ๆœๅŠกๅ™จ๏ผšTestNatRequest** - -```protobuf -message TestNatRequest { - int32 serial = 1; // ้…็ฝฎๅบๅˆ—ๅท -} -``` - -**ๆœๅŠกๅ™จ โ†’ ๅฎขๆˆท็ซฏ๏ผšTestNatResponse** - -```protobuf -message TestNatResponse { - int32 port = 1; // ่ง‚ๆต‹ๅˆฐ็š„ๆบ็ซฏๅฃ - ConfigUpdate cu = 2; // ้…็ฝฎๆ›ดๆ–ฐ -} -``` - -NAT ๆฃ€ๆต‹ๅŽŸ็†๏ผš -1. ๅฎขๆˆท็ซฏๅŒๆ—ถๅ‘ไธป็ซฏๅฃ๏ผˆ21116๏ผ‰ๅ’Œ NAT ๆต‹่ฏ•็ซฏๅฃ๏ผˆ21115๏ผ‰ๅ‘้€่ฏทๆฑ‚ -2. ๆฏ”่พƒไธคๆฌกๅ“ๅบ”ไธญ่ง‚ๆต‹ๅˆฐ็š„ๆบ็ซฏๅฃ -3. ๅฆ‚ๆžœ็ซฏๅฃไธ€่‡ด๏ผŒๅˆ™ไธบ ASYMMETRIC NAT๏ผˆ้€‚ๅˆๆ‰“ๆดž๏ผ‰ -4. ๅฆ‚ๆžœ็ซฏๅฃไธไธ€่‡ด๏ผŒๅˆ™ไธบ SYMMETRIC NAT๏ผˆ้œ€่ฆ Relay๏ผ‰ - -## ๅœฐๅ€็ผ–็  - -RustDesk ไฝฟ็”จ `AddrMangle` ๅฏน SocketAddr ่ฟ›่กŒ็ผ–็ ๏ผš - -```rust -// ็ผ–็ ็คบไพ‹ -// IPv4: 4 bytes IP + 2 bytes port = 6 bytes -// IPv6: 16 bytes IP + 2 bytes port = 18 bytes -pub fn encode(addr: SocketAddr) -> Vec; -pub fn decode(bytes: &[u8]) -> SocketAddr; -``` - -## ๅฎ‰ๅ…จๆœบๅˆถ - -### ๆœๅŠกๅ™จ็ญพๅ - -ๅฝ“ๆœๅŠกๅ™จ้…็ฝฎไบ†็ง้’ฅๆ—ถ๏ผŒไผšๅฏน Peer ็š„ๅ…ฌ้’ฅ่ฟ›่กŒ็ญพๅ๏ผš - -```rust -// rustdesk-server/src/rendezvous_server.rs:1160-1182 -async fn get_pk(&mut self, version: &str, id: String) -> Bytes { - if version.is_empty() || self.inner.sk.is_none() { - Bytes::new() - } else { - match self.pm.get(&id).await { - Some(peer) => { - let pk = peer.read().await.pk.clone(); - // ไฝฟ็”จๆœๅŠกๅ™จ็ง้’ฅ็ญพๅ IdPk - sign::sign( - &IdPk { id, pk, ..Default::default() } - .write_to_bytes() - .unwrap_or_default(), - self.inner.sk.as_ref().unwrap(), - ).into() - } - _ => Bytes::new(), - } - } -} -``` - -### IP ๅฐ้” - -ๆœๅŠกๅ™จๅฎž็Žฐไบ† IP ๅฐ้”ๆœบๅˆถ้˜ฒๆญขๆปฅ็”จ๏ผš - -```rust -// rustdesk-server/src/rendezvous_server.rs:866-894 -async fn check_ip_blocker(&self, ip: &str, id: &str) -> bool { - let mut lock = IP_BLOCKER.lock().await; - if let Some(old) = lock.get_mut(ip) { - // ๆฏ็ง’่ฏทๆฑ‚่ถ…่ฟ‡ 30 ๆฌกๅˆ™ๅฐ้” - if counter.0 > 30 { - return false; - } - // ๆฏๅคฉ่ถ…่ฟ‡ 300 ไธชไธๅŒ ID ๅˆ™ๅฐ้” - if counter.0.len() > 300 { - return !is_new; - } - } - true -} -``` - -## ๆ—ถๅบๅ›พ - -### ๅฎŒๆ•ด่ฟžๆŽฅๅปบ็ซ‹ๆต็จ‹ - -``` -ๆŽงๅˆถ็ซฏ Rendezvous Server ่ขซๆŽง็ซฏ - โ”‚ โ”‚ โ”‚ - โ”‚ PunchHoleRequest โ”‚ โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ - โ”‚ โ”‚ PunchHole โ”‚ - โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ PunchHoleSent โ”‚ - โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค - โ”‚ PunchHoleResponse โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ P2P Connection โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ -``` diff --git a/docs/report/rustdesk/03-relay-protocol.md b/docs/report/rustdesk/03-relay-protocol.md deleted file mode 100644 index c672f42d..00000000 --- a/docs/report/rustdesk/03-relay-protocol.md +++ /dev/null @@ -1,318 +0,0 @@ -# Relay ๆœๅŠกๅ™จๅ่ฎฎ - -## ๆฆ‚่ฟฐ - -Relay Server๏ผˆhbbr๏ผ‰ๆ˜ฏ RustDesk ็š„ๆ•ฐๆฎไธญ่ฝฌๆœๅŠกๅ™จ๏ผŒๅฝ“ P2P ่ฟžๆŽฅๆ— ๆณ•ๅปบ็ซ‹ๆ—ถ๏ผˆๅฆ‚ๅŒๆ–น้ƒฝๅœจ Symmetric NAT ๅŽ้ข๏ผ‰๏ผŒๆ‰€ๆœ‰้€šไฟกๆ•ฐๆฎ้€š่ฟ‡ Relay Server ่ฝฌๅ‘ใ€‚ - -## ๆœๅŠกๅ™จๆžถๆž„ - -### ็›‘ๅฌ็ซฏๅฃ - -| ็ซฏๅฃ | ๅ่ฎฎ | ็”จ้€” | -|------|------|------| -| 21117 | TCP | ไธป่ฆไธญ่ฝฌ็ซฏๅฃ | -| 21119 | WebSocket | Web ๅฎขๆˆท็ซฏๆ”ฏๆŒ | - -### ๆ ธๅฟƒ้…็ฝฎ - -```rust -// rustdesk-server/src/relay_server.rs:40-46 -static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 0.66 -static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // 30ๅˆ†้’Ÿ (ms) -static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // 32 Mb/s -static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// 1024 Mb/s -static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// 128 Mb/s -const BLACKLIST_FILE: &str = "blacklist.txt"; -const BLOCKLIST_FILE: &str = "blocklist.txt"; -``` - -## ่ฟžๆŽฅ้…ๅฏนๆœบๅˆถ - -### ้…ๅฏนๅŽŸ็† - -Relay Server ไฝฟ็”จ UUID ๆฅ้…ๅฏนไธคไธชๅฎขๆˆท็ซฏ็š„่ฟžๆŽฅ๏ผš - -1. ็ฌฌไธ€ไธชๅฎขๆˆท็ซฏ่ฟžๆŽฅๅนถๅ‘้€ `RequestRelay` ๆถˆๆฏ๏ผˆๅŒ…ๅซ UUID๏ผ‰ -2. ๆœๅŠกๅ™จๅฐ†่ฏฅ่ฟžๆŽฅๅญ˜ๅ‚จๅœจ็ญ‰ๅพ…้˜Ÿๅˆ—ไธญ -3. ็ฌฌไบŒไธชๅฎขๆˆท็ซฏไฝฟ็”จ็›ธๅŒ็š„ UUID ่ฟžๆŽฅ -4. ๆœๅŠกๅ™จๅฐ†ไธคไธช่ฟžๆŽฅ้…ๅฏน๏ผŒๅผ€ๅง‹่ฝฌๅ‘ๆ•ฐๆฎ - -### ้…ๅฏนๆต็จ‹ - -```rust -// rustdesk-server/src/relay_server.rs:425-462 -async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limiter: Limiter) { - let mut stream = stream; - if let Ok(Some(Ok(bytes))) = timeout(30_000, stream.recv()).await { - if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) { - if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union { - // ้ชŒ่ฏ่ฎธๅฏ่ฏๅฏ†้’ฅ - if !key.is_empty() && rf.licence_key != key { - log::warn!("Relay authentication failed from {}", addr); - return; - } - - if !rf.uuid.is_empty() { - // ๅฐ่ฏ•ๆŸฅๆ‰พ้…ๅฏน - let mut peer = PEERS.lock().await.remove(&rf.uuid); - if let Some(peer) = peer.as_mut() { - // ๆ‰พๅˆฐ้…ๅฏน๏ผŒๅผ€ๅง‹ไธญ่ฝฌ - log::info!("Relay request {} got paired", rf.uuid); - relay(addr, &mut stream, peer, limiter).await; - } else { - // ๆฒกๆ‰พๅˆฐ๏ผŒๅญ˜ๅ‚จ็ญ‰ๅพ…้…ๅฏน - log::info!("New relay request {} from {}", rf.uuid, addr); - PEERS.lock().await.insert(rf.uuid.clone(), Box::new(stream)); - sleep(30.).await; // ็ญ‰ๅพ… 30 ็ง’ - PEERS.lock().await.remove(&rf.uuid); // ่ถ…ๆ—ถ็งป้™ค - } - } - } - } - } -} -``` - -## ๆ•ฐๆฎ่ฝฌๅ‘ - -### ่ฝฌๅ‘้€ป่พ‘ - -```rust -// rustdesk-server/src/relay_server.rs:464-566 -async fn relay( - addr: SocketAddr, - stream: &mut impl StreamTrait, - peer: &mut Box, - total_limiter: Limiter, -) -> ResultType<()> { - let limiter = ::new(SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64); - let blacklist_limiter = ::new(LIMIT_SPEED.load(Ordering::SeqCst) as _); - - loop { - tokio::select! { - // ไปŽ peer ๆŽฅๆ”ถๆ•ฐๆฎ๏ผŒๅ‘้€็ป™ stream - res = peer.recv() => { - if let Some(Ok(bytes)) = res { - // ๅธฆๅฎฝ้™ๅˆถ - if blacked || downgrade { - blacklist_limiter.consume(bytes.len() * 8).await; - } else { - limiter.consume(bytes.len() * 8).await; - } - total_limiter.consume(bytes.len() * 8).await; - stream.send_raw(bytes.into()).await?; - } else { - break; - } - }, - // ไปŽ stream ๆŽฅๆ”ถๆ•ฐๆฎ๏ผŒๅ‘้€็ป™ peer - res = stream.recv() => { - if let Some(Ok(bytes)) = res { - // ๅธฆๅฎฝ้™ๅˆถ - limiter.consume(bytes.len() * 8).await; - total_limiter.consume(bytes.len() * 8).await; - peer.send_raw(bytes.into()).await?; - } else { - break; - } - }, - _ = timer.tick() => { - // ่ถ…ๆ—ถๆฃ€ๆต‹ - if last_recv_time.elapsed().as_secs() > 30 { - bail!("Timeout"); - } - } - } - - // ้™็บงๆฃ€ๆต‹ - if elapsed > DOWNGRADE_START_CHECK && total > elapsed * downgrade_threshold { - downgrade = true; - log::info!("Downgrade {}, exceed threshold", id); - } - } - Ok(()) -} -``` - -### ๅŽŸๅง‹ๆจกๅผ - -ๅฝ“ไธค็ซฏ้ƒฝๆ”ฏๆŒๅŽŸๅง‹ๆจกๅผๆ—ถ๏ผŒ่ทณ่ฟ‡ protobuf ่งฃๆžไปฅๆ้ซ˜ๆ€ง่ƒฝ๏ผš - -```rust -// rustdesk-server/src/relay_server.rs:440-444 -if !stream.is_ws() && !peer.is_ws() { - peer.set_raw(); - stream.set_raw(); - log::info!("Both are raw"); -} -``` - -## ๅธฆๅฎฝๆŽงๅˆถ - -### ๅคš็บง้™้€Ÿ - -1. **ๆ€ปๅธฆๅฎฝ้™ๅˆถ**๏ผšๆ•ดไธชๆœๅŠกๅ™จ็š„ๆ€ปๅธฆๅฎฝ -2. **ๅ•่ฟžๆŽฅ้™ๅˆถ**๏ผšๆฏไธชไธญ่ฝฌ่ฟžๆŽฅ็š„ๅธฆๅฎฝ -3. **้ป‘ๅๅ•้™้€Ÿ**๏ผšๅฏน้ป‘ๅๅ• IP ็š„็‰นๆฎŠ้™ๅˆถ - -### ้™็บงๆœบๅˆถ - -ๅฝ“่ฟžๆŽฅๆŒ็ปญๅ ็”จ้ซ˜ๅธฆๅฎฝๆ—ถ๏ผŒไผš่งฆๅ‘้™็บง๏ผš - -```rust -// ๆกไปถ๏ผš -// 1. ่ฟžๆŽฅๆ—ถ้—ด > DOWNGRADE_START_CHECK (30ๅˆ†้’Ÿ) -// 2. ๅนณๅ‡ๅธฆๅฎฝ > SINGLE_BANDWIDTH * 0.66 -// ้™็บงๅŽไฝฟ็”จ LIMIT_SPEED (32 Mb/s) ้™้€Ÿ -if elapsed > DOWNGRADE_START_CHECK.load(Ordering::SeqCst) - && !downgrade - && total > elapsed * downgrade_threshold -{ - downgrade = true; -} -``` - -## ๅฎ‰ๅ…จๆŽงๅˆถ - -### ้ป‘ๅๅ• - -็”จไบŽ้™้€Ÿ็‰นๅฎš IP๏ผš - -``` -# blacklist.txt -192.168.1.100 -10.0.0.50 -``` - -### ๅฐ้”ๅๅ• - -็”จไบŽๅฎŒๅ…จๆ‹’็ป็‰นๅฎš IP๏ผš - -``` -# blocklist.txt -1.2.3.4 -5.6.7.8 -``` - -### ่ฟ่กŒๆ—ถ็ฎก็†ๅ‘ฝไปค - -้€š่ฟ‡ๆœฌๅœฐ TCP ่ฟžๆŽฅ๏ผˆไป…้™ localhost๏ผ‰ๅ‘้€ๅ‘ฝไปค๏ผš - -```rust -// rustdesk-server/src/relay_server.rs:152-324 -match fds.next() { - Some("h") => // ๅธฎๅŠฉ - Some("blacklist-add" | "ba") => // ๆทปๅŠ ้ป‘ๅๅ• - Some("blacklist-remove" | "br") => // ็งป้™ค้ป‘ๅๅ• - Some("blacklist" | "b") => // ๆŸฅ็œ‹้ป‘ๅๅ• - Some("blocklist-add" | "Ba") => // ๆทปๅŠ ๅฐ้”ๅๅ• - Some("blocklist-remove" | "Br") => // ็งป้™คๅฐ้”ๅๅ• - Some("blocklist" | "B") => // ๆŸฅ็œ‹ๅฐ้”ๅๅ• - Some("downgrade-threshold" | "dt") => // ่ฎพ็ฝฎ้™็บง้˜ˆๅ€ผ - Some("downgrade-start-check" | "t") => // ่ฎพ็ฝฎ้™็บงๆฃ€ๆต‹ๆ—ถ้—ด - Some("limit-speed" | "ls") => // ่ฎพ็ฝฎ้™้€Ÿ - Some("total-bandwidth" | "tb") => // ่ฎพ็ฝฎๆ€ปๅธฆๅฎฝ - Some("single-bandwidth" | "sb") => // ่ฎพ็ฝฎๅ•่ฟžๆŽฅๅธฆๅฎฝ - Some("usage" | "u") => // ๆŸฅ็œ‹ไฝฟ็”จ็ปŸ่ฎก -} -``` - -## ๅ่ฎฎๆถˆๆฏ - -### RequestRelay - -็”จไบŽๅปบ็ซ‹ไธญ่ฝฌ่ฟžๆŽฅ็š„่ฏทๆฑ‚ๆถˆๆฏ๏ผš - -```protobuf -message RequestRelay { - string id = 1; // ็›ฎๆ ‡ Peer ID - string uuid = 2; // ่ฟžๆŽฅ UUID๏ผˆ้…ๅฏน็”จ๏ผ‰ - bytes socket_addr = 3; // ๆœฌ็ซฏๅœฐๅ€ - string relay_server = 4; // Relay ๆœๅŠกๅ™จ - bool secure = 5; // ๆ˜ฏๅฆๅŠ ๅฏ† - string licence_key = 6; // ่ฎธๅฏ่ฏๅฏ†้’ฅ - ConnType conn_type = 7; // ่ฟžๆŽฅ็ฑปๅž‹ - string token = 8; // ่ฎค่ฏไปค็‰Œ -} -``` - -## ๆ—ถๅบๅ›พ - -### ไธญ่ฝฌ่ฟžๆŽฅๅปบ็ซ‹ - -``` -ๅฎขๆˆท็ซฏ A Relay Server ๅฎขๆˆท็ซฏ B - โ”‚ โ”‚ โ”‚ - โ”‚ RequestRelay(uuid) โ”‚ โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ (ๅญ˜ๅ‚จ็ญ‰ๅพ…้…ๅฏน) โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ RequestRelay(uuid) โ”‚ - โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ (้…ๅฏนๆˆๅŠŸ) โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๆ•ฐๆฎ่ฝฌๅ‘ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ โ”‚ -``` - -### ๆ•ฐๆฎ่ฝฌๅ‘ - -``` -ๅฎขๆˆท็ซฏ A Relay Server ๅฎขๆˆท็ซฏ B - โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€โ”€[ๆ•ฐๆฎ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ โ”‚ - โ”‚ โ”‚ โ”€โ”€โ”€โ”€[ๆ•ฐๆฎ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ โ—„โ”€โ”€โ”€[ๆ•ฐๆฎ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ - โ”‚ โ—„โ”€โ”€โ”€[ๆ•ฐๆฎ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ -``` - -## ๆ€ง่ƒฝไผ˜ๅŒ– - -### ้›ถๆ‹ท่ด - -ไฝฟ็”จ `Bytes` ็ฑปๅž‹ๅ‡ๅฐ‘ๅ†…ๅญ˜ๆ‹ท่ด๏ผš - -```rust -async fn send_raw(&mut self, bytes: Bytes) -> ResultType<()>; -``` - -### WebSocket ๆ”ฏๆŒ - -ๆ”ฏๆŒ WebSocket ๅ่ฎฎไปฅ็ฉฟ่ถŠ้˜ฒ็ซๅข™๏ผš - -```rust -#[async_trait] -impl StreamTrait for tokio_tungstenite::WebSocketStream { - async fn recv(&mut self) -> Option> { - if let Some(msg) = self.next().await { - match msg { - Ok(tungstenite::Message::Binary(bytes)) => { - Some(Ok(bytes[..].into())) - } - // ... - } - } - } -} -``` - -## ็›‘ๆŽงๆŒ‡ๆ ‡ - -ๆœๅŠกๅ™จ่ทŸ่ธชไปฅไธ‹ๆŒ‡ๆ ‡๏ผš - -| ๆŒ‡ๆ ‡ | ่ฏดๆ˜Ž | -|------|------| -| elapsed | ่ฟžๆŽฅๆŒ็ปญๆ—ถ้—ด (ms) | -| total | ๆ€ปไผ ่พ“ๆ•ฐๆฎ้‡ (bit) | -| highest | ๆœ€้ซ˜็žฌๆ—ถ้€Ÿ็އ (kb/s) | -| speed | ๅฝ“ๅ‰้€Ÿ็އ (kb/s) | - -้€š่ฟ‡ `usage` ๅ‘ฝไปคๆŸฅ็œ‹๏ผš - -``` -192.168.1.100:12345: 3600s 1024.00MB 50000kb/s 45000kb/s 42000kb/s -``` diff --git a/docs/report/rustdesk/04-p2p-connection.md b/docs/report/rustdesk/04-p2p-connection.md deleted file mode 100644 index c41e8fa0..00000000 --- a/docs/report/rustdesk/04-p2p-connection.md +++ /dev/null @@ -1,424 +0,0 @@ -# P2P ่ฟžๆŽฅๆต็จ‹ - -## ๆฆ‚่ฟฐ - -RustDesk ไผ˜ๅ…ˆๅฐ่ฏ•ๅปบ็ซ‹ P2P ็›ด่ฟž๏ผŒๅชๆœ‰ๅœจ็›ด่ฟžๅคฑ่ดฅๆ—ถๆ‰ไฝฟ็”จ Relay ไธญ่ฝฌใ€‚P2P ่ฟžๆŽฅๆ”ฏๆŒๅคš็งๆ–นๅผ๏ผš -- TCP ๆ‰“ๆดž -- UDP ๆ‰“ๆดž๏ผˆKCP๏ผ‰ -- ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž -- IPv6 ็›ด่ฟž - -## ่ฟžๆŽฅๅ†ณ็ญ–ๆต็จ‹ - -``` - ๅผ€ๅง‹่ฟžๆŽฅ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ ๆ˜ฏๅฆๅผบๅˆถ Relay๏ผŸโ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - ๆ˜ฏ โ”‚ ๅฆ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ–ผ โ–ผ - ไฝฟ็”จ Relay ๆฃ€ๆŸฅ NAT ็ฑปๅž‹ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ - ๅŒๆ–น้ƒฝๆ˜ฏๅฏน็งฐ NAT๏ผŸ ๆœ‰ไธ€ๆ–นๆ˜ฏๅฏ็ฉฟ้€ NAT - โ”‚ โ”‚ - ๆ˜ฏ โ”‚ โ”‚ - โ–ผ โ–ผ - ไฝฟ็”จ Relay ๅฐ่ฏ• P2P ่ฟžๆŽฅ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ - ๅŒไธ€ๅฑ€ๅŸŸ็ฝ‘๏ผŸ ไธๅŒ็ฝ‘็ปœ - โ”‚ โ”‚ - ๆ˜ฏ โ”‚ โ”‚ - โ–ผ โ–ผ - ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž ๅฐ่ฏ•ๆ‰“ๆดž - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ - TCP ๆ‰“ๆดžๆˆๅŠŸ๏ผŸ UDP ๆ‰“ๆดžๆˆๅŠŸ๏ผŸ - โ”‚ โ”‚ - ๆ˜ฏ โ”‚ ๅฆ ๆ˜ฏ โ”‚ ๅฆ - โ–ผ โ”‚ โ–ผ โ”‚ - TCP P2P ่ฟžๆŽฅ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ KCP P2P ่ฟžๆŽฅ โ”‚ - โ–ผ - ไฝฟ็”จ Relay -``` - -## ๅฎขๆˆท็ซฏ่ฟžๆŽฅๅ…ฅๅฃ - -```rust -// rustdesk/src/client.rs:188-230 -impl Client { - pub async fn start( - peer: &str, - key: &str, - token: &str, - conn_type: ConnType, - interface: impl Interface, - ) -> ResultType<...> { - // ๆฃ€ๆŸฅๆ˜ฏๅฆไธบ IP ็›ด่ฟž - if hbb_common::is_ip_str(peer) { - return connect_tcp_local(check_port(peer, RELAY_PORT + 1), None, CONNECT_TIMEOUT).await; - } - - // ๆฃ€ๆŸฅๆ˜ฏๅฆไธบๅŸŸๅ:็ซฏๅฃๆ ผๅผ - if hbb_common::is_domain_port_str(peer) { - return connect_tcp_local(peer, None, CONNECT_TIMEOUT).await; - } - - // ้€š่ฟ‡ Rendezvous Server ่ฟžๆŽฅ - let (rendezvous_server, servers, _) = crate::get_rendezvous_server(1_000).await; - Self::_start_inner(peer, key, token, conn_type, interface, rendezvous_server, servers).await - } -} -``` - -## ่ขซๆŽง็ซฏๅค„็†่ฟžๆŽฅ่ฏทๆฑ‚ - -### ๅค„็† PunchHole ๆถˆๆฏ - -```rust -// rustdesk/src/rendezvous_mediator.rs:554-619 -async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> { - let peer_addr = AddrMangle::decode(&ph.socket_addr); - let relay_server = self.get_relay_server(ph.relay_server); - - // ๅˆคๆ–ญๆ˜ฏๅฆ้œ€่ฆ Relay - if ph.nat_type.enum_value() == Ok(NatType::SYMMETRIC) - || Config::get_nat_type() == NatType::SYMMETRIC as i32 - || relay - { - // ไฝฟ็”จ Relay - let uuid = Uuid::new_v4().to_string(); - return self.create_relay(ph.socket_addr, relay_server, uuid, server, true, true).await; - } - - // ๅฐ่ฏ• UDP ๆ‰“ๆดž - if ph.udp_port > 0 { - peer_addr.set_port(ph.udp_port as u16); - self.punch_udp_hole(peer_addr, server, msg_punch).await?; - return Ok(()); - } - - // ๅฐ่ฏ• TCP ๆ‰“ๆดž - log::debug!("Punch tcp hole to {:?}", peer_addr); - let socket = { - let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; - let local_addr = socket.local_addr(); - // ๅ…ณ้”ฎๆญฅ้ชค๏ผšๅฐ่ฏ•่ฟžๆŽฅๅฏนๆ–น๏ผŒ่ฎฉ NAT ๅปบ็ซ‹ๆ˜ ๅฐ„ - allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); - socket - }; - - // ๅ‘้€ PunchHoleSent ๅ‘Š็Ÿฅ Rendezvous Server - let mut msg_out = Message::new(); - msg_out.set_punch_hole_sent(PunchHoleSent { - socket_addr: ph.socket_addr, - id: Config::get_id(), - relay_server, - nat_type: nat_type.into(), - version: crate::VERSION.to_owned(), - }); - socket.send_raw(msg_out.write_to_bytes()?).await?; - - // ๆŽฅๅ—ๆŽงๅˆถ็ซฏ่ฟžๆŽฅ - crate::accept_connection(server.clone(), socket, peer_addr, true).await; - Ok(()) -} -``` - -### ๅค„็† FetchLocalAddr๏ผˆๅฑ€ๅŸŸ็ฝ‘่ฟžๆŽฅ๏ผ‰ - -```rust -// rustdesk/src/rendezvous_mediator.rs:481-552 -async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> { - let peer_addr = AddrMangle::decode(&fla.socket_addr); - let relay_server = self.get_relay_server(fla.relay_server.clone()); - - // ๅฐ่ฏ•ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž - if is_ipv4(&self.addr) && !relay && !config::is_disable_tcp_listen() { - if let Err(err) = self.handle_intranet_(fla.clone(), server.clone(), relay_server.clone()).await { - log::debug!("Failed to handle intranet: {:?}, will try relay", err); - } else { - return Ok(()); - } - } - - // ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟžๅคฑ่ดฅ๏ผŒไฝฟ็”จ Relay - let uuid = Uuid::new_v4().to_string(); - self.create_relay(fla.socket_addr, relay_server, uuid, server, true, true).await -} - -async fn handle_intranet_(&self, fla: FetchLocalAddr, server: ServerPtr, relay_server: String) -> ResultType<()> { - let peer_addr = AddrMangle::decode(&fla.socket_addr); - let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; - let local_addr = socket.local_addr(); - - // ๅ‘้€ๆœฌๅœฐๅœฐๅ€็ป™ Rendezvous Server - let mut msg_out = Message::new(); - msg_out.set_local_addr(LocalAddr { - id: Config::get_id(), - socket_addr: AddrMangle::encode(peer_addr).into(), - local_addr: AddrMangle::encode(local_addr).into(), - relay_server, - version: crate::VERSION.to_owned(), - }); - socket.send_raw(msg_out.write_to_bytes()?).await?; - - // ๆŽฅๅ—่ฟžๆŽฅ - crate::accept_connection(server.clone(), socket, peer_addr, true).await; - Ok(()) -} -``` - -## UDP ๆ‰“ๆดž (KCP) - -### ๆ‰“ๆดžๅŽŸ็† - -UDP ๆ‰“ๆดžๅˆฉ็”จ NAT ็š„็ซฏๅฃๆ˜ ๅฐ„็‰นๆ€ง๏ผš - -1. A ๅ‘ Rendezvous Server ๆณจๅ†Œ๏ผŒNAT ๅˆ›ๅปบๆ˜ ๅฐ„ `A_internal:port1 โ†’ A_external:port2` -2. B ๅŒๆ ทๆณจๅ†Œ๏ผŒๅˆ›ๅปบๆ˜ ๅฐ„ `B_internal:port3 โ†’ B_external:port4` -3. A ๅ‘ B ็š„ๅค–้ƒจๅœฐๅ€ๅ‘้€ UDP ๅŒ…๏ผŒA ็š„ NAT ๅˆ›ๅปบๅˆฐ B ็š„ๆ˜ ๅฐ„ -4. B ๅ‘ A ็š„ๅค–้ƒจๅœฐๅ€ๅ‘้€ UDP ๅŒ…๏ผŒB ็š„ NAT ๅˆ›ๅปบๅˆฐ A ็š„ๆ˜ ๅฐ„ -5. ๅฆ‚ๆžœ NAT ไธๆ˜ฏ Symmetric ็ฑปๅž‹๏ผŒๅŒๆ–น็š„ๅŒ…ๅฏไปฅๅˆฐ่พพๅฏนๆ–น - -```rust -// rustdesk/src/rendezvous_mediator.rs:621-642 -async fn punch_udp_hole( - &self, - peer_addr: SocketAddr, - server: ServerPtr, - msg_punch: PunchHoleSent, -) -> ResultType<()> { - let mut msg_out = Message::new(); - msg_out.set_punch_hole_sent(msg_punch); - let (socket, addr) = new_direct_udp_for(&self.host).await?; - let data = msg_out.write_to_bytes()?; - - // ๅ‘้€ๅˆฐ Rendezvous Server - socket.send_to(&data, addr).await?; - - // ๅคšๆฌกๅฐ่ฏ•ๅ‘้€ไปฅๅขžๅŠ ๆˆๅŠŸ็އ - let socket_cloned = socket.clone(); - tokio::spawn(async move { - for _ in 0..2 { - let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.; - hbb_common::sleep(tm).await; - socket.send_to(&data, addr).await.ok(); - } - }); - - // ็ญ‰ๅพ…ๅฏนๆ–น่ฟžๆŽฅ - udp_nat_listen(socket_cloned.clone(), peer_addr, peer_addr, server).await?; - Ok(()) -} -``` - -### KCP ๅ่ฎฎ - -RustDesk ๅœจ UDP ไธŠไฝฟ็”จ KCP ๅ่ฎฎๆไพ›ๅฏ้ ไผ ่พ“๏ผš - -```rust -// rustdesk/src/rendezvous_mediator.rs:824-851 -async fn udp_nat_listen( - socket: Arc, - peer_addr: SocketAddr, - peer_addr_v4: SocketAddr, - server: ServerPtr, -) -> ResultType<()> { - socket.connect(peer_addr).await?; - - // ๆ‰ง่กŒ UDP ๆ‰“ๆดž - let res = crate::punch_udp(socket.clone(), true).await?; - - // ๅปบ็ซ‹ KCP ๆต - let stream = crate::kcp_stream::KcpStream::accept( - socket, - Duration::from_millis(CONNECT_TIMEOUT as _), - res, - ).await?; - - // ๅˆ›ๅปบ่ฟžๆŽฅ - crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?; - Ok(()) -} -``` - -## TCP ๆ‰“ๆดž - -### ๅŽŸ็† - -TCP ๆ‰“ๆดžๆฏ” UDP ๆ›ด้šพ๏ผŒๅ› ไธบ TCP ้œ€่ฆไธ‰ๆฌกๆกๆ‰‹ใ€‚ๅŸบๆœฌๆ€่ทฏ๏ผš - -1. A ๅ’Œ B ้ƒฝๅฐ่ฏ•ๅŒๆ—ถๅ‘ๅฏนๆ–นๅ‘่ตท่ฟžๆŽฅ -2. ็ฌฌไธ€ไธช SYN ๅŒ…ไผš่ขซๅฏนๆ–น็š„ NAT ไธขๅผƒ๏ผˆๅ› ไธบๆฒกๆœ‰ๆ˜ ๅฐ„๏ผ‰ -3. ไฝ†่ฟ™ไธช SYN ๅŒ…ไผšๅœจ A ็š„ NAT ไธŠๅˆ›ๅปบๆ˜ ๅฐ„ -4. ๅฝ“ B ็š„ SYN ๅŒ…ๅˆฐ่พพ A ็š„ NAT ๆ—ถ๏ผŒ็”ฑไบŽๅทฒๆœ‰ๆ˜ ๅฐ„๏ผŒไผš่ขซ่ฝฌๅ‘็ป™ A -5. ่ฟžๆŽฅๅปบ็ซ‹ - -### ๅฎž็Žฐ - -```rust -// rustdesk/src/rendezvous_mediator.rs:604-617 -log::debug!("Punch tcp hole to {:?}", peer_addr); -let mut socket = { - let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; - let local_addr = socket.local_addr(); - // ๅ…ณ้”ฎ๏ผšไฝฟ็”จ็›ธๅŒ็š„ๆœฌๅœฐๅœฐๅ€ๅฐ่ฏ•่ฟžๆŽฅๅฏนๆ–น - // ่ฟ™ไผšๅœจ NAT ไธŠๅˆ›ๅปบๆ˜ ๅฐ„๏ผŒไฝฟๅฏนๆ–น็š„่ฟžๆŽฅ่ฏทๆฑ‚่ƒฝๅคŸๅˆฐ่พพ - allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); - socket -}; -``` - -## Relay ่ฟžๆŽฅ - -ๅฝ“ P2P ๅคฑ่ดฅๆ—ถ๏ผŒไฝฟ็”จ Relay๏ผš - -```rust -// rustdesk/src/rendezvous_mediator.rs:434-479 -async fn create_relay( - &self, - socket_addr: Vec, - relay_server: String, - uuid: String, - server: ServerPtr, - secure: bool, - initiate: bool, -) -> ResultType<()> { - let peer_addr = AddrMangle::decode(&socket_addr); - log::info!( - "create_relay requested from {:?}, relay_server: {}, uuid: {}, secure: {}", - peer_addr, relay_server, uuid, secure, - ); - - // ่ฟžๆŽฅ Rendezvous Server ๅ‘้€ RelayResponse - let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; - let mut msg_out = Message::new(); - let mut rr = RelayResponse { - socket_addr: socket_addr.into(), - version: crate::VERSION.to_owned(), - ..Default::default() - }; - if initiate { - rr.uuid = uuid.clone(); - rr.relay_server = relay_server.clone(); - rr.set_id(Config::get_id()); - } - msg_out.set_relay_response(rr); - socket.send(&msg_out).await?; - - // ่ฟžๆŽฅ Relay Server - crate::create_relay_connection( - server, - relay_server, - uuid, - peer_addr, - secure, - is_ipv4(&self.addr), - ).await; - Ok(()) -} -``` - -## IPv6 ๆ”ฏๆŒ - -RustDesk ไผ˜ๅ…ˆๅฐ่ฏ• IPv6 ่ฟžๆŽฅ๏ผš - -```rust -// rustdesk/src/rendezvous_mediator.rs:808-822 -async fn start_ipv6( - peer_addr_v6: SocketAddr, - peer_addr_v4: SocketAddr, - server: ServerPtr, -) -> bytes::Bytes { - crate::test_ipv6().await; - if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await { - let server = server.clone(); - tokio::spawn(async move { - allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await); - }); - return local_addr_v6; - } - Default::default() -} -``` - -## ่ฟžๆŽฅ็Šถๆ€ๆœบ - -``` - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” - โ”‚ ็ญ‰ๅพ…่ฟžๆŽฅ โ”‚โ”€โ”€โ”€โ”€โ”€โ”€PunchHoleRequestโ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ๆญฃๅœจ่ฟžๆŽฅ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ P2P TCP โ”‚ โ”‚ P2P UDP/KCP โ”‚ โ”‚ Relay โ”‚ - โ”‚ ่ฟžๆŽฅไธญ โ”‚ โ”‚ ่ฟžๆŽฅไธญ โ”‚ โ”‚ ่ฟžๆŽฅไธญ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - ๆˆๅŠŸ โ”‚ ๅคฑ่ดฅ ๆˆๅŠŸ โ”‚ ๅคฑ่ดฅ ๆˆๅŠŸ โ”‚ ๅคฑ่ดฅ - โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ - โ–ผ โ”‚ โ–ผ โ”‚ โ–ผ โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ - โ”‚ๅทฒ่ฟžๆŽฅ โ”‚โ”‚ โ”‚ๅทฒ่ฟžๆŽฅ โ”‚โ”‚ โ”‚ๅทฒ่ฟžๆŽฅ โ”‚โ”‚ - โ”‚(็›ด่ฟž) โ”‚โ”‚ โ”‚(UDP) โ”‚โ”‚ โ”‚(ไธญ่ฝฌ) โ”‚โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ - โ”‚ โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บๅฐ่ฏ• Relayโ—„โ”€โ”€โ”€โ”˜ โ”‚ - โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ็›ดๆŽฅ่ฟžๆŽฅๆจกๅผ - -็”จๆˆทๅฏไปฅ้…็ฝฎๅ…่ฎธ็›ดๆŽฅ TCP ่ฟžๆŽฅ๏ผˆไธ็ป่ฟ‡ Rendezvous Server๏ผ‰๏ผš - -```rust -// rustdesk/src/rendezvous_mediator.rs:727-792 -async fn direct_server(server: ServerPtr) { - let mut listener = None; - let mut port = get_direct_port(); // ้ป˜่ฎค 21118 - - loop { - let disabled = !option2bool(OPTION_DIRECT_SERVER, &Config::get_option(OPTION_DIRECT_SERVER)); - - if !disabled && listener.is_none() { - match hbb_common::tcp::listen_any(port as _).await { - Ok(l) => { - listener = Some(l); - log::info!("Direct server listening on: {:?}", l.local_addr()); - } - Err(err) => { - log::error!("Failed to start direct server: {}", err); - } - } - } - - if let Some(l) = listener.as_mut() { - if let Ok(Ok((stream, addr))) = hbb_common::timeout(1000, l.accept()).await { - stream.set_nodelay(true).ok(); - log::info!("direct access from {}", addr); - let server = server.clone(); - tokio::spawn(async move { - crate::server::create_tcp_connection(server, stream, addr, false).await - }); - } - } - } -} -``` diff --git a/docs/report/rustdesk/05-message-format.md b/docs/report/rustdesk/05-message-format.md deleted file mode 100644 index 12028efd..00000000 --- a/docs/report/rustdesk/05-message-format.md +++ /dev/null @@ -1,574 +0,0 @@ -# ๆถˆๆฏๆ ผๅผๅฎšไน‰ - -## ๆฆ‚่ฟฐ - -RustDesk ไฝฟ็”จ Protocol Buffers (protobuf) ๅฎšไน‰ๆ‰€ๆœ‰็ฝ‘็ปœๆถˆๆฏๆ ผๅผใ€‚ไธป่ฆๆœ‰ไธคไธช proto ๆ–‡ไปถ๏ผš - -- `rendezvous.proto` - Rendezvous/Relay ๆœๅŠกๅ™จ้€šไฟกๆถˆๆฏ -- `message.proto` - ๅฎขๆˆท็ซฏไน‹้—ด้€šไฟกๆถˆๆฏ - -## Rendezvous ๆถˆๆฏ (rendezvous.proto) - -### ้กถๅฑ‚ๆถˆๆฏ - -```protobuf -message RendezvousMessage { - oneof union { - RegisterPeer register_peer = 6; - RegisterPeerResponse register_peer_response = 7; - PunchHoleRequest punch_hole_request = 8; - PunchHole punch_hole = 9; - PunchHoleSent punch_hole_sent = 10; - PunchHoleResponse punch_hole_response = 11; - FetchLocalAddr fetch_local_addr = 12; - LocalAddr local_addr = 13; - ConfigUpdate configure_update = 14; - RegisterPk register_pk = 15; - RegisterPkResponse register_pk_response = 16; - SoftwareUpdate software_update = 17; - RequestRelay request_relay = 18; - RelayResponse relay_response = 19; - TestNatRequest test_nat_request = 20; - TestNatResponse test_nat_response = 21; - PeerDiscovery peer_discovery = 22; - OnlineRequest online_request = 23; - OnlineResponse online_response = 24; - KeyExchange key_exchange = 25; - HealthCheck hc = 26; - } -} -``` - -### ๆณจๅ†Œ็›ธๅ…ณ - -```protobuf -// Peer ๆณจๅ†Œ -message RegisterPeer { - string id = 1; // Peer ID - int32 serial = 2; // ้…็ฝฎๅบๅˆ—ๅท -} - -message RegisterPeerResponse { - bool request_pk = 2; // ๆ˜ฏๅฆ้œ€่ฆๆณจๅ†Œๅ…ฌ้’ฅ -} - -// ๅ…ฌ้’ฅๆณจๅ†Œ -message RegisterPk { - string id = 1; // Peer ID - bytes uuid = 2; // ่ฎพๅค‡ UUID - bytes pk = 3; // Ed25519 ๅ…ฌ้’ฅ - string old_id = 4; // ๆ—ง ID -} - -message RegisterPkResponse { - enum Result { - OK = 0; - UUID_MISMATCH = 2; - ID_EXISTS = 3; - TOO_FREQUENT = 4; - INVALID_ID_FORMAT = 5; - NOT_SUPPORT = 6; - SERVER_ERROR = 7; - } - Result result = 1; - int32 keep_alive = 2; -} -``` - -### ่ฟžๆŽฅๅ่ฐƒ็›ธๅ…ณ - -```protobuf -// ่ฟžๆŽฅ็ฑปๅž‹ -enum ConnType { - DEFAULT_CONN = 0; - FILE_TRANSFER = 1; - PORT_FORWARD = 2; - RDP = 3; - VIEW_CAMERA = 4; -} - -// NAT ็ฑปๅž‹ -enum NatType { - UNKNOWN_NAT = 0; - ASYMMETRIC = 1; // ๅฏๆ‰“ๆดž - SYMMETRIC = 2; // ้œ€่ฆไธญ่ฝฌ -} - -// Punch Hole ่ฏทๆฑ‚ -message PunchHoleRequest { - string id = 1; // ็›ฎๆ ‡ Peer ID - NatType nat_type = 2; - string licence_key = 3; - ConnType conn_type = 4; - string token = 5; - string version = 6; -} - -// Punch Hole ๅ“ๅบ” -message PunchHoleResponse { - bytes socket_addr = 1; // ็›ฎๆ ‡ๅœฐๅ€ - bytes pk = 2; // ๅ…ฌ้’ฅ๏ผˆๅทฒ็ญพๅ๏ผ‰ - enum Failure { - ID_NOT_EXIST = 0; - OFFLINE = 2; - LICENSE_MISMATCH = 3; - LICENSE_OVERUSE = 4; - } - Failure failure = 3; - string relay_server = 4; - oneof union { - NatType nat_type = 5; - bool is_local = 6; - } - string other_failure = 7; - int32 feedback = 8; -} - -// ๆœๅŠกๅ™จ่ฝฌๅ‘็ป™่ขซๆŽง็ซฏ -message PunchHole { - bytes socket_addr = 1; // ๆŽงๅˆถ็ซฏๅœฐๅ€ - string relay_server = 2; - NatType nat_type = 3; -} - -// ่ขซๆŽง็ซฏๅ‘้€็ป™ๆœๅŠกๅ™จ -message PunchHoleSent { - bytes socket_addr = 1; - string id = 2; - string relay_server = 3; - NatType nat_type = 4; - string version = 5; -} -``` - -### Relay ็›ธๅ…ณ - -```protobuf -// Relay ่ฏทๆฑ‚ -message RequestRelay { - string id = 1; - string uuid = 2; // ้…ๅฏน UUID - bytes socket_addr = 3; - string relay_server = 4; - bool secure = 5; - string licence_key = 6; - ConnType conn_type = 7; - string token = 8; -} - -// Relay ๅ“ๅบ” -message RelayResponse { - bytes socket_addr = 1; - string uuid = 2; - string relay_server = 3; - oneof union { - string id = 4; - bytes pk = 5; - } - string refuse_reason = 6; - string version = 7; - int32 feedback = 9; -} -``` - -## ไผš่ฏๆถˆๆฏ (message.proto) - -### ้กถๅฑ‚ๆถˆๆฏ - -```protobuf -message Message { - oneof union { - SignedId signed_id = 3; - PublicKey public_key = 4; - TestDelay test_delay = 5; - VideoFrame video_frame = 6; - LoginRequest login_request = 7; - LoginResponse login_response = 8; - Hash hash = 9; - MouseEvent mouse_event = 10; - AudioFrame audio_frame = 11; - CursorData cursor_data = 12; - CursorPosition cursor_position = 13; - uint64 cursor_id = 14; - KeyEvent key_event = 15; - Clipboard clipboard = 16; - FileAction file_action = 17; - FileResponse file_response = 18; - Misc misc = 19; - Cliprdr cliprdr = 20; - MessageBox message_box = 21; - SwitchSidesResponse switch_sides_response = 22; - VoiceCallRequest voice_call_request = 23; - VoiceCallResponse voice_call_response = 24; - PeerInfo peer_info = 25; - PointerDeviceEvent pointer_device_event = 26; - Auth2FA auth_2fa = 27; - MultiClipboards multi_clipboards = 28; - } -} -``` - -### ่ฎค่ฏ็›ธๅ…ณ - -```protobuf -// ID ๅ’Œๅ…ฌ้’ฅ -message IdPk { - string id = 1; - bytes pk = 2; -} - -// ๅฏ†้’ฅไบคๆข -message PublicKey { - bytes asymmetric_value = 1; // X25519 ๅ…ฌ้’ฅ - bytes symmetric_value = 2; // ๅŠ ๅฏ†็š„ๅฏน็งฐๅฏ†้’ฅ -} - -// ็ญพๅ็š„ ID -message SignedId { - bytes id = 1; // ็ญพๅ็š„ IdPk -} - -// ๅฏ†็ ๅ“ˆๅธŒๆŒ‘ๆˆ˜ -message Hash { - string salt = 1; - string challenge = 2; -} - -// ็™ปๅฝ•่ฏทๆฑ‚ -message LoginRequest { - string username = 1; - bytes password = 2; // ๅŠ ๅฏ†็š„ๅฏ†็  - string my_id = 4; - string my_name = 5; - OptionMessage option = 6; - oneof union { - FileTransfer file_transfer = 7; - PortForward port_forward = 8; - ViewCamera view_camera = 15; - } - bool video_ack_required = 9; - uint64 session_id = 10; - string version = 11; - OSLogin os_login = 12; - string my_platform = 13; - bytes hwid = 14; -} - -// ็™ปๅฝ•ๅ“ๅบ” -message LoginResponse { - oneof union { - string error = 1; - PeerInfo peer_info = 2; - } - bool enable_trusted_devices = 3; -} - -// 2FA ่ฎค่ฏ -message Auth2FA { - string code = 1; - bytes hwid = 2; -} -``` - -### ่ง†้ข‘็›ธๅ…ณ - -```protobuf -// ็ผ–็ ๅŽ็š„่ง†้ข‘ๅธง -message EncodedVideoFrame { - bytes data = 1; - bool key = 2; // ๆ˜ฏๅฆๅ…ณ้”ฎๅธง - int64 pts = 3; // ๆ—ถ้—ดๆˆณ -} - -message EncodedVideoFrames { - repeated EncodedVideoFrame frames = 1; -} - -// ่ง†้ข‘ๅธง -message VideoFrame { - oneof union { - EncodedVideoFrames vp9s = 6; - RGB rgb = 7; - YUV yuv = 8; - EncodedVideoFrames h264s = 10; - EncodedVideoFrames h265s = 11; - EncodedVideoFrames vp8s = 12; - EncodedVideoFrames av1s = 13; - } - int32 display = 14; // ๆ˜พ็คบๅ™จ็ดขๅผ• -} - -// ๆ˜พ็คบไฟกๆฏ -message DisplayInfo { - sint32 x = 1; - sint32 y = 2; - int32 width = 3; - int32 height = 4; - string name = 5; - bool online = 6; - bool cursor_embedded = 7; - Resolution original_resolution = 8; - double scale = 9; -} -``` - -### ่พ“ๅ…ฅ็›ธๅ…ณ - -```protobuf -// ้ผ ๆ ‡ไบ‹ไปถ -message MouseEvent { - int32 mask = 1; // ๆŒ‰้’ฎๆŽฉ็  - sint32 x = 2; - sint32 y = 3; - repeated ControlKey modifiers = 4; -} - -// ้”ฎ็›˜ไบ‹ไปถ -message KeyEvent { - bool down = 1; // ๆŒ‰ไธ‹/้‡Šๆ”พ - bool press = 2; // ๅ•ๅ‡ป - oneof union { - ControlKey control_key = 3; - uint32 chr = 4; // ๅญ—็ฌฆ็  - uint32 unicode = 5; // Unicode - string seq = 6; // ๅญ—็ฌฆๅบๅˆ— - uint32 win2win_hotkey = 7; - } - repeated ControlKey modifiers = 8; - KeyboardMode mode = 9; -} - -// ้”ฎ็›˜ๆจกๅผ -enum KeyboardMode { - Legacy = 0; - Map = 1; - Translate = 2; - Auto = 3; -} - -// ๆŽงๅˆถ้”ฎๆžšไธพ๏ผˆ้ƒจๅˆ†๏ผ‰ -enum ControlKey { - Unknown = 0; - Alt = 1; - Backspace = 2; - CapsLock = 3; - Control = 4; - Delete = 5; - // ... ๆ›ดๅคšๆŒ‰้”ฎ - CtrlAltDel = 100; - LockScreen = 101; -} -``` - -### ้Ÿณ้ข‘็›ธๅ…ณ - -```protobuf -// ้Ÿณ้ข‘ๆ ผๅผ -message AudioFormat { - uint32 sample_rate = 1; - uint32 channels = 2; -} - -// ้Ÿณ้ข‘ๅธง -message AudioFrame { - bytes data = 1; // Opus ็ผ–็ ๆ•ฐๆฎ -} -``` - -### ๅ‰ช่ดดๆฟ็›ธๅ…ณ - -```protobuf -// ๅ‰ช่ดดๆฟๆ ผๅผ -enum ClipboardFormat { - Text = 0; - Rtf = 1; - Html = 2; - ImageRgba = 21; - ImagePng = 22; - ImageSvg = 23; - Special = 31; -} - -// ๅ‰ช่ดดๆฟๅ†…ๅฎน -message Clipboard { - bool compress = 1; - bytes content = 2; - int32 width = 3; - int32 height = 4; - ClipboardFormat format = 5; - string special_name = 6; -} - -message MultiClipboards { - repeated Clipboard clipboards = 1; -} -``` - -### ๆ–‡ไปถไผ ่พ“็›ธๅ…ณ - -```protobuf -// ๆ–‡ไปถๆ“ไฝœ -message FileAction { - oneof union { - ReadDir read_dir = 1; - FileTransferSendRequest send = 2; - FileTransferReceiveRequest receive = 3; - FileDirCreate create = 4; - FileRemoveDir remove_dir = 5; - FileRemoveFile remove_file = 6; - ReadAllFiles all_files = 7; - FileTransferCancel cancel = 8; - FileTransferSendConfirmRequest send_confirm = 9; - FileRename rename = 10; - ReadEmptyDirs read_empty_dirs = 11; - } -} - -// ๆ–‡ไปถๅ“ๅบ” -message FileResponse { - oneof union { - FileDirectory dir = 1; - FileTransferBlock block = 2; - FileTransferError error = 3; - FileTransferDone done = 4; - FileTransferDigest digest = 5; - ReadEmptyDirsResponse empty_dirs = 6; - } -} - -// ๆ–‡ไปถไผ ่พ“ๅ— -message FileTransferBlock { - int32 id = 1; - sint32 file_num = 2; - bytes data = 3; - bool compressed = 4; - uint32 blk_id = 5; -} - -// ๆ–‡ไปถๆก็›ฎ -message FileEntry { - FileType entry_type = 1; - string name = 2; - bool is_hidden = 3; - uint64 size = 4; - uint64 modified_time = 5; -} -``` - -### ๆ‚้กนๆถˆๆฏ - -```protobuf -message Misc { - oneof union { - ChatMessage chat_message = 4; - SwitchDisplay switch_display = 5; - PermissionInfo permission_info = 6; - OptionMessage option = 7; - AudioFormat audio_format = 8; - string close_reason = 9; - bool refresh_video = 10; - bool video_received = 12; - BackNotification back_notification = 13; - bool restart_remote_device = 14; - // ... ๆ›ดๅคš้€‰้กน - } -} - -// Peer ไฟกๆฏ -message PeerInfo { - string username = 1; - string hostname = 2; - string platform = 3; - repeated DisplayInfo displays = 4; - int32 current_display = 5; - bool sas_enabled = 6; - string version = 7; - Features features = 9; - SupportedEncoding encoding = 10; - SupportedResolutions resolutions = 11; - string platform_additions = 12; - WindowsSessions windows_sessions = 13; -} - -// ้€‰้กนๆถˆๆฏ -message OptionMessage { - enum BoolOption { - NotSet = 0; - No = 1; - Yes = 2; - } - ImageQuality image_quality = 1; - BoolOption lock_after_session_end = 2; - BoolOption show_remote_cursor = 3; - BoolOption privacy_mode = 4; - BoolOption block_input = 5; - int32 custom_image_quality = 6; - BoolOption disable_audio = 7; - BoolOption disable_clipboard = 8; - BoolOption enable_file_transfer = 9; - SupportedDecoding supported_decoding = 10; - int32 custom_fps = 11; - // ... ๆ›ดๅคš้€‰้กน -} -``` - -## ๆถˆๆฏ็ผ–็  - -### ้•ฟๅบฆๅ‰็ผ€ - -TCP ไผ ่พ“ๆ—ถไฝฟ็”จ้•ฟๅบฆๅ‰็ผ€็ผ–็ ๏ผš - -```rust -// hbb_common/src/bytes_codec.rs -pub struct BytesCodec { - state: DecodeState, - raw: bool, -} - -impl Decoder for BytesCodec { - type Item = BytesMut; - type Error = std::io::Error; - - fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { - if self.raw { - // ๅŽŸๅง‹ๆจกๅผ๏ผš็›ดๆŽฅ่ฟ”ๅ›žๆ•ฐๆฎ - if buf.is_empty() { - Ok(None) - } else { - Ok(Some(buf.split())) - } - } else { - // ๆ ‡ๅ‡†ๆจกๅผ๏ผš4 ๅญ—่Š‚้•ฟๅบฆๅ‰็ผ€ + ๆ•ฐๆฎ - match self.state { - DecodeState::Head => { - if buf.len() < 4 { - return Ok(None); - } - let len = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize; - buf.advance(4); - self.state = DecodeState::Data(len); - self.decode(buf) - } - DecodeState::Data(len) => { - if buf.len() < len { - return Ok(None); - } - let data = buf.split_to(len); - self.state = DecodeState::Head; - Ok(Some(data)) - } - } - } - } -} -``` - -### ๅŠ ๅฏ†ๆจกๅผ - -ๅฝ“ๅฏ็”จๅŠ ๅฏ†ๆ—ถ๏ผŒๆถˆๆฏ็ป“ๆž„ไธบ๏ผš - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Length(4) โ”‚ Nonce(8) โ”‚ Encrypted Data(N) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` diff --git a/docs/report/rustdesk/06-encryption.md b/docs/report/rustdesk/06-encryption.md deleted file mode 100644 index c2215c42..00000000 --- a/docs/report/rustdesk/06-encryption.md +++ /dev/null @@ -1,342 +0,0 @@ -# ๅŠ ๅฏ†ๆœบๅˆถ - -## ๆฆ‚่ฟฐ - -RustDesk ไฝฟ็”จ libsodium (sodiumoxide) ๅบ“ๅฎž็Žฐ็ซฏๅˆฐ็ซฏๅŠ ๅฏ†๏ผŒไธป่ฆๅŒ…ๅซ๏ผš - -- **Ed25519**: ็”จไบŽ่บซไปฝ็ญพๅๅ’Œ้ชŒ่ฏ -- **X25519**: ็”จไบŽๅฏ†้’ฅไบคๆข -- **ChaCha20-Poly1305**: ็”จไบŽๅฏน็งฐๅŠ ๅฏ† - -## ๅฏ†้’ฅ็ฑปๅž‹ - -### 1. ่บซไปฝๅฏ†้’ฅๅฏน (Ed25519) - -็”จไบŽ Peer ่บซไปฝ่ฎค่ฏๅ’Œ็ญพๅ๏ผš - -```rust -// ็”Ÿๆˆๅฏ†้’ฅๅฏน -use sodiumoxide::crypto::sign; -let (pk, sk) = sign::gen_keypair(); -// pk: sign::PublicKey (32 bytes) -// sk: sign::SecretKey (64 bytes) -``` - -### 2. ๆœๅŠกๅ™จ็ญพๅๅฏ†้’ฅ - -Rendezvous Server ๅฏไปฅ้…็ฝฎ็ญพๅๅฏ†้’ฅ๏ผŒ็”จไบŽ็ญพๅ Peer ๅ…ฌ้’ฅ๏ผš - -```rust -// rustdesk-server/src/rendezvous_server.rs:1185-1210 -fn get_server_sk(key: &str) -> (String, Option) { - let mut out_sk = None; - let mut key = key.to_owned(); - - // ๅฆ‚ๆžœๆ˜ฏ base64 ็ผ–็ ็š„็ง้’ฅ - if let Ok(sk) = base64::decode(&key) { - if sk.len() == sign::SECRETKEYBYTES { - log::info!("The key is a crypto private key"); - key = base64::encode(&sk[(sign::SECRETKEYBYTES / 2)..]); // ๅ…ฌ้’ฅ้ƒจๅˆ† - let mut tmp = [0u8; sign::SECRETKEYBYTES]; - tmp[..].copy_from_slice(&sk); - out_sk = Some(sign::SecretKey(tmp)); - } - } - - // ๅฆ‚ๆžœๆ˜ฏๅ ไฝ็ฌฆ๏ผŒ็”Ÿๆˆๆ–ฐๅฏ†้’ฅๅฏน - if key.is_empty() || key == "-" || key == "_" { - let (pk, sk) = crate::common::gen_sk(0); - out_sk = sk; - if !key.is_empty() { - key = pk; - } - } - - if !key.is_empty() { - log::info!("Key: {}", key); - } - (key, out_sk) -} -``` - -### 3. ไผš่ฏๅฏ†้’ฅ (X25519 + ChaCha20) - -็”จไบŽๅฎขๆˆท็ซฏไน‹้—ด็š„ๅŠ ๅฏ†้€šไฟก๏ผš - -```rust -// hbb_common/src/tcp.rs:27-28 -#[derive(Clone)] -pub struct Encrypt(pub Key, pub u64, pub u64); -// Key: secretbox::Key (32 bytes) -// u64: ๅ‘้€่ฎกๆ•ฐๅ™จ -// u64: ๆŽฅๆ”ถ่ฎกๆ•ฐๅ™จ -``` - -## ๅฏ†้’ฅไบคๆขๆต็จ‹ - -### 1. ่บซไปฝ้ชŒ่ฏ - -ๅฎขๆˆท็ซฏ้ฆ–ๅ…ˆไบคๆข็ญพๅ็š„่บซไปฝ๏ผš - -```protobuf -message IdPk { - string id = 1; // Peer ID - bytes pk = 2; // Ed25519 ๅ…ฌ้’ฅ -} - -message SignedId { - bytes id = 1; // ็ญพๅ็š„ IdPk (by server or self) -} -``` - -### 2. X25519 ๅฏ†้’ฅไบคๆข - -ไฝฟ็”จ X25519 ECDH ็”Ÿๆˆๅ…ฑไบซๅฏ†้’ฅ๏ผš - -```rust -// ็”Ÿๆˆไธดๆ—ถๅฏ†้’ฅๅฏน -use sodiumoxide::crypto::box_; -let (our_pk, our_sk) = box_::gen_keypair(); - -// ่ฎก็ฎ—ๅ…ฑไบซๅฏ†้’ฅ -let shared_secret = box_::curve25519xsalsa20poly1305::scalarmult(&our_sk, &their_pk); - -// ๆดพ็”Ÿๅฏน็งฐๅฏ†้’ฅ -let symmetric_key = secretbox::Key::from_slice(&shared_secret[..32]).unwrap(); -``` - -### 3. ๅฏน็งฐๅฏ†้’ฅๆถˆๆฏ - -```protobuf -message PublicKey { - bytes asymmetric_value = 1; // X25519 ๅ…ฌ้’ฅ - bytes symmetric_value = 2; // ๅŠ ๅฏ†็š„ๅฏน็งฐๅฏ†้’ฅ๏ผˆ็”จไบŽ้ขๅค–ๅฎ‰ๅ…จ๏ผ‰ -} -``` - -## ไผš่ฏๅŠ ๅฏ† - -### ๅŠ ๅฏ†ๅฎž็Žฐ - -```rust -// hbb_common/src/tcp.rs -impl Encrypt { - pub fn new(key: Key) -> Self { - Self(key, 0, 0) // ๅˆๅง‹ๅŒ–่ฎกๆ•ฐๅ™จไธบ 0 - } - - // ๅŠ ๅฏ† - pub fn enc(&mut self, data: &[u8]) -> Vec { - self.1 += 1; // ้€’ๅขžๅ‘้€่ฎกๆ•ฐๅ™จ - let nonce = self.get_nonce(self.1); - let encrypted = secretbox::seal(data, &nonce, &self.0); - - // ๆ ผๅผ: nonce (8 bytes) + encrypted data - let mut result = Vec::with_capacity(8 + encrypted.len()); - result.extend_from_slice(&self.1.to_le_bytes()); - result.extend_from_slice(&encrypted); - result - } - - // ่งฃๅฏ† - pub fn dec(&mut self, data: &mut BytesMut) -> io::Result<()> { - if data.len() < 8 + secretbox::MACBYTES { - return Err(io::Error::new(io::ErrorKind::InvalidData, "too short")); - } - - // ๆๅ– nonce - let counter = u64::from_le_bytes(data[..8].try_into().unwrap()); - - // ้˜ฒ้‡ๆ”พๆ”ปๅ‡ปๆฃ€ๆŸฅ - if counter <= self.2 { - return Err(io::Error::new(io::ErrorKind::InvalidData, "replay attack")); - } - self.2 = counter; - - let nonce = self.get_nonce(counter); - let plaintext = secretbox::open(&data[8..], &nonce, &self.0) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt failed"))?; - - data.clear(); - data.extend_from_slice(&plaintext); - Ok(()) - } - - fn get_nonce(&self, counter: u64) -> Nonce { - let mut nonce = [0u8; 24]; - nonce[..8].copy_from_slice(&counter.to_le_bytes()); - Nonce(nonce) - } -} -``` - -### ๆถˆๆฏๆ ผๅผ - -ๅŠ ๅฏ†ๅŽ็š„ๆถˆๆฏ็ป“ๆž„๏ผš - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Counter (8B) โ”‚ Encrypted Data + MAC (N+16 bytes) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๅฏ†็ ้ชŒ่ฏ - -### ๆŒ‘ๆˆ˜-ๅ“ๅบ”ๆœบๅˆถ - -่ขซๆŽง็ซฏ็”Ÿๆˆ้šๆœบ็›ๅ’ŒๆŒ‘ๆˆ˜๏ผŒๆŽงๅˆถ็ซฏ่ฎก็ฎ—ๅ“ˆๅธŒๅ“ๅบ”๏ผš - -```protobuf -message Hash { - string salt = 1; // ้šๆœบ็› - string challenge = 2; // ้šๆœบๆŒ‘ๆˆ˜ -} -``` - -### ๅฏ†็ ๅค„็† - -```rust -// ๅฎขๆˆท็ซฏ่ฎก็ฎ—ๅฏ†็ ๅ“ˆๅธŒ -fn get_password_hash(password: &str, salt: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(password.as_bytes()); - hasher.update(salt.as_bytes()); - hasher.finalize().to_vec() -} - -// ๅ‘้€ๅŠ ๅฏ†็š„ๅฏ†็ ๏ผˆไฝฟ็”จๅฏน็งฐๅฏ†้’ฅๅŠ ๅฏ†๏ผ‰ -fn encrypt_password(password_hash: &[u8], symmetric_key: &Key) -> Vec { - secretbox::seal(password_hash, &nonce, symmetric_key) -} -``` - -## ๆœๅŠกๅ™จๅ…ฌ้’ฅ้ชŒ่ฏ - -### ็ญพๅ้ชŒ่ฏ - -ๅฆ‚ๆžœ Rendezvous Server ้…็ฝฎไบ†ๅฏ†้’ฅ๏ผŒไผš็ญพๅ Peer ๅ…ฌ้’ฅ๏ผš - -```rust -// ๆœๅŠกๅ™จ็ญพๅ IdPk -let signed_id_pk = sign::sign( - &IdPk { id, pk, ..Default::default() } - .write_to_bytes()?, - &server_sk, -); - -// ๅฎขๆˆท็ซฏ้ชŒ่ฏ -fn verify_server_signature(signed_pk: &[u8], server_pk: &sign::PublicKey) -> Option { - if let Ok(verified) = sign::verify(signed_pk, server_pk) { - return IdPk::parse_from_bytes(&verified).ok(); - } - None -} -``` - -### ๅฎขๆˆท็ซฏ่Žทๅ–ๆœๅŠกๅ™จๅ…ฌ้’ฅ - -```rust -pub async fn get_rs_pk(id: &str) -> ResultType<(String, sign::PublicKey)> { - // ไปŽ้…็ฝฎๆˆ– Rendezvous Server ่Žทๅ–ๅ…ฌ้’ฅ - let key = Config::get_option("key"); - if !key.is_empty() { - if let Ok(pk) = base64::decode(&key) { - if pk.len() == sign::PUBLICKEYBYTES { - return Ok((key, sign::PublicKey::from_slice(&pk).unwrap())); - } - } - } - // ... ไปŽๆœๅŠกๅ™จ่Žทๅ– -} -``` - -## TCP ่ฟžๆŽฅๅŠ ๅฏ† - -### ๅฎ‰ๅ…จ TCP ๆกๆ‰‹ - -```rust -// rustdesk/src/common.rs -pub async fn secure_tcp(conn: &mut Stream, key: &str) -> ResultType<()> { - // 1. ็”Ÿๆˆไธดๆ—ถ X25519 ๅฏ†้’ฅๅฏน - let (our_pk, our_sk) = box_::gen_keypair(); - - // 2. ๅ‘้€ๆˆ‘ไปฌ็š„ๅ…ฌ้’ฅ - let mut msg = Message::new(); - msg.set_public_key(PublicKey { - asymmetric_value: our_pk.0.to_vec().into(), - ..Default::default() - }); - conn.send(&msg).await?; - - // 3. ๆŽฅๆ”ถๅฏนๆ–นๅ…ฌ้’ฅ - let msg = conn.next_timeout(CONNECT_TIMEOUT).await? - .ok_or_else(|| anyhow!("timeout"))?; - let their_pk = msg.get_public_key(); - - // 4. ่ฎก็ฎ—ๅ…ฑไบซๅฏ†้’ฅ - let shared = box_::curve25519xsalsa20poly1305::scalarmult( - &our_sk, - &box_::PublicKey::from_slice(&their_pk.asymmetric_value)?, - ); - - // 5. ่ฎพ็ฝฎๅŠ ๅฏ† - conn.set_key(secretbox::Key::from_slice(&shared[..32]).unwrap()); - Ok(()) -} -``` - -## ๅฎ‰ๅ…จ็‰นๆ€ง - -### 1. ๅ‰ๅ‘ไฟๅฏ† - -ๆฏไธชไผš่ฏไฝฟ็”จไธดๆ—ถๅฏ†้’ฅๅฏน๏ผŒๅณไฝฟ้•ฟๆœŸๅฏ†้’ฅๆณ„้œฒ๏ผŒๅކๅฒไผš่ฏไป็„ถๅฎ‰ๅ…จใ€‚ - -### 2. ้‡ๆ”พๆ”ปๅ‡ป้˜ฒๆŠค - -ไฝฟ็”จ้€’ๅขž่ฎกๆ•ฐๅ™จไฝœไธบ nonce ็š„ไธ€้ƒจๅˆ†๏ผŒๆ‹’็ปๆ—ง็š„ๆˆ–้‡ๅค็š„ๆถˆๆฏใ€‚ - -### 3. ไธญ้—ดไบบๆ”ปๅ‡ป้˜ฒๆŠค - -- ๆœๅŠกๅ™จ็ญพๅ Peer ๅ…ฌ้’ฅ -- ๅฏ้…็ฝฎๆœๅŠกๅ™จๅ…ฌ้’ฅ้ชŒ่ฏ - -### 4. ๅฏ†็ ๆšดๅŠ›็ ด่งฃ้˜ฒๆŠค - -- ไฝฟ็”จ็›ๅ’Œๅคšๆฌกๅ“ˆๅธŒ -- ๆœๅŠกๅ™จ็ซฏ้™ๆต - -## ๅŠ ๅฏ†็ฎ—ๆณ•ๅ‚ๆ•ฐ - -| ็ฎ—ๆณ• | ๅฏ†้’ฅๅคงๅฐ | Nonce ๅคงๅฐ | MAC ๅคงๅฐ | -|------|----------|------------|----------| -| Ed25519 | 64 bytes (private), 32 bytes (public) | N/A | 64 bytes | -| X25519 | 32 bytes | N/A | N/A | -| ChaCha20-Poly1305 | 32 bytes | 24 bytes | 16 bytes | - -## ๅฏ†้’ฅ็”Ÿๅ‘ฝๅ‘จๆœŸ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ้•ฟๆœŸๅฏ†้’ฅ (Ed25519) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ่ฎพๅค‡้ฆ–ๆฌกๅฏๅŠจๆ—ถ็”Ÿๆˆ โ”‚ โ”‚ -โ”‚ โ”‚ ๅญ˜ๅ‚จๅœจ้…็ฝฎๆ–‡ไปถไธญ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ไผš่ฏๅฏ†้’ฅ (X25519) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๆฏๆฌก่ฟžๆŽฅๆ—ถ็”Ÿๆˆ โ”‚โ”€โ”€โ”€โ–บโ”‚ ็”จไบŽๅฏ†้’ฅๅๅ•† โ”‚ โ”‚ -โ”‚ โ”‚ ไธดๆ—ถๅฏ†้’ฅๅฏน โ”‚ โ”‚ ๆดพ็”Ÿๅฏน็งฐๅฏ†้’ฅ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ๅฏน็งฐๅฏ†้’ฅ (ChaCha20-Poly1305) โ”‚ โ”‚ -โ”‚ โ”‚ ็”จไบŽไผš่ฏไธญ็š„ๆ‰€ๆœ‰ๆถˆๆฏๅŠ ๅฏ† โ”‚ โ”‚ -โ”‚ โ”‚ ไผš่ฏ็ป“ๆŸๆ—ถ้”€ๆฏ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` diff --git a/docs/report/rustdesk/07-nat-traversal.md b/docs/report/rustdesk/07-nat-traversal.md deleted file mode 100644 index cfc75688..00000000 --- a/docs/report/rustdesk/07-nat-traversal.md +++ /dev/null @@ -1,410 +0,0 @@ -# NAT ็ฉฟ้€ๆŠ€ๆœฏ - -## ๆฆ‚่ฟฐ - -RustDesk ๅฎž็Žฐไบ†ๅคš็ง NAT ็ฉฟ้€ๆŠ€ๆœฏ๏ผŒไปฅๅœจไธๅŒ็ฝ‘็ปœ็Žฏๅขƒไธ‹ๅปบ็ซ‹ P2P ่ฟžๆŽฅ๏ผš - -- NAT ็ฑปๅž‹ๆฃ€ๆต‹ -- UDP ๆ‰“ๆดž -- TCP ๆ‰“ๆดž -- Relay ไธญ่ฝฌ๏ผˆไฝœไธบๅŽๅค‡๏ผ‰ - -## NAT ็ฑปๅž‹ - -### ๅˆ†็ฑป - -```protobuf -enum NatType { - UNKNOWN_NAT = 0; // ๆœช็Ÿฅ - ASYMMETRIC = 1; // ้žๅฏน็งฐ NAT (Cone NAT) - ๅฏๆ‰“ๆดž - SYMMETRIC = 2; // ๅฏน็งฐ NAT - ้€šๅธธ้œ€่ฆ Relay -} -``` - -### NAT ็ฑปๅž‹่ฏดๆ˜Ž - -| ็ฑปๅž‹ | ๆ่ฟฐ | ๅฏๆ‰“ๆดž | -|------|------|--------| -| Full Cone | ๅค–้ƒจ็ซฏๅฃๅ›บๅฎš๏ผŒไปปไฝ•ๅค–้ƒจไธปๆœบๅฏ่ฎฟ้—ฎ | โœ… ๆœ€ๅฎนๆ˜“ | -| Restricted Cone | ๅค–้ƒจ็ซฏๅฃๅ›บๅฎš๏ผŒไป…ๅ…่ฎธๆ›พๅ‘้€่ฟ‡ๆ•ฐๆฎ็š„ IP | โœ… ๅฎนๆ˜“ | -| Port Restricted Cone | ๅค–้ƒจ็ซฏๅฃๅ›บๅฎš๏ผŒไป…ๅ…่ฎธๆ›พๅ‘้€่ฟ‡ๆ•ฐๆฎ็š„ IP:Port | โœ… ๅฏ่ƒฝ | -| Symmetric | ๆฏไธช็›ฎๆ ‡ๅœฐๅ€ไฝฟ็”จไธๅŒๅค–้ƒจ็ซฏๅฃ | โŒ ๅ›ฐ้šพ | - -## NAT ็ฑปๅž‹ๆฃ€ๆต‹ - -### ๆฃ€ๆต‹ๅŽŸ็† - -RustDesk ไฝฟ็”จๅŒ็ซฏๅฃๆฃ€ๆต‹ๆณ•๏ผš - -1. ๅฎขๆˆท็ซฏๅ‘ Rendezvous Server ็š„ไธป็ซฏๅฃ (21116) ๅ‘้€ TestNatRequest -2. ๅŒๆ—ถๅ‘ NAT ๆต‹่ฏ•็ซฏๅฃ (21115) ๅ‘้€ TestNatRequest -3. ๆฏ”่พƒไธคๆฌกๅ“ๅบ”ไธญ่ง‚ๆต‹ๅˆฐ็š„ๆบ็ซฏๅฃ - -``` -ๅฎขๆˆท็ซฏ Rendezvous Server - โ”‚ โ”‚ - โ”‚ TestNatRequest โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ Port 21116 - โ”‚ โ”‚ - โ”‚ TestNatRequest โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ Port 21115 - โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ TestNatResponse โ”‚ (ๅŒ…ๅซ่ง‚ๆต‹ๅˆฐ็š„ๆบ็ซฏๅฃ) - โ”‚ โ”‚ - โ”‚ โ”‚ - โ”‚ ๆฏ”่พƒไธคๆฌกๆบ็ซฏๅฃ โ”‚ - โ”‚ ็›ธๅŒ โ†’ ASYMMETRIC โ”‚ - โ”‚ ไธๅŒ โ†’ SYMMETRIC โ”‚ -``` - -### ๅฎž็Žฐไปฃ็  - -**ๅฎขๆˆท็ซฏๅ‘้€ๆฃ€ๆต‹่ฏทๆฑ‚๏ผš** - -```rust -// rustdesk/src/lib.rs -pub fn test_nat_type() { - tokio::spawn(async move { - let rendezvous_server = Config::get_rendezvous_servers().first().cloned(); - if let Some(host) = rendezvous_server { - // ่ฟžๆŽฅไธป็ซฏๅฃ - let host = check_port(&host, RENDEZVOUS_PORT); - - // ่ฟžๆŽฅ NAT ๆต‹่ฏ•็ซฏๅฃ - let host2 = crate::increase_port(&host, -1); - - // ๅ‘้€ๆต‹่ฏ•่ฏทๆฑ‚ - let mut msg = RendezvousMessage::new(); - msg.set_test_nat_request(TestNatRequest { - serial: Config::get_serial(), - }); - - // ๆ”ถ้›†ไธคๆฌกๅ“ๅบ”็š„็ซฏๅฃ - let port1 = send_and_get_port(&host, &msg).await; - let port2 = send_and_get_port(&host2, &msg).await; - - // ๅˆคๆ–ญ NAT ็ฑปๅž‹ - let nat_type = if port1 == port2 { - NatType::ASYMMETRIC // ๅฏๆ‰“ๆดž - } else { - NatType::SYMMETRIC // ้œ€่ฆ Relay - }; - - Config::set_nat_type(nat_type as i32); - } - }); -} -``` - -**ๆœๅŠกๅ™จๅ“ๅบ”๏ผš** - -```rust -// rustdesk-server/src/rendezvous_server.rs:1080-1087 -Some(rendezvous_message::Union::TestNatRequest(_)) => { - let mut msg_out = RendezvousMessage::new(); - msg_out.set_test_nat_response(TestNatResponse { - port: addr.port() as _, // ่ฟ”ๅ›ž่ง‚ๆต‹ๅˆฐ็š„ๆบ็ซฏๅฃ - ..Default::default() - }); - stream.send(&msg_out).await.ok(); -} -``` - -## UDP ๆ‰“ๆดž - -### ๅŽŸ็† - -UDP ๆ‰“ๆดžๅˆฉ็”จ NAT ็š„็ซฏๅฃๆ˜ ๅฐ„ๆœบๅˆถ๏ผš - -``` - A (ๅ†…็ฝ‘) B (ๅ†…็ฝ‘) - โ”‚ โ”‚ - โ”‚ โ”€โ”€โ–บ NAT_A โ”€โ”€โ–บ Internet โ”€โ”€โ–บ NAT_B โ”€โ”€โ–บ (ไธขๅผƒ) โ”‚ - โ”‚ โ”‚ - โ”‚ โ”‚ - โ”‚ (NAT_A ๅˆ›ๅปบไบ†ๆ˜ ๅฐ„ A:port โ†’ A_ext:port_a) โ”‚ - โ”‚ โ”‚ - โ”‚ โ”‚ - โ”‚ (ไธขๅผƒ) โ—„โ”€โ”€ NAT_A โ—„โ”€โ”€ Internet โ—„โ”€โ”€ NAT_B โ—„โ”€โ”€ โ”‚ - โ”‚ โ”‚ - โ”‚ โ”‚ - โ”‚ (NAT_B ๅˆ›ๅปบไบ†ๆ˜ ๅฐ„ B:port โ†’ B_ext:port_b) โ”‚ - โ”‚ โ”‚ - โ”‚ โ”€โ”€โ–บ NAT_A โ”€โ”€โ–บ Internet โ”€โ”€โ–บ NAT_B โ”€โ”€โ–บ โ”‚ - โ”‚ (NAT_A ็š„ๆ˜ ๅฐ„ๅญ˜ๅœจ๏ผŒๅŒ…่ขซ่ฝฌๅ‘) โ”‚ - โ”‚ โ”‚ - โ”‚ โ—„โ”€โ”€ NAT_A โ—„โ”€โ”€ Internet โ—„โ”€โ”€ NAT_B โ—„โ”€โ”€ โ”‚ - โ”‚ (NAT_B ็š„ๆ˜ ๅฐ„ๅญ˜ๅœจ๏ผŒๅŒ…่ขซ่ฝฌๅ‘) โ”‚ - โ”‚ โ”‚ - โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ๅŒๅ‘้€šไฟกๅปบ็ซ‹ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ -``` - -### ๅฎž็Žฐ - -**่ขซๆŽง็ซฏๆ‰“ๆดž๏ผš** - -```rust -// rustdesk/src/rendezvous_mediator.rs:621-642 -async fn punch_udp_hole( - &self, - peer_addr: SocketAddr, - server: ServerPtr, - msg_punch: PunchHoleSent, -) -> ResultType<()> { - let mut msg_out = Message::new(); - msg_out.set_punch_hole_sent(msg_punch); - - // ๅˆ›ๅปบ UDP socket - let (socket, addr) = new_direct_udp_for(&self.host).await?; - let data = msg_out.write_to_bytes()?; - - // ๅ‘้€ๅˆฐ Rendezvous Server๏ผˆไผš่ฝฌๅ‘็ป™ๆŽงๅˆถ็ซฏ๏ผ‰ - socket.send_to(&data, addr).await?; - - // ๅคšๆฌกๅ‘้€ไปฅๅขžๅŠ ๆˆๅŠŸ็އ - let socket_cloned = socket.clone(); - tokio::spawn(async move { - for _ in 0..2 { - let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.; - hbb_common::sleep(tm).await; - socket.send_to(&data, addr).await.ok(); - } - }); - - // ็ญ‰ๅพ…ๅฏนๆ–น่ฟžๆŽฅ - udp_nat_listen(socket_cloned, peer_addr, peer_addr, server).await?; - Ok(()) -} -``` - -**UDP ็›‘ๅฌๅ’Œ KCP ๅปบ็ซ‹๏ผš** - -```rust -// rustdesk/src/rendezvous_mediator.rs:824-851 -async fn udp_nat_listen( - socket: Arc, - peer_addr: SocketAddr, - peer_addr_v4: SocketAddr, - server: ServerPtr, -) -> ResultType<()> { - // ่ฟžๆŽฅๅˆฐๅฏนๆ–นๅœฐๅ€ - socket.connect(peer_addr).await?; - - // ๆ‰ง่กŒ UDP ๆ‰“ๆดž - let res = crate::punch_udp(socket.clone(), true).await?; - - // ๅปบ็ซ‹ KCP ๅฏ้ ไผ ่พ“ๅฑ‚ - let stream = crate::kcp_stream::KcpStream::accept( - socket, - Duration::from_millis(CONNECT_TIMEOUT as _), - res, - ).await?; - - // ๅˆ›ๅปบ่ฟžๆŽฅ - crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?; - Ok(()) -} -``` - -### KCP ๅ่ฎฎ - -RustDesk ๅœจ UDP ไธŠไฝฟ็”จ KCP ๆไพ›ๅฏ้ ไผ ่พ“๏ผŒKCP ็‰น็‚น๏ผš - -- ๆ›ดๆฟ€่ฟ›็š„้‡ไผ ็ญ–็•ฅ -- ๆ›ดไฝŽ็š„ๅปถ่ฟŸ -- ๅฏ้…็ฝฎ็š„ๅฏ้ ๆ€ง็บงๅˆซ - -## TCP ๆ‰“ๆดž - -### ๅŽŸ็† - -TCP ๆ‰“ๆดžๆฏ” UDP ๅ›ฐ้šพ๏ผŒๅ› ไธบ TCP ้œ€่ฆไธ‰ๆฌกๆกๆ‰‹ใ€‚ๆŠ€ๅทงๆ˜ฏ่ฎฉๅŒๆ–นๅŒๆ—ถๅ‘่ตท่ฟžๆŽฅ๏ผš - -``` - A NAT_A NAT_B B - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€ SYN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ–บ (ไธขๅผƒ๏ผŒๆ— ๆ˜ ๅฐ„) โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ (NAT_A ๅˆ›ๅปบๅˆฐ B ็š„ๆ˜ ๅฐ„) โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ (ไธขๅผƒ๏ผŒๆ— ๆ˜ ๅฐ„) โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ—„โ”€โ”€โ”€ SYN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ (NAT_B ๅˆ›ๅปบๅˆฐ A ็š„ๆ˜ ๅฐ„) โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€ SYN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ–บ SYN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ - โ”‚ โ”‚ โ”‚ (ๆ˜ ๅฐ„ๅญ˜ๅœจ๏ผŒ่ฝฌๅ‘ๆˆๅŠŸ) โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ—„โ”€โ”€โ”€ SYN+ACK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ—„โ”€โ”€โ”€ SYN+ACK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€ ACK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ–บ ACK โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ่ฟžๆŽฅๅปบ็ซ‹ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ -``` - -### ๅฎž็Žฐ - -```rust -// rustdesk/src/rendezvous_mediator.rs:604-617 -log::debug!("Punch tcp hole to {:?}", peer_addr); -let mut socket = { - // 1. ๅ…ˆ่ฟžๆŽฅ Rendezvous Server ่Žทๅ–ๆœฌๅœฐๅœฐๅ€ - let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?; - let local_addr = socket.local_addr(); - - // 2. ็”จ็›ธๅŒ็š„ๆœฌๅœฐๅœฐๅ€ๅฐ่ฏ•่ฟžๆŽฅๅฏนๆ–น - // ่ฟ™ไผšๅœจ NAT ไธŠๅˆ›ๅปบๆ˜ ๅฐ„ - // ่™ฝ็„ถ่ฟžๆŽฅไผšๅคฑ่ดฅ๏ผŒไฝ†ๆ˜ ๅฐ„ๅทฒๅปบ็ซ‹ - allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await); - - socket -}; - -// 3. ๅ‘้€ PunchHoleSent ้€š็ŸฅๆœๅŠกๅ™จ -// ๆœๅŠกๅ™จไผš่ฝฌๅ‘็ป™ๆŽงๅˆถ็ซฏ -let mut msg_out = Message::new(); -msg_out.set_punch_hole_sent(msg_punch); -socket.send_raw(msg_out.write_to_bytes()?).await?; - -// 4. ็ญ‰ๅพ…ๆŽงๅˆถ็ซฏ่ฟžๆŽฅ -// ็”ฑไบŽๅทฒๆœ‰ๆ˜ ๅฐ„๏ผŒๆŽงๅˆถ็ซฏ็š„่ฟžๆŽฅๅฏไปฅๆˆๅŠŸ -crate::accept_connection(server.clone(), socket, peer_addr, true).await; -``` - -## ๅฑ€ๅŸŸ็ฝ‘็›ด่ฟž - -### ๆฃ€ๆต‹ๅŒไธ€ๅฑ€ๅŸŸ็ฝ‘ - -```rust -// rustdesk-server/src/rendezvous_server.rs:721-728 -let same_intranet: bool = !ws - && (peer_is_lan && is_lan || { - match (peer_addr, addr) { - (SocketAddr::V4(a), SocketAddr::V4(b)) => a.ip() == b.ip(), - (SocketAddr::V6(a), SocketAddr::V6(b)) => a.ip() == b.ip(), - _ => false, - } - }); -``` - -### ๅฑ€ๅŸŸ็ฝ‘่ฟžๆŽฅๆต็จ‹ - -``` -ๆŽงๅˆถ็ซฏ Rendezvous Server ่ขซๆŽง็ซฏ - โ”‚ โ”‚ โ”‚ - โ”‚ PunchHoleRequest โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ (ๆฃ€ๆต‹ๅˆฐๅŒไธ€ๅฑ€ๅŸŸ็ฝ‘) โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ FetchLocalAddr โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€ LocalAddr โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ (ๅŒ…ๅซ่ขซๆŽง็ซฏๅ†…็ฝ‘ๅœฐๅ€) โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚โ—„โ”€ PunchHoleResponse โ”€โ”€โ”‚ โ”‚ - โ”‚ (is_local=true) โ”‚ โ”‚ - โ”‚ (socket_addr=ๅ†…็ฝ‘ๅœฐๅ€)โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ - โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ็›ดๆŽฅ่ฟžๆŽฅๅ†…็ฝ‘ๅœฐๅ€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ -``` - -## IPv6 ๆ”ฏๆŒ - -IPv6 ้€šๅธธไธ้œ€่ฆ NAT ็ฉฟ้€๏ผŒไฝ† RustDesk ไปๆ”ฏๆŒ IPv6 ๆ‰“ๆดžไปฅๅค„็†ๆœ‰็Šถๆ€้˜ฒ็ซๅข™๏ผš - -```rust -// rustdesk/src/rendezvous_mediator.rs:808-822 -async fn start_ipv6( - peer_addr_v6: SocketAddr, - peer_addr_v4: SocketAddr, - server: ServerPtr, -) -> bytes::Bytes { - crate::test_ipv6().await; - if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await { - let server = server.clone(); - tokio::spawn(async move { - allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await); - }); - return local_addr_v6; - } - Default::default() -} -``` - -## ่ฟžๆŽฅ็ญ–็•ฅๅ†ณ็ญ–ๆ ‘ - -``` - ๅผ€ๅง‹่ฟžๆŽฅ - โ”‚ - โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ NAT ็ฑปๅž‹ๆฃ€ๆต‹ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ - ASYMMETRIC UNKNOWN SYMMETRIC - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ”‚ ๅฐ่ฏ• UDP โ”‚ โ”‚ ๅฐ่ฏ• TCP โ”‚ โ”‚ - โ”‚ ๆ‰“ๆดž โ”‚ โ”‚ ๆ‰“ๆดž โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ”‚ โ”‚ โ”‚ - ๆˆๅŠŸ โ”‚ ๅคฑ่ดฅ ๆˆๅŠŸ โ”‚ ๅคฑ่ดฅ โ”‚ - โ–ผ โ”‚ โ–ผ โ”‚ โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ โ”‚ - โ”‚UDP P2P โ”‚โ”‚ โ”‚TCP P2P โ”‚โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ - โ”‚ โ”‚ - โ–ผ โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - โ”‚ ไฝฟ็”จ Relay โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -## ๆ€ง่ƒฝไผ˜ๅŒ– - -### ๅคš่ทฏๅพ„ๅฐ่ฏ• - -RustDesk ๅŒๆ—ถๅฐ่ฏ•ๅคš็ง่ฟžๆŽฅๆ–นๅผ๏ผŒ้€‰ๆ‹ฉๆœ€ๅฟซๆˆๅŠŸ็š„๏ผš - -```rust -// rustdesk/src/client.rs:342-364 -let mut connect_futures = Vec::new(); - -// ๅŒๆ—ถๅฐ่ฏ• UDP ๅ’Œ TCP -if udp.0.is_some() { - connect_futures.push(Self::_start_inner(..., udp).boxed()); -} -connect_futures.push(Self::_start_inner(..., (None, None)).boxed()); - -// ไฝฟ็”จ select_ok ้€‰ๆ‹ฉ็ฌฌไธ€ไธชๆˆๅŠŸ็š„ -match select_ok(connect_futures).await { - Ok(conn) => Ok(conn), - Err(e) => Err(e), -} -``` - -### ่ถ…ๆ—ถๆŽงๅˆถ - -```rust -const CONNECT_TIMEOUT: u64 = 18_000; // 18 ็ง’ -const REG_TIMEOUT: i32 = 30_000; // 30 ็ง’ - -// ่ฟžๆŽฅ่ถ…ๆ—ถๅค„็† -if let Ok(Ok((stream, addr))) = timeout(CONNECT_TIMEOUT, socket.accept()).await { - // ่ฟžๆŽฅๆˆๅŠŸ -} else { - // ่ถ…ๆ—ถ๏ผŒๅฐ่ฏ•ๅ…ถไป–ๆ–นๅผ -} -``` - -## ๅธธ่ง้—ฎ้ข˜ๅ’Œ่งฃๅ†ณๆ–นๆกˆ - -| ้—ฎ้ข˜ | ๅŽŸๅ›  | ่งฃๅ†ณๆ–นๆกˆ | -|------|------|----------| -| ๅŒ Symmetric NAT | ไธค็ซฏ้ƒฝๆ˜ฏๅฏน็งฐ NAT | ไฝฟ็”จ Relay | -| ้˜ฒ็ซๅข™้˜ปๆญข UDP | ไผไธš้˜ฒ็ซๅข™ | ไฝฟ็”จ TCP ๆˆ– WebSocket | -| ็ซฏๅฃ้ข„ๆต‹ๅคฑ่ดฅ | NAT ็ซฏๅฃๅˆ†้…ไธ่ง„ๅพ‹ | ๅคšๆฌกๅฐ่ฏ•ๆˆ–ไฝฟ็”จ Relay | -| IPv6 ไธ้€š | ISP ๆˆ–้˜ฒ็ซๅข™้—ฎ้ข˜ | ๅ›ž้€€ๅˆฐ IPv4 | diff --git a/docs/report/rustdesk/08-onekvm-comparison.md b/docs/report/rustdesk/08-onekvm-comparison.md deleted file mode 100644 index 5fdf5494..00000000 --- a/docs/report/rustdesk/08-onekvm-comparison.md +++ /dev/null @@ -1,401 +0,0 @@ -# RustDesk ๅ่ฎฎ vs One-KVM ๅฎž็Žฐๅฏนๆฏ”ๅˆ†ๆž - -ๆœฌๆ–‡ๆกฃๅฏนๆฏ”ๅˆ†ๆž RustDesk ๅŽŸๅง‹ๅ่ฎฎไธŽ One-KVM ็š„ๅฎž็Žฐๅทฎๅผ‚ใ€‚ - -## 1. ๆฆ‚่ฟฐ - -One-KVM ไฝœไธบ IP-KVM ่งฃๅ†ณๆ–นๆกˆ๏ผŒๅชๅฎž็Žฐไบ† RustDesk ๅ่ฎฎ็š„**่ขซๆŽง็ซฏ๏ผˆControlled๏ผ‰** ๅŠŸ่ƒฝ๏ผŒไธๅฎž็ŽฐๆŽงๅˆถ็ซฏ๏ผˆController๏ผ‰ๅŠŸ่ƒฝใ€‚่ฟ™ๆ˜ฏ่ฎพ่ฎกๅ†ณ็ญ–๏ผŒๅ› ไธบ KVM ่ฎพๅค‡ๅช้œ€่ฆๆŽฅๆ”ถ่ฟœ็จ‹ๆŽงๅˆถ๏ผŒไธ้œ€่ฆๆŽงๅˆถๅ…ถไป–่ฎพๅค‡ใ€‚ - -### ๆžถๆž„ๅทฎๅผ‚ - -| ๆ–น้ข | RustDesk ๅŽŸ็‰ˆ | One-KVM | -|------|---------------|---------| -| ่ง’่‰ฒ | ๅŒๅ‘๏ผˆๆŽงๅˆถ็ซฏ+่ขซๆŽง็ซฏ๏ผ‰ | ๅ•ๅ‘๏ผˆไป…่ขซๆŽง็ซฏ๏ผ‰ | -| ่ฟžๆŽฅๆ–นๅผ | P2P + Relay | ไป… Relay (TCP) | -| NAT ็ฉฟ้€ | UDP/TCP ๆ‰“ๆดž + TURN | ไธๆ”ฏๆŒ | -| ไผ ่พ“ๅ่ฎฎ | UDP/TCP | ไป… TCP | - -## 2. ๅทฒๅฎž็ŽฐๅŠŸ่ƒฝ - -### 2.1 Rendezvous ๅ่ฎฎ (hbbs ้€šไฟก) - -| ๆถˆๆฏ็ฑปๅž‹ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|----------|----------|------| -| RegisterPeer | โœ… ๅทฒๅฎž็Žฐ | ๆณจๅ†Œ่ฎพๅค‡ๅˆฐๆœๅŠกๅ™จ | -| RegisterPeerResponse | โœ… ๅทฒๅฎž็Žฐ | ๅค„็†ๆณจๅ†Œๅ“ๅบ” | -| RegisterPk | โœ… ๅทฒๅฎž็Žฐ | ๆณจๅ†Œๅ…ฌ้’ฅ | -| RegisterPkResponse | โœ… ๅทฒๅฎž็Žฐ | ๅค„็†ๅ…ฌ้’ฅๆณจๅ†Œๅ“ๅบ” | -| PunchHoleSent | โœ… ๅทฒๅฎž็Žฐ | ๅ“ๅบ”ๆ‰“ๆดž่ฏทๆฑ‚ | -| FetchLocalAddr | โœ… ๅทฒๅฎž็Žฐ | ่Žทๅ–ๆœฌๅœฐๅœฐๅ€ | -| LocalAddr | โœ… ๅทฒๅฎž็Žฐ | ่ฟ”ๅ›žๆœฌๅœฐๅœฐๅ€ | -| RequestRelay | โœ… ๅทฒๅฎž็Žฐ | ่ฏทๆฑ‚ไธญ็ปง่ฟžๆŽฅ | -| RelayResponse | โœ… ๅทฒๅฎž็Žฐ | ๅค„็†ไธญ็ปงๅ“ๅบ” | -| ConfigUpdate | โœ… ๅทฒๅฎž็Žฐ | ๆŽฅๆ”ถ้…็ฝฎๆ›ดๆ–ฐ | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/rendezvous.rs` (~829 ่กŒ) - -```rust -// ๆ ธๅฟƒ็ป“ๆž„ -pub struct RendezvousMediator { - config: RustDeskConfig, - key_pair: KeyPair, - signing_key: SigningKeyPair, - socket: UdpSocket, - status: Arc>, - // ... -} -``` - -### 2.2 ่ฟžๆŽฅๅ่ฎฎ (ๅฎขๆˆท็ซฏ่ฟžๆŽฅ) - -| ๆถˆๆฏ็ฑปๅž‹ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|----------|----------|------| -| SignedId | โœ… ๅทฒๅฎž็Žฐ | ็ญพๅ่บซไปฝ้ชŒ่ฏ | -| PublicKey | โœ… ๅทฒๅฎž็Žฐ | ๅ…ฌ้’ฅไบคๆข | -| Hash | โœ… ๅทฒๅฎž็Žฐ | ๅ“ˆๅธŒๆŒ‘ๆˆ˜ๅ“ๅบ” | -| LoginRequest | โœ… ๅทฒๅฎž็Žฐ | ็™ปๅฝ•่ฎค่ฏ | -| LoginResponse | โœ… ๅทฒๅฎž็Žฐ | ็™ปๅฝ•ๅ“ๅบ” | -| TestDelay | โœ… ๅทฒๅฎž็Žฐ | ๅปถ่ฟŸๆต‹่ฏ• | -| VideoFrame | โœ… ๅทฒๅฎž็Žฐ | ่ง†้ข‘ๅธงๅ‘้€ | -| AudioFrame | โœ… ๅทฒๅฎž็Žฐ | ้Ÿณ้ข‘ๅธงๅ‘้€ | -| CursorData | โœ… ๅทฒๅฎž็Žฐ | ๅ…‰ๆ ‡ๅ›พๅƒ | -| CursorPosition | โœ… ๅทฒๅฎž็Žฐ | ๅ…‰ๆ ‡ไฝ็ฝฎ | -| MouseEvent | โœ… ๅทฒๅฎž็Žฐ | ้ผ ๆ ‡ไบ‹ไปถๆŽฅๆ”ถ | -| KeyEvent | โœ… ๅทฒๅฎž็Žฐ | ้”ฎ็›˜ไบ‹ไปถๆŽฅๆ”ถ | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/connection.rs` (~1349 ่กŒ) - -```rust -// ่ฟžๆŽฅ็Šถๆ€ๆœบ -pub enum ConnectionState { - WaitingForSignedId, - WaitingForPublicKey, - WaitingForHash, - WaitingForLogin, - Authenticated, - Streaming, -} -``` - -### 2.3 ๅŠ ๅฏ†ๆจกๅ— - -| ๅŠŸ่ƒฝ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|------|----------|------| -| Curve25519 ๅฏ†้’ฅๅฏน | โœ… ๅทฒๅฎž็Žฐ | ็”จไบŽๅŠ ๅฏ† | -| Ed25519 ็ญพๅๅฏ†้’ฅๅฏน | โœ… ๅทฒๅฎž็Žฐ | ็”จไบŽ็ญพๅ | -| Ed25519 โ†’ Curve25519 ่ฝฌๆข | โœ… ๅทฒๅฎž็Žฐ | ๅฏ†้’ฅๆดพ็”Ÿ | -| XSalsa20-Poly1305 | โœ… ๅทฒๅฎž็Žฐ | ไผš่ฏๅŠ ๅฏ† (secretbox) | -| ๅฏ†็ ๅ“ˆๅธŒ | โœ… ๅทฒๅฎž็Žฐ | ๅ•้‡/ๅŒ้‡ SHA256 | -| ไผš่ฏๅฏ†้’ฅๅๅ•† | โœ… ๅทฒๅฎž็Žฐ | ๅฏน็งฐๅฏ†้’ฅๆดพ็”Ÿ | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/crypto.rs` (~468 ่กŒ) - -```rust -// ๅฏ†้’ฅๅฏน็ป“ๆž„ -pub struct KeyPair { - secret_key: [u8; 32], // Curve25519 ็ง้’ฅ - public_key: [u8; 32], // Curve25519 ๅ…ฌ้’ฅ -} - -pub struct SigningKeyPair { - secret_key: [u8; 64], // Ed25519 ็ง้’ฅ - public_key: [u8; 32], // Ed25519 ๅ…ฌ้’ฅ -} -``` - -### 2.4 ่ง†้ข‘/้Ÿณ้ข‘ๆต - -| ็ผ–็ ๆ ผๅผ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|----------|----------|------| -| H.264 | โœ… ๅทฒๅฎž็Žฐ | ไธป่ฆๆ ผๅผ | -| H.265/HEVC | โœ… ๅทฒๅฎž็Žฐ | ้ซ˜ๆ•ˆ็ผ–็  | -| VP8 | โœ… ๅทฒๅฎž็Žฐ | WebRTC ๅ…ผๅฎน | -| VP9 | โœ… ๅทฒๅฎž็Žฐ | ้ซ˜่ดจ้‡ | -| AV1 | โœ… ๅทฒๅฎž็Žฐ | ๆ–ฐไธ€ไปฃ็ผ–็  | -| Opus ้Ÿณ้ข‘ | โœ… ๅทฒๅฎž็Žฐ | ไฝŽๅปถ่ฟŸ้Ÿณ้ข‘ | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/frame_adapters.rs` (~316 ่กŒ) - -### 2.5 HID ไบ‹ไปถ - -| ๅŠŸ่ƒฝ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|------|----------|------| -| ้ผ ๆ ‡็งปๅŠจ | โœ… ๅทฒๅฎž็Žฐ | ็ปๅฏน/็›ธๅฏนๅๆ ‡ | -| ้ผ ๆ ‡ๆŒ‰้”ฎ | โœ… ๅทฒๅฎž็Žฐ | ๅทฆ/ไธญ/ๅณ้”ฎ | -| ้ผ ๆ ‡ๆปš่ฝฎ | โœ… ๅทฒๅฎž็Žฐ | ๅž‚็›ดๆปšๅŠจ | -| ้”ฎ็›˜ๆŒ‰้”ฎ | โœ… ๅทฒๅฎž็Žฐ | ๆŒ‰ไธ‹/้‡Šๆ”พ | -| ๆŽงๅˆถ้”ฎๆ˜ ๅฐ„ | โœ… ๅทฒๅฎž็Žฐ | ControlKey โ†’ USB HID | -| X11 ้”ฎ็ ๆ˜ ๅฐ„ | โœ… ๅทฒๅฎž็Žฐ | X11 โ†’ USB HID | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/hid_adapter.rs` (~386 ่กŒ) - -### 2.6 ๅ่ฎฎๅธง็ผ–็  - -| ๅŠŸ่ƒฝ | ๅฎž็Žฐ็Šถๆ€ | ๅค‡ๆณจ | -|------|----------|------| -| BytesCodec | โœ… ๅทฒๅฎž็Žฐ | ๅ˜้•ฟๅธง็ผ–็  | -| 1-4 ๅญ—่Š‚ๅคด | โœ… ๅทฒๅฎž็Žฐ | ๆ นๆฎ้•ฟๅบฆ่‡ชๅŠจ้€‰ๆ‹ฉ | -| ๆœ€ๅคง 1GB ๆถˆๆฏ | โœ… ๅทฒๅฎž็Žฐ | ไธŽๅŽŸ็‰ˆไธ€่‡ด | - -**ๅฎž็Žฐๆ–‡ไปถ**: `src/rustdesk/bytes_codec.rs` (~253 ่กŒ) - -## 3. ๆœชๅฎž็ŽฐๅŠŸ่ƒฝ - -### 3.1 NAT ็ฉฟ้€็›ธๅ…ณ - -| ๅŠŸ่ƒฝ | ๅŽŸๅ›  | -|------|------| -| UDP ๆ‰“ๆดž | One-KVM ไป…ไฝฟ็”จ TCP ไธญ็ปง | -| TCP ๆ‰“ๆดž | ๅŒไธŠ | -| STUN/TURN | ไธ้œ€่ฆ NAT ็ฑปๅž‹ๆฃ€ๆต‹ | -| TestNat | ๅŒไธŠ | -| P2P ็›ด่ฟž | ่ฎพ่ฎก็ฎ€ๅŒ–๏ผŒไป…ๆ”ฏๆŒไธญ็ปง | - -### 3.2 ๅฎขๆˆท็ซฏๅ‘่ตทๅŠŸ่ƒฝ - -| ๅŠŸ่ƒฝ | ๅŽŸๅ›  | -|------|------| -| PunchHole (ๅ‘่ตท) | KVM ๅชๆŽฅๆ”ถ่ฟžๆŽฅ | -| RelayRequest | ๅŒไธŠ | -| ConnectPeer | ๅŒไธŠ | -| OnlineRequest | ไธ้œ€่ฆๆŸฅ่ฏขๅ…ถไป–่ฎพๅค‡ | - -### 3.3 ๆ–‡ไปถไผ ่พ“ - -| ๅŠŸ่ƒฝ | ๅŽŸๅ›  | -|------|------| -| FileTransfer | ่ถ…ๅ‡บ KVM ๅŠŸ่ƒฝ่Œƒๅ›ด | -| FileAction | ๅŒไธŠ | -| FileResponse | ๅŒไธŠ | -| FileTransferBlock | ๅŒไธŠ | - -### 3.4 ้ซ˜็บงๅŠŸ่ƒฝ - -| ๅŠŸ่ƒฝ | ๅŽŸๅ›  | -|------|------| -| ๅ‰ช่ดดๆฟๅŒๆญฅ | ่ถ…ๅ‡บ KVM ๅŠŸ่ƒฝ่Œƒๅ›ด | -| ๅคšๆ˜พ็คบๅ™จๅˆ‡ๆข | One-KVM ไฝฟ็”จๅ•ไธ€่ง†้ข‘ๆบ | -| ่™šๆ‹Ÿๆ˜พ็คบๅ™จ | ไธ้€‚็”จ | -| ็ซฏๅฃ่ฝฌๅ‘ | ่ถ…ๅ‡บ KVM ๅŠŸ่ƒฝ่Œƒๅ›ด | -| ่ฏญ้Ÿณ้€š่ฏ | ไธ้œ€่ฆ | -| RDP ่พ“ๅ…ฅ | ไธ้œ€่ฆ | -| ๆ’ไปถ็ณป็ปŸ | ไธๆ”ฏๆŒ | -| ่ฝฏไปถๆ›ดๆ–ฐ | One-KVM ๆœ‰่‡ชๅทฑ็š„ๆ›ดๆ–ฐๆœบๅˆถ | - -### 3.5 ๆƒ้™ๅๅ•† - -| ๅŠŸ่ƒฝ | ๅŽŸๅ›  | -|------|------| -| Option ๆถˆๆฏ | One-KVM ๅ‡่ฎพๅฎŒๅ…จๆŽงๅˆถๆƒ้™ | -| ๆƒ้™่ฏทๆฑ‚ | ๅŒไธŠ | -| PermissionInfo | ๅŒไธŠ | - -## 4. ๅฎž็Žฐๅทฎๅผ‚ - -### 4.1 ่ฟžๆŽฅๆจกๅผ - -**RustDesk ๅŽŸ็‰ˆ:** -``` -ๅฎขๆˆท็ซฏ โ”€โ”€UDPๆ‰“ๆดžโ”€โ”€> ่ขซๆŽง็ซฏ (P2P ไผ˜ๅ…ˆ) - โ””โ”€โ”€Relayโ”€โ”€> ่ขซๆŽง็ซฏ (ๅ›ž้€€) -``` - -**One-KVM:** -``` -RustDeskๅฎขๆˆท็ซฏ โ”€โ”€TCPไธญ็ปงโ”€โ”€> hbbrๆœๅŠกๅ™จ โ”€โ”€> One-KVM่ฎพๅค‡ -``` - -One-KVM ๅชๆ”ฏๆŒ TCP ไธญ็ปง่ฟžๆŽฅ๏ผŒไธๆ”ฏๆŒ P2P ็›ด่ฟžใ€‚่ฟ™็ฎ€ๅŒ–ไบ†ๅฎž็Žฐ๏ผŒไฝ†ๅฏ่ƒฝๅขžๅŠ ๅปถ่ฟŸใ€‚ - -### 4.2 ไผš่ฏๅŠ ๅฏ† - -**RustDesk ๅŽŸ็‰ˆ:** -- ๆ”ฏๆŒ ChaCha20-Poly1305 (ๆตๅผ) -- ๆ”ฏๆŒ XSalsa20-Poly1305 (secretbox) -- ๅŠจๆ€ๅๅ•†ๅŠ ๅฏ†ๆ–นๅผ - -**One-KVM:** -- ไป…ๆ”ฏๆŒ XSalsa20-Poly1305 (secretbox) -- ไฝฟ็”จๅบๅˆ—ๅทไฝœไธบ nonce - -```rust -// One-KVM ็š„ๅŠ ๅฏ†ๅฎž็Žฐ -fn encrypt_message(&mut self, plaintext: &[u8]) -> Vec { - let nonce = make_nonce(&self.send_nonce); - self.send_nonce = self.send_nonce.wrapping_add(1); - secretbox::seal(plaintext, &nonce, &self.session_key) -} -``` - -### 4.3 ่ง†้ข‘ๆตๆ–นๅ‘ - -**RustDesk ๅŽŸ็‰ˆ:** -- ๅŒๅ‘่ง†้ข‘ๆต๏ผˆๅฏๆŽงๅˆถๅ’Œ่ขซๆŽงๅˆถ๏ผ‰ -- ่ฟœ็จ‹ๆกŒ้ขๆ•่Žท - -**One-KVM:** -- ๅ•ๅ‘่ง†้ข‘ๆต๏ผˆไป…ๅ‘้€๏ผ‰ -- ไปŽ V4L2 ่ฎพๅค‡ๆ•่Žท -- ้›†ๆˆๅˆฐ One-KVM ็š„ VideoStreamManager - -```rust -// One-KVM ่ง†้ข‘ๆต้›†ๆˆ -pub async fn start_video_stream(&self, state: &AppState) { - let stream_manager = &state.video_stream_manager; - // ไปŽ One-KVM ็š„่ง†้ข‘็ฎก็†ๅ™จ่Žทๅ–ๅธง -} -``` - -### 4.4 HID ไบ‹ไปถๅค„็† - -**RustDesk ๅŽŸ็‰ˆ:** -- ่ฝฌๅ‘ๅˆฐ่ฟœ็จ‹็ณป็ปŸ็š„่พ“ๅ…ฅๅญ็ณป็ปŸ -- ไฝฟ็”จ enigo ๆˆ– uinput - -**One-KVM:** -- ่ฝฌๅ‘ๅˆฐ USB OTG/HID ่ฎพๅค‡ -- ๆŽงๅˆถ็‰ฉ็† KVM ็›ฎๆ ‡ๆœบๅ™จ - -```rust -// One-KVM HID ้€‚้… -pub fn convert_mouse_event(event: &RustDeskMouseEvent) -> Option { - // ่ฝฌๆข RustDesk ้ผ ๆ ‡ไบ‹ไปถๅˆฐ One-KVM HID ไบ‹ไปถ -} - -pub fn convert_key_event(event: &RustDeskKeyEvent) -> Option { - // ่ฝฌๆข RustDesk ้”ฎ็›˜ไบ‹ไปถๅˆฐ One-KVM HID ไบ‹ไปถ -} -``` - -### 4.5 ้…็ฝฎ็ฎก็† - -**RustDesk ๅŽŸ็‰ˆ:** -- ไฝฟ็”จ TOML/JSON ้…็ฝฎๆ–‡ไปถ -- ็กฌ็ผ–็ ้ป˜่ฎคๅ€ผ - -**One-KVM:** -- ้›†ๆˆๅˆฐ SQLite ้…็ฝฎ็ณป็ปŸ -- Web UI ็ฎก็† -- ไฝฟ็”จ typeshare ็”Ÿๆˆ TypeScript ็ฑปๅž‹ - -```rust -#[typeshare] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RustDeskConfig { - pub enabled: bool, - pub rendezvous_server: String, - pub device_id: String, - // ... -} -``` - -### 4.6 ่ฎพๅค‡ ID ็”Ÿๆˆ - -**RustDesk ๅŽŸ็‰ˆ:** -- ๅŸบไบŽ MAC ๅœฐๅ€ๅ’Œ็กฌไปถไฟกๆฏ -- ๅ›บๅฎšไพฟๆบๅผ ID - -**One-KVM:** -- ้šๆœบ็”Ÿๆˆ 9 ไฝๆ•ฐๅญ— -- ๅญ˜ๅ‚จๅœจ้…็ฝฎไธญ - -```rust -pub fn generate_device_id() -> String { - let mut rng = rand::thread_rng(); - let id: u32 = rng.gen_range(100_000_000..999_999_999); - id.to_string() -} -``` - -## 5. ๅ่ฎฎๅ…ผๅฎนๆ€ง - -### 5.1 ๅฎŒๅ…จๅ…ผๅฎน - -| ๅŠŸ่ƒฝ | ่ฏดๆ˜Ž | -|------|------| -| Rendezvous ๆณจๅ†Œ | ๅฏไธŽๅฎ˜ๆ–น hbbs ๆœๅŠกๅ™จ้€šไฟก | -| ไธญ็ปง่ฟžๆŽฅ | ๅฏ้€š่ฟ‡ๅฎ˜ๆ–น hbbr ๆœๅŠกๅ™จไธญ็ปง | -| ๅŠ ๅฏ†ๆกๆ‰‹ | ไธŽ RustDesk ๅฎขๆˆท็ซฏๅ…ผๅฎน | -| ่ง†้ข‘็ผ–็  | ๆ”ฏๆŒๆ‰€ๆœ‰ไธปๆต็ผ–็ ๆ ผๅผ | -| HID ไบ‹ไปถ | ๆŽฅๆ”ถๆ ‡ๅ‡† RustDesk ่พ“ๅ…ฅไบ‹ไปถ | - -### 5.2 ้ƒจๅˆ†ๅ…ผๅฎน - -| ๅŠŸ่ƒฝ | ่ฏดๆ˜Ž | -|------|------| -| ๅฏ†็ ่ฎค่ฏ | ไป…ๆ”ฏๆŒ่ฎพๅค‡ๅฏ†็ ๏ผŒไธๆ”ฏๆŒไธ€ๆฌกๆ€งๅฏ†็  | -| ไผš่ฏๅŠ ๅฏ† | ไป… XSalsa20-Poly1305 | - -### 5.3 ไธๅ…ผๅฎน - -| ๅŠŸ่ƒฝ | ่ฏดๆ˜Ž | -|------|------| -| P2P ่ฟžๆŽฅ | ๅฎขๆˆท็ซฏๅฟ…้กป้€š่ฟ‡ไธญ็ปง่ฟžๆŽฅ | -| ๆ–‡ไปถไผ ่พ“ | ไธๆ”ฏๆŒ | -| ๅ‰ช่ดดๆฟ | ไธๆ”ฏๆŒ | - -## 6. ไปฃ็ ็ป“ๆž„ๅฏนๆฏ” - -### RustDesk ๅŽŸ็‰ˆ็ป“ๆž„ - -``` -rustdesk/ -โ”œโ”€โ”€ libs/hbb_common/ # ๅ…ฌๅ…ฑๅบ“ -โ”‚ โ”œโ”€โ”€ protos/ # Protobuf ๅฎšไน‰ -โ”‚ โ””โ”€โ”€ src/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ server/ # ่ขซๆŽง็ซฏๆœๅŠก -โ”‚ โ”œโ”€โ”€ client/ # ๆŽงๅˆถ็ซฏ -โ”‚ โ”œโ”€โ”€ ui/ # ็”จๆˆท็•Œ้ข -โ”‚ โ””โ”€โ”€ rendezvous_mediator.rs -``` - -### One-KVM ็ป“ๆž„ - -``` -src/rustdesk/ -โ”œโ”€โ”€ mod.rs # ๆจกๅ—ๅฏผๅ‡บ -โ”œโ”€โ”€ config.rs # ้…็ฝฎ็ฑปๅž‹ (~164 ่กŒ) -โ”œโ”€โ”€ crypto.rs # ๅŠ ๅฏ†ๆจกๅ— (~468 ่กŒ) -โ”œโ”€โ”€ bytes_codec.rs # ๅธง็ผ–็  (~253 ่กŒ) -โ”œโ”€โ”€ protocol.rs # ๆถˆๆฏ่พ…ๅŠฉ (~170 ่กŒ) -โ”œโ”€โ”€ rendezvous.rs # Rendezvous ไธญไป‹ (~829 ่กŒ) -โ”œโ”€โ”€ connection.rs # ่ฟžๆŽฅๅค„็† (~1349 ่กŒ) -โ”œโ”€โ”€ hid_adapter.rs # HID ่ฝฌๆข (~386 ่กŒ) -โ””โ”€โ”€ frame_adapters.rs # ่ง†้ข‘/้Ÿณ้ข‘้€‚้… (~316 ่กŒ) -``` - -**ๆ€ป่ฎก**: ~3935 ่กŒไปฃ็  - -## 7. ๆ€ป็ป“ - -### ๅฎž็Žฐ็އ็ปŸ่ฎก - -| ็ฑปๅˆซ | RustDesk ๅŠŸ่ƒฝๆ•ฐ | One-KVM ๅฎž็Žฐๆ•ฐ | ๅฎž็Žฐ็އ | -|------|-----------------|----------------|--------| -| Rendezvous ๅ่ฎฎ | 15+ | 10 | ~67% | -| ่ฟžๆŽฅๅ่ฎฎ | 30+ | 12 | ~40% | -| ๅŠ ๅฏ†ๅŠŸ่ƒฝ | 8 | 6 | 75% | -| ่ง†้ข‘/้Ÿณ้ข‘ | 6 | 6 | 100% | -| HID ๅŠŸ่ƒฝ | 6 | 6 | 100% | - -### ่ฎพ่ฎก็†ๅฟต - -One-KVM ็š„ RustDesk ๅฎž็Žฐไธ“ๆณจไบŽ **IP-KVM ๆ ธๅฟƒๅŠŸ่ƒฝ**: - -1. **็ฒพ็ฎ€**: ๅชๅฎž็Žฐๅฟ…่ฆ็š„่ขซๆŽง็ซฏๅŠŸ่ƒฝ -2. **ๅฏ้ **: ไฝฟ็”จ TCP ไธญ็ปงไฟ่ฏ่ฟžๆŽฅ็จณๅฎšๆ€ง -3. **้›†ๆˆ**: ไธŽ One-KVM ็Žฐๆœ‰่ง†้ข‘/HID ็ณป็ปŸๆ— ็ผ้›†ๆˆ -4. **ๅฎ‰ๅ…จ**: ๅฎŒๆ•ดๅฎž็ŽฐๅŠ ๅฏ†ๅ’Œ่ฎค่ฏๆœบๅˆถ - -### ๅฎขๆˆท็ซฏๅ…ผๅฎนๆ€ง - -One-KVM ๅฏไธŽๆ ‡ๅ‡† RustDesk ๅฎขๆˆท็ซฏ้…ๅˆไฝฟ็”จ: -- RustDesk ๆกŒ้ขๅฎขๆˆท็ซฏ (Windows/macOS/Linux) -- RustDesk ็งปๅŠจๅฎขๆˆท็ซฏ (Android/iOS) -- RustDesk Web ๅฎขๆˆท็ซฏ - -ๅช้œ€็กฎไฟ: -1. ้…็ฝฎ็›ธๅŒ็š„ Rendezvous ๆœๅŠกๅ™จ -2. ไฝฟ็”จ่ฎพๅค‡ ID ๅ’Œๅฏ†็ ่ฟžๆŽฅ -3. ๅฎขๆˆท็ซฏๆ”ฏๆŒไธญ็ปง่ฟžๆŽฅ diff --git a/docs/system-architecture.md b/docs/system-architecture.md deleted file mode 100644 index 4db1560f..00000000 --- a/docs/system-architecture.md +++ /dev/null @@ -1,920 +0,0 @@ -# One-KVM ็ณป็ปŸๆžถๆž„ๆ–‡ๆกฃ - -## 1. ้กน็›ฎๆฆ‚่ฟฐ - -One-KVM ๆ˜ฏไธ€ไธช็”จ Rust ็ผ–ๅ†™็š„่ฝป้‡็บงใ€ๅผ€ๆบ IP-KVM ่งฃๅ†ณๆ–นๆกˆใ€‚ๅฎƒๆไพ› BIOS ็บงๅˆซ็š„่ฟœ็จ‹ๆœๅŠกๅ™จ็ฎก็†่ƒฝๅŠ›๏ผŒๆ”ฏๆŒ่ง†้ข‘ๆตใ€้”ฎ้ผ ๆŽงๅˆถใ€่™šๆ‹Ÿๅญ˜ๅ‚จใ€็”ตๆบ็ฎก็†ๅ’Œ้Ÿณ้ข‘็ญ‰ๅŠŸ่ƒฝใ€‚ - -### 1.1 ๆ ธๅฟƒ็‰นๆ€ง - -- **ๅ•ไธ€ไบŒ่ฟ›ๅˆถ้ƒจ็ฝฒ**๏ผšWeb UI + ๅŽ็ซฏไธ€ไฝ“ๅŒ–๏ผŒๆ— ้œ€้ขๅค–้…็ฝฎๆ–‡ไปถ -- **ๅŒๆตๆจกๅผ**๏ผšๆ”ฏๆŒ WebRTC๏ผˆH264/H265/VP8/VP9๏ผ‰ๅ’Œ MJPEG ไธค็งๆตๆจกๅผ -- **USB OTG**๏ผš่™šๆ‹Ÿ้”ฎ้ผ ใ€่™šๆ‹Ÿๅญ˜ๅ‚จใ€่™šๆ‹Ÿ็ฝ‘ๅก -- **ATX ็”ตๆบๆŽงๅˆถ**๏ผšGPIO/USB ็ปง็”ตๅ™จ -- **RustDesk ๅ่ฎฎ้›†ๆˆ**๏ผšๆ”ฏๆŒ่ทจๅนณๅฐ่ฎฟ้—ฎ -- **Vue3 SPA ๅ‰็ซฏ**๏ผšๆ”ฏๆŒไธญๆ–‡/่‹ฑๆ–‡ -- **SQLite ้…็ฝฎๅญ˜ๅ‚จ**๏ผšๆ— ้œ€้…็ฝฎๆ–‡ไปถ - -### 1.2 ็›ฎๆ ‡ๅนณๅฐ - -| ๅนณๅฐ | ๆžถๆž„ | ็”จ้€” | -|------|------|------| -| aarch64-unknown-linux-gnu | ARM64 | ไธป่ฆ็›ฎๆ ‡๏ผˆRockchip RK3328 ็ญ‰๏ผ‰ | -| armv7-unknown-linux-gnueabihf | ARMv7 | ๅค‡้€‰ๅนณๅฐ | -| x86_64-unknown-linux-gnu | x86-64 | ๅผ€ๅ‘/ๆต‹่ฏ•็Žฏๅขƒ | - ---- - -## 2. ็ณป็ปŸๆžถๆž„ๅ›พ - -### 2.1 ๆ•ดไฝ“ๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ One-KVM System โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Web Frontend (Vue3) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Console โ”‚ โ”‚ Settings โ”‚ โ”‚ Login โ”‚ โ”‚ Setup โ”‚ โ”‚ Virtual โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ View โ”‚ โ”‚ View โ”‚ โ”‚ View โ”‚ โ”‚ View โ”‚ โ”‚ Keyboard โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Pinia State Store โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ API Client Layer โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ HTTP REST โ”‚ WebSocket โ”‚ WebRTC Signaling โ”‚ MJPEG โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ HTTP/WS/WebRTC โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Axum Web Server (routes.rs) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Public โ”‚ โ”‚ User โ”‚ โ”‚ Admin โ”‚ โ”‚ Static โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Routes โ”‚ โ”‚ Routes โ”‚ โ”‚ Routes โ”‚ โ”‚ Files โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ AppState (state.rs) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Central State Hub โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ConfigStore โ”‚ โ”‚SessionStoreโ”‚ โ”‚ UserStore โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ (SQLite) โ”‚ โ”‚ (Memory) โ”‚ โ”‚ (SQLite) โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ EventBus โ”‚ โ”‚ OtgService โ”‚ โ”‚ Extensions โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ (Broadcast)โ”‚ โ”‚ (USB) โ”‚ โ”‚ Manager โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Video โ”‚ โ”‚ HID โ”‚ โ”‚ Audio โ”‚ โ”‚ -โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ -โ”‚ โ”‚ Capture โ”‚ โ”‚ Controller โ”‚ โ”‚ Capture โ”‚ โ”‚ -โ”‚ โ”‚ Encoder โ”‚ โ”‚ OTG Backendโ”‚ โ”‚ Encoder โ”‚ โ”‚ -โ”‚ โ”‚ Streamer โ”‚ โ”‚ CH9329 โ”‚ โ”‚ Pipeline โ”‚ โ”‚ -โ”‚ โ”‚ Pipeline โ”‚ โ”‚ Monitor โ”‚ โ”‚ (Opus) โ”‚ โ”‚ -โ”‚ โ”‚ Manager โ”‚ โ”‚ DataChan โ”‚ โ”‚ Shared โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ MSD โ”‚ โ”‚ ATX โ”‚ โ”‚ RustDesk โ”‚ โ”‚ -โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ -โ”‚ โ”‚ Controller โ”‚ โ”‚ Controller โ”‚ โ”‚ Service โ”‚ โ”‚ -โ”‚ โ”‚ Image Mgr โ”‚ โ”‚ Executor โ”‚ โ”‚ Rendezvous โ”‚ โ”‚ -โ”‚ โ”‚ Ventoy โ”‚ โ”‚ LED Monitorโ”‚ โ”‚ Connection โ”‚ โ”‚ -โ”‚ โ”‚ Drive โ”‚ โ”‚ WOL โ”‚ โ”‚ Protocol โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Hardware Layer โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ V4L2 Video โ”‚ โ”‚ USB OTG โ”‚ โ”‚ GPIO โ”‚ โ”‚ ALSA โ”‚ โ”‚ -โ”‚ โ”‚ Device โ”‚ โ”‚ Gadget โ”‚ โ”‚ Sysfs โ”‚ โ”‚ Audio โ”‚ โ”‚ -โ”‚ โ”‚/dev/video* โ”‚ โ”‚ ConfigFS โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 2.2 ๆ•ฐๆฎๆตๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Data Flow Overview โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ Target PC โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ HDMI Capture โ”‚ โ”‚ USB Port โ”‚ โ”‚ GPIO/Relay โ”‚ -โ”‚ Card โ”‚ โ”‚ (OTG Mode) โ”‚ โ”‚ (ATX) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ /dev/video0 โ”‚ โ”‚ /dev/hidg* โ”‚ โ”‚ /sys/class/ โ”‚ -โ”‚ (V4L2) โ”‚ โ”‚ (USB Gadget) โ”‚ โ”‚ gpio/gpio* โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ One-KVM Application โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Video โ”‚ โ”‚ HID โ”‚ โ”‚ ATX โ”‚ โ”‚ -โ”‚ โ”‚ Pipeline โ”‚ โ”‚ Controller โ”‚ โ”‚ Controller โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Event Bus โ”‚ โ”‚ -โ”‚ โ”‚ (tokio broadcast channel) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Web Server (Axum) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ MJPEG โ”‚ โ”‚ WebRTC โ”‚ โ”‚WebSocket โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Stream โ”‚ โ”‚ Stream โ”‚ โ”‚ Events โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Client Browser โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Video โ”‚ โ”‚ Input โ”‚ โ”‚ Control โ”‚ โ”‚ -โ”‚ โ”‚ Display โ”‚ โ”‚ Events โ”‚ โ”‚ Panel โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 3. ๆจกๅ—ไพ่ต–ๅ…ณ็ณป - -### 3.1 ๆจกๅ—ๅฑ‚ๆฌกๅ›พ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Application Layer โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ main.rs โ”€โ”€โ–บ state.rs โ”€โ”€โ–บ web/routes.rs โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚video/โ”‚ โ”‚ hid/ โ”‚ โ”‚ msd/ โ”‚ โ”‚ atx/ โ”‚ โ”‚audio/โ”‚ โ”‚webrtcโ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”ฌโ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ otg/ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ (OtgSvc) โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Infrastructure Layer โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ config/ โ”‚ โ”‚ auth/ โ”‚ โ”‚ events/ โ”‚ โ”‚extensionsโ”‚ โ”‚ -โ”‚ โ”‚(ConfigSt)โ”‚ โ”‚(Session) โ”‚ โ”‚(EventBus)โ”‚ โ”‚(ExtMgr) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ rustdesk/ (RustDeskService) โ”‚ โ”‚ -โ”‚ โ”‚ connection.rs โ”‚ rendezvous.rs โ”‚ crypto.rs โ”‚ protocol.rs โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 3.2 ไพ่ต–็Ÿฉ้˜ต - -| ๆจกๅ— | ไพ่ต–็š„ๆจกๅ— | -|------|-----------| -| `main.rs` | state, config, auth, video, hid, msd, atx, audio, webrtc, web, rustdesk, events | -| `state.rs` | config, auth, video, hid, msd, atx, audio, webrtc, rustdesk, events, otg | -| `video/` | events, hwcodec (ๅค–้ƒจ) | -| `hid/` | otg, events | -| `msd/` | otg, events | -| `atx/` | events | -| `audio/` | events | -| `webrtc/` | video, audio, hid, events | -| `web/` | state, auth, config, video, hid, msd, atx, audio, webrtc, events | -| `rustdesk/` | video, audio, hid, events | -| `otg/` | (ๆ— ๅ†…้ƒจไพ่ต–) | -| `config/` | (ๆ— ๅ†…้ƒจไพ่ต–) | -| `auth/` | config | -| `events/` | (ๆ— ๅ†…้ƒจไพ่ต–) | - ---- - -## 4. ๆ ธๅฟƒ็ป„ไปถ่ฏฆ่งฃ - -### 4.1 AppState (state.rs) - -AppState ๆ˜ฏๆ•ดไธชๅบ”็”จ็š„็Šถๆ€ไธญๆžข๏ผŒ้€š่ฟ‡ `Arc` ๅŒ…่ฃ…็š„ๆ–นๅผๅœจๆ‰€ๆœ‰ handler ไน‹้—ดๅ…ฑไบซใ€‚ - -```rust -pub struct AppState { - // ้…็ฝฎๅ’Œๅญ˜ๅ‚จ - config: ConfigStore, // SQLite ้…็ฝฎๅญ˜ๅ‚จ - sessions: SessionStore, // ไผš่ฏๅญ˜ๅ‚จ๏ผˆๅ†…ๅญ˜๏ผ‰ - users: UserStore, // SQLite ็”จๆˆทๅญ˜ๅ‚จ - - // ๆ ธๅฟƒๆœๅŠก - otg_service: Arc, // USB Gadget ็ปŸไธ€็ฎก็†๏ผˆHID/MSD ็”Ÿๅ‘ฝๅ‘จๆœŸๅ่ฐƒ่€…๏ผ‰ - stream_manager: Arc, // ่ง†้ข‘ๆต็ฎก็†ๅ™จ๏ผˆMJPEG/WebRTC๏ผ‰ - hid: Arc, // HID ๆŽงๅˆถๅ™จ๏ผˆ้”ฎ้ผ ๆŽงๅˆถ๏ผ‰ - msd: Arc>>, // MSD ๆŽงๅˆถๅ™จ๏ผˆๅฏ้€‰๏ผŒ่™šๆ‹ŸU็›˜๏ผ‰ - atx: Arc>>, // ATX ๆŽงๅˆถๅ™จ๏ผˆๅฏ้€‰๏ผŒ็”ตๆบๆŽงๅˆถ๏ผ‰ - audio: Arc, // ้Ÿณ้ข‘ๆŽงๅˆถๅ™จ๏ผˆALSA + Opus๏ผ‰ - rustdesk: Arc>>>, // RustDesk๏ผˆๅฏ้€‰๏ผŒ่ฟœ็จ‹่ฎฟ้—ฎ๏ผ‰ - extensions: Arc,// ๆ‰ฉๅฑ•็ฎก็†ๅ™จ๏ผˆttyd, gostc, easytier๏ผ‰ - - // ้€šไฟกๅ’Œ็”Ÿๅ‘ฝๅ‘จๆœŸ - events: Arc, // ไบ‹ไปถๆ€ป็บฟ๏ผˆtokio broadcast channel๏ผ‰ - shutdown_tx: broadcast::Sender<()>, // ๅ…ณ้—ญไฟกๅท - data_dir: PathBuf, // ๆ•ฐๆฎ็›ฎๅฝ• -} -``` - -### 4.2 ่ง†้ข‘ๆต็ฎก้“ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Video Pipeline Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ V4L2 Device โ”‚ -โ”‚ /dev/video0 โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ Raw MJPEG/YUYV/NV12 - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ VideoCapturer โ”‚ โ—„โ”€โ”€โ”€ src/video/capture.rs -โ”‚ (capture.rs) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ VideoFrame - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ SharedVideoPipeline โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Decode Stage โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ MJPEG โ†’ YUV โ”‚ turbojpeg / VAAPI โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Convert Stage โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚YUV โ†’ Target โ”‚ libyuv (SIMD accelerated) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Format โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Encode Stage โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ H264 โ”‚ โ”‚ H265 โ”‚ โ”‚ VP8 โ”‚ โ”‚ VP9 โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚Encoder โ”‚ โ”‚Encoder โ”‚ โ”‚Encoder โ”‚ โ”‚Encoder โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ (VAAPI/RKMPP/V4L2 M2M/Software) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ MJPEG Streamer โ”‚ โ”‚ WebRTC Streamer โ”‚ โ”‚ RustDesk Service โ”‚ -โ”‚ (HTTP Stream) โ”‚ โ”‚ (RTP Packets) โ”‚ โ”‚ (P2P Stream) โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ - HTTP/1.1 โ”‚ โ”‚ - DataChannel โ”‚ โ”‚ - TCP/UDP Relay โ”‚ -โ”‚ - multipart/x- โ”‚ โ”‚ - SRTP โ”‚ โ”‚ - NaCl Encrypted โ”‚ -โ”‚ mixed-replace โ”‚ โ”‚ - ICE/STUN/TURN โ”‚ โ”‚ - Rendezvous โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ - โ–ผ โ–ผ โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Clients โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Browser โ”‚ โ”‚ Browser โ”‚ โ”‚ RustDesk Client โ”‚ โ”‚ -โ”‚ โ”‚ (MJPEG) โ”‚ โ”‚ (WebRTC) โ”‚ โ”‚ (Desktop/Mobile) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 4.3 OTG ๆœๅŠกๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ OTG Service Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ OtgService (service.rs) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Public Interface โ”‚ โ”‚ -โ”‚ โ”‚ enable_hid() โ”‚ disable_hid() โ”‚ enable_msd() โ”‚ disable_msd() โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ OtgGadgetManager (manager.rs) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Gadget Lifecycle โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ create_gadget() โ”‚ destroy_gadget() โ”‚ bind_udc() โ”‚ unbind() โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ HID Function โ”‚ โ”‚ MSD Function โ”‚ โ”‚ Endpoint Alloc โ”‚ โ”‚ -โ”‚ โ”‚ (hid.rs) โ”‚ โ”‚ (msd.rs) โ”‚ โ”‚ (endpoint.rs) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ ConfigFS Operations โ”‚ โ”‚ -โ”‚ โ”‚ /sys/kernel/config/usb_gadget/one-kvm/ โ”‚ โ”‚ -โ”‚ โ”‚ โ”œโ”€โ”€ idVendor, idProduct, strings/ โ”‚ โ”‚ -โ”‚ โ”‚ โ”œโ”€โ”€ configs/c.1/ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ functions/ (symlinks) โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€ functions/ โ”‚ โ”‚ -โ”‚ โ”‚ โ”œโ”€โ”€ hid.usb0, hid.usb1, hid.usb2 โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€ mass_storage.usb0 โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Linux Kernel โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ /dev/hidg* โ”‚ โ”‚ Mass Storage โ”‚ โ”‚ -โ”‚ โ”‚ (HID devices) โ”‚ โ”‚ Backend โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 4.4 ไบ‹ไปถ็ณป็ปŸๆžถๆž„ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Event System Architecture โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Event Producers โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ Video โ”‚ โ”‚ HID โ”‚ โ”‚ MSD โ”‚ โ”‚ ATX โ”‚ โ”‚ Audio โ”‚ โ”‚ -โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ Module โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ EventBus โ”‚ โ”‚ -โ”‚ โ”‚ (tokio broadcast channel) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ SystemEvent Enum โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ StreamStateChanged โ”‚ HidStateChanged โ”‚ MsdStateChanged โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ AtxStateChanged โ”‚ AudioStateChanged โ”‚ DeviceInfo โ”‚ Error โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ–ผ โ–ผ โ–ผ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚WebSocket โ”‚ โ”‚ DeviceInfoโ”‚ โ”‚ Internal โ”‚ โ”‚ -โ”‚ โ”‚ Clients โ”‚ โ”‚Broadcasterโ”‚ โ”‚ Tasks โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 5. ๅˆๅง‹ๅŒ–ๆต็จ‹ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Application Startup Flow โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -main() - โ”‚ - โ”œโ”€โ”€โ–บ Parse CLI Arguments (clap) - โ”‚ - address, port, data_dir - โ”‚ - enable_https, ssl_cert, ssl_key - โ”‚ - verbosity (-v, -vv, -vvv) - โ”‚ - โ”œโ”€โ”€โ–บ Initialize Logging (tracing) - โ”‚ - โ”œโ”€โ”€โ–บ Create/Open SQLite Database - โ”‚ โ””โ”€โ–บ ConfigStore::new() - โ”‚ โ””โ”€โ–บ UserStore::new() - โ”‚ โ””โ”€โ–บ SessionStore::new() - โ”‚ - โ”œโ”€โ”€โ–บ Initialize Core Services - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ EventBus::new() - โ”‚ โ”‚ โ””โ”€โ–บ Create tokio broadcast channel - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ OtgService::new() - โ”‚ โ”‚ โ””โ”€โ–บ Detect UDC device (/sys/class/udc) - โ”‚ โ”‚ โ””โ”€โ–บ Initialize OtgGadgetManager - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ HidController::new() - โ”‚ โ”‚ โ””โ”€โ–บ Detect backend type (OTG/CH9329/None) - โ”‚ โ”‚ โ””โ”€โ–บ Create controller with optional OtgService - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ HidController::init() - โ”‚ โ”‚ โ””โ”€โ–บ Request HID function from OtgService - โ”‚ โ”‚ โ””โ”€โ–บ Create HID devices (/dev/hidg0-3) - โ”‚ โ”‚ โ””โ”€โ–บ Open device files with O_NONBLOCK - โ”‚ โ”‚ โ””โ”€โ–บ Initialize HidHealthMonitor - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ MsdController::init() (if configured) - โ”‚ โ”‚ โ””โ”€โ–บ Request MSD function from OtgService - โ”‚ โ”‚ โ””โ”€โ–บ Create mass storage device - โ”‚ โ”‚ โ””โ”€โ–บ Initialize Ventoy drive (if available) - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ AtxController::init() (if configured) - โ”‚ โ”‚ โ””โ”€โ–บ Setup GPIO pins or USB relay - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ AudioController::init() - โ”‚ โ”‚ โ””โ”€โ–บ Open ALSA device - โ”‚ โ”‚ โ””โ”€โ–บ Initialize Opus encoder - โ”‚ โ”‚ - โ”‚ โ”œโ”€โ”€โ–บ VideoStreamManager::new() - โ”‚ โ”‚ โ””โ”€โ–บ Initialize SharedVideoPipeline - โ”‚ โ”‚ โ””โ”€โ–บ Setup encoder registry (H264/H265/VP8/VP9) - โ”‚ โ”‚ โ””โ”€โ–บ Detect hardware acceleration (VAAPI/RKMPP/V4L2 M2M) - โ”‚ โ”‚ - โ”‚ โ””โ”€โ”€โ–บ RustDeskService::new() (if configured) - โ”‚ โ””โ”€โ–บ Load/generate device ID and keys - โ”‚ โ””โ”€โ–บ Connect to rendezvous server - โ”‚ - โ”œโ”€โ”€โ–บ Create AppState - โ”‚ โ””โ”€โ–บ Wrap all services in Arc<> - โ”‚ - โ”œโ”€โ”€โ–บ Spawn Background Tasks - โ”‚ โ”œโ”€โ”€โ–บ spawn_device_info_broadcaster() - โ”‚ โ”œโ”€โ”€โ–บ extension_health_check_task() - โ”‚ โ””โ”€โ”€โ–บ rustdesk_reconnect_task() - โ”‚ - โ”œโ”€โ”€โ–บ Create Axum Router - โ”‚ โ””โ”€โ–บ create_router(app_state) - โ”‚ - โ””โ”€โ”€โ–บ Start HTTP/HTTPS Server - โ””โ”€โ–บ axum::serve() or axum_server with TLS -``` - ---- - -## 6. ็›ฎๅฝ•็ป“ๆž„ - -``` -One-KVM-RUST/ -โ”œโ”€โ”€ src/ # Rust ๆบไปฃ็  -โ”‚ โ”œโ”€โ”€ main.rs # ๅบ”็”จๅ…ฅๅฃ็‚น -โ”‚ โ”œโ”€โ”€ lib.rs # ๅบ“ๅฏผๅ‡บ -โ”‚ โ”œโ”€โ”€ state.rs # AppState ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ error.rs # ้”™่ฏฏ็ฑปๅž‹ๅฎšไน‰ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ video/ # ่ง†้ข‘ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ capture.rs # V4L2 ้‡‡้›† -โ”‚ โ”‚ โ”œโ”€โ”€ streamer.rs # ่ง†้ข‘ๆตๆœๅŠก -โ”‚ โ”‚ โ”œโ”€โ”€ stream_manager.rs # ๆต็ฎก็†ๅ™จ -โ”‚ โ”‚ โ”œโ”€โ”€ shared_video_pipeline.rs # ๅ…ฑไบซ่ง†้ข‘็ฎก้“ -โ”‚ โ”‚ โ”œโ”€โ”€ format.rs # ๅƒ็ด ๆ ผๅผ -โ”‚ โ”‚ โ”œโ”€โ”€ frame.rs # ่ง†้ข‘ๅธง -โ”‚ โ”‚ โ”œโ”€โ”€ convert.rs # ๆ ผๅผ่ฝฌๆข -โ”‚ โ”‚ โ””โ”€โ”€ encoder/ # ็ผ–็ ๅ™จ -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ traits.rs -โ”‚ โ”‚ โ”œโ”€โ”€ h264.rs -โ”‚ โ”‚ โ”œโ”€โ”€ h265.rs -โ”‚ โ”‚ โ”œโ”€โ”€ vp8.rs -โ”‚ โ”‚ โ”œโ”€โ”€ vp9.rs -โ”‚ โ”‚ โ””โ”€โ”€ jpeg.rs -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ hid/ # HID ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs # HidController๏ผˆไธปๆŽงๅˆถๅ™จ๏ผ‰ -โ”‚ โ”‚ โ”œโ”€โ”€ backend.rs # HidBackend trait ๅ’Œ HidBackendType -โ”‚ โ”‚ โ”œโ”€โ”€ otg.rs # OTG ๅŽ็ซฏ๏ผˆUSB Gadget HID๏ผ‰ -โ”‚ โ”‚ โ”œโ”€โ”€ ch9329.rs # CH9329 ไธฒๅฃๅŽ็ซฏ -โ”‚ โ”‚ โ”œโ”€โ”€ consumer.rs # Consumer Control usage codes -โ”‚ โ”‚ โ”œโ”€โ”€ keymap.rs # JS keyCode โ†’ USB HID ่ฝฌๆข่กจ -โ”‚ โ”‚ โ”œโ”€โ”€ types.rs # ไบ‹ไปถ็ฑปๅž‹ๅฎšไน‰ -โ”‚ โ”‚ โ”œโ”€โ”€ monitor.rs # HidHealthMonitor๏ผˆ้”™่ฏฏ่ทŸ่ธชไธŽๆขๅค๏ผ‰ -โ”‚ โ”‚ โ”œโ”€โ”€ datachannel.rs # DataChannel ไบŒ่ฟ›ๅˆถๅ่ฎฎ่งฃๆž -โ”‚ โ”‚ โ””โ”€โ”€ websocket.rs # WebSocket ไบŒ่ฟ›ๅˆถๅ่ฎฎ้€‚้… -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ otg/ # USB OTG ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ service.rs # OtgService -โ”‚ โ”‚ โ”œโ”€โ”€ manager.rs # GadgetManager -โ”‚ โ”‚ โ”œโ”€โ”€ hid.rs # HID Function -โ”‚ โ”‚ โ”œโ”€โ”€ msd.rs # MSD Function -โ”‚ โ”‚ โ”œโ”€โ”€ configfs.rs # ConfigFS ๆ“ไฝœ -โ”‚ โ”‚ โ”œโ”€โ”€ endpoint.rs # ็ซฏ็‚นๅˆ†้… -โ”‚ โ”‚ โ””โ”€โ”€ report_desc.rs # HID ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ msd/ # MSD ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ controller.rs # MsdController -โ”‚ โ”‚ โ”œโ”€โ”€ image.rs # ้•œๅƒ็ฎก็† -โ”‚ โ”‚ โ”œโ”€โ”€ ventoy_drive.rs # Ventoy ้ฉฑๅŠจ -โ”‚ โ”‚ โ”œโ”€โ”€ monitor.rs # ๅฅๅบท็›‘่ง† -โ”‚ โ”‚ โ””โ”€โ”€ types.rs # ็ฑปๅž‹ๅฎšไน‰ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ atx/ # ATX ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ controller.rs # AtxController -โ”‚ โ”‚ โ”œโ”€โ”€ executor.rs # ๅŠจไฝœๆ‰ง่กŒๅ™จ -โ”‚ โ”‚ โ”œโ”€โ”€ types.rs # ็ฑปๅž‹ๅฎšไน‰ -โ”‚ โ”‚ โ”œโ”€โ”€ led.rs # LED ็›‘่ง† -โ”‚ โ”‚ โ””โ”€โ”€ wol.rs # Wake-on-LAN -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ audio/ # ้Ÿณ้ข‘ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ controller.rs # AudioController -โ”‚ โ”‚ โ”œโ”€โ”€ capture.rs # ALSA ้‡‡้›† -โ”‚ โ”‚ โ”œโ”€โ”€ encoder.rs # Opus ็ผ–็  -โ”‚ โ”‚ โ”œโ”€โ”€ shared_pipeline.rs # ๅ…ฑไบซ็ฎก้“ -โ”‚ โ”‚ โ”œโ”€โ”€ monitor.rs # ๅฅๅบท็›‘่ง† -โ”‚ โ”‚ โ””โ”€โ”€ device.rs # ่ฎพๅค‡ๆžšไธพ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ webrtc/ # WebRTC ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ webrtc_streamer.rs # WebRTC ็ฎก็†ๅ™จ -โ”‚ โ”‚ โ”œโ”€โ”€ universal_session.rs # ไผš่ฏ็ฎก็† -โ”‚ โ”‚ โ”œโ”€โ”€ video_track.rs # ่ง†้ข‘่ฝจ้“ -โ”‚ โ”‚ โ”œโ”€โ”€ rtp.rs # RTP ๆ‰“ๅŒ… -โ”‚ โ”‚ โ”œโ”€โ”€ h265_payloader.rs # H265 RTP -โ”‚ โ”‚ โ”œโ”€โ”€ peer.rs # PeerConnection -โ”‚ โ”‚ โ”œโ”€โ”€ config.rs # ้…็ฝฎ -โ”‚ โ”‚ โ”œโ”€โ”€ signaling.rs # ไฟกไปค -โ”‚ โ”‚ โ””โ”€โ”€ track.rs # ่ฝจ้“ๅŸบ็ฑป -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ auth/ # ่ฎค่ฏๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ user.rs # ็”จๆˆท็ฎก็† -โ”‚ โ”‚ โ”œโ”€โ”€ session.rs # ไผš่ฏ็ฎก็† -โ”‚ โ”‚ โ”œโ”€โ”€ password.rs # ๅฏ†็ ๅ“ˆๅธŒ -โ”‚ โ”‚ โ””โ”€โ”€ middleware.rs # Axum ไธญ้—ดไปถ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ config/ # ้…็ฝฎๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ schema.rs # ้…็ฝฎ็ป“ๆž„ๅฎšไน‰ -โ”‚ โ”‚ โ””โ”€โ”€ store.rs # SQLite ๅญ˜ๅ‚จ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ events/ # ไบ‹ไปถๆจกๅ— -โ”‚ โ”‚ โ””โ”€โ”€ mod.rs # EventBus -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ rustdesk/ # RustDesk ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs # RustDeskService -โ”‚ โ”‚ โ”œโ”€โ”€ connection.rs # ่ฟžๆŽฅ็ฎก็† -โ”‚ โ”‚ โ”œโ”€โ”€ rendezvous.rs # ๆธฒๆŸ“ๆœๅŠกๅ™จ้€šไฟก -โ”‚ โ”‚ โ”œโ”€โ”€ crypto.rs # NaCl ๅŠ ๅฏ† -โ”‚ โ”‚ โ”œโ”€โ”€ config.rs # ้…็ฝฎ -โ”‚ โ”‚ โ”œโ”€โ”€ hid_adapter.rs # HID ้€‚้… -โ”‚ โ”‚ โ”œโ”€โ”€ frame_adapters.rs # ๅธงๆ ผๅผ่ฝฌๆข -โ”‚ โ”‚ โ”œโ”€โ”€ protocol.rs # ๅ่ฎฎๅŒ…่ฃ… -โ”‚ โ”‚ โ””โ”€โ”€ bytes_codec.rs # ๅธง็ผ–็  -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ extensions/ # ๆ‰ฉๅฑ•ๆจกๅ— -โ”‚ โ”‚ โ””โ”€โ”€ mod.rs # ExtensionManager -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ web/ # Web ๆจกๅ— -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ”œโ”€โ”€ routes.rs # ่ทฏ็”ฑๅฎšไน‰ -โ”‚ โ”‚ โ”œโ”€โ”€ ws.rs # WebSocket -โ”‚ โ”‚ โ”œโ”€โ”€ audio_ws.rs # ้Ÿณ้ข‘ WebSocket -โ”‚ โ”‚ โ”œโ”€โ”€ static_files.rs # ้™ๆ€ๆ–‡ไปถ -โ”‚ โ”‚ โ””โ”€โ”€ handlers/ # API ๅค„็†ๅ™จ -โ”‚ โ”‚ โ”œโ”€โ”€ mod.rs -โ”‚ โ”‚ โ””โ”€โ”€ config/ -โ”‚ โ”‚ -โ”‚ โ”œโ”€โ”€ stream/ # MJPEG ๆต -โ”‚ โ”‚ โ””โ”€โ”€ mod.rs -โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€ utils/ # ๅทฅๅ…ทๅ‡ฝๆ•ฐ -โ”‚ โ””โ”€โ”€ mod.rs -โ”‚ -โ”œโ”€โ”€ web/ # Vue3 ๅ‰็ซฏ -โ”‚ โ”œโ”€โ”€ src/ -โ”‚ โ”‚ โ”œโ”€โ”€ views/ # ้กต้ข็ป„ไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ components/ # UI ็ป„ไปถ -โ”‚ โ”‚ โ”œโ”€โ”€ api/ # API ๅฎขๆˆท็ซฏ -โ”‚ โ”‚ โ”œโ”€โ”€ stores/ # Pinia ็Šถๆ€ -โ”‚ โ”‚ โ”œโ”€โ”€ router/ # ่ทฏ็”ฑ้…็ฝฎ -โ”‚ โ”‚ โ”œโ”€โ”€ i18n/ # ๅ›ฝ้™…ๅŒ– -โ”‚ โ”‚ โ””โ”€โ”€ types/ # TypeScript ็ฑปๅž‹ -โ”‚ โ””โ”€โ”€ package.json -โ”‚ -โ”œโ”€โ”€ libs/ # ๅค–้ƒจๅบ“ -โ”‚ โ”œโ”€โ”€ hwcodec/ # ็กฌไปถ่ง†้ข‘็ผ–็  -โ”‚ โ””โ”€โ”€ ventoy-img-rs/ # Ventoy ๆ”ฏๆŒ -โ”‚ -โ”œโ”€โ”€ protos/ # Protobuf ๅฎšไน‰ -โ”‚ โ”œโ”€โ”€ message.proto # RustDesk ๆถˆๆฏ -โ”‚ โ””โ”€โ”€ rendezvous.proto # RustDesk ๆธฒๆŸ“ -โ”‚ -โ”œโ”€โ”€ docs/ # ๆ–‡ๆกฃ -โ”œโ”€โ”€ scripts/ # ่„šๆœฌ -โ”œโ”€โ”€ Cargo.toml # Rust ้…็ฝฎ -โ”œโ”€โ”€ build.rs # ๆž„ๅปบ่„šๆœฌ -โ””โ”€โ”€ README.md -``` - ---- - -## 7. ๅฎ‰ๅ…จๆžถๆž„ - -### 7.1 ่ฎค่ฏๆต็จ‹ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Authentication Flow โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Client โ”‚ โ”‚ Axum โ”‚ โ”‚ Auth โ”‚ โ”‚ SQLite โ”‚ -โ”‚ Browser โ”‚ โ”‚ Server โ”‚ โ”‚ Module โ”‚ โ”‚ Database โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ POST /auth/login โ”‚ โ”‚ โ”‚ - โ”‚ {username, pass} โ”‚ โ”‚ โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ verify_user() โ”‚ โ”‚ - โ”‚ โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ SELECT user โ”‚ - โ”‚ โ”‚ โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ - โ”‚ โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ Argon2 verify โ”‚ - โ”‚ โ”‚ โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ session_token โ”‚ โ”‚ - โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ Set-Cookie: โ”‚ โ”‚ โ”‚ - โ”‚ session_id=token โ”‚ โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ GET /api/... โ”‚ โ”‚ โ”‚ - โ”‚ Cookie: session โ”‚ โ”‚ โ”‚ - โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ โ”‚ - โ”‚ โ”‚ validate_session()โ”‚ โ”‚ - โ”‚ โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ โ”‚ - โ”‚ โ”‚ user_info โ”‚ โ”‚ - โ”‚ โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ โ”‚ - โ”‚ โ”‚ โ”‚ โ”‚ - โ”‚ Response โ”‚ โ”‚ โ”‚ - โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ โ”‚ โ”‚ -``` - -### 7.2 ๆƒ้™ๅฑ‚็บง - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Permission Levels โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Public (No Auth) โ”‚ -โ”‚ โ”œโ”€โ”€ GET /health โ”‚ -โ”‚ โ”œโ”€โ”€ POST /auth/login โ”‚ -โ”‚ โ”œโ”€โ”€ GET /setup โ”‚ -โ”‚ โ””โ”€โ”€ POST /setup/init โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ User (Authenticated) โ”‚ -โ”‚ โ”œโ”€โ”€ GET /info (็ณป็ปŸไฟกๆฏ) โ”‚ -โ”‚ โ”œโ”€โ”€ GET /devices (่ฎพๅค‡ๅˆ—่กจ) โ”‚ -โ”‚ โ”œโ”€โ”€ GET/POST /stream/* (ๆตๆŽงๅˆถ) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /webrtc/* (WebRTC ไฟกไปค) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /hid/* (HID ๆŽงๅˆถ) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /audio/* (้Ÿณ้ข‘ๆŽงๅˆถ) โ”‚ -โ”‚ โ””โ”€โ”€ WebSocket endpoints (ๅฎžๆ—ถ้€šไฟก) โ”‚ -โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค -โ”‚ Admin (Admin Role) โ”‚ -โ”‚ โ”œโ”€โ”€ GET/PATCH /config/* (้…็ฝฎ็ฎก็†) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /msd/* (MSD ๆ“ไฝœ) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /atx/* (็”ตๆบๆŽงๅˆถ) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /extensions/* (ๆ‰ฉๅฑ•็ฎก็†) โ”‚ -โ”‚ โ”œโ”€โ”€ POST /rustdesk/* (RustDesk ้…็ฝฎ) โ”‚ -โ”‚ โ””โ”€โ”€ POST /users/* (็”จๆˆท็ฎก็†) โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - ---- - -## 8. ้ƒจ็ฝฒๆžถๆž„ - -### 8.1 ๅ•ๆœบ้ƒจ็ฝฒ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Single Binary Deployment โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ARM64 Device (e.g., Rockchip RK3328) โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ one-kvm (single binary, ~15MB) โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Embedded Assets (rust-embed, gzip compressed) โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ - index.html, app.js, app.css, assets/* โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ Runtime Data (data_dir) โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ - one-kvm.db (SQLite) โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ - images/ (MSD images) โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ - certs/ (SSL certificates) โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ -โ”‚ Hardware Connections: โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ HDMI Input โ”‚ โ”‚ USB OTG Port โ”‚ โ”‚ GPIO Header โ”‚ โ”‚ -โ”‚ โ”‚ (/dev/video0) โ”‚ โ”‚ (USB Gadget) โ”‚ โ”‚ (ATX Control) โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”‚ USB Cable - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Target PC โ”‚ -โ”‚ - Receives USB HID events (keyboard/mouse) โ”‚ -โ”‚ - Provides HDMI video output โ”‚ -โ”‚ - Can boot from virtual USB drive โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -``` - -### 8.2 ็ฝ‘็ปœๆ‹“ๆ‰‘ - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ Network Topology โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - - Internet - โ”‚ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ RustDesk โ”‚ โ”‚ Client โ”‚ - โ”‚ Server โ”‚ โ”‚ Browser โ”‚ - โ”‚ (hbbs/hbbr) โ”‚ โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ โ”‚ - โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” - โ”‚ Router โ”‚ - โ”‚ NAT โ”‚ - โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ - โ”‚ - Local Network - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ โ”‚ - โ–ผ โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ One-KVM โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ Target PC โ”‚ - โ”‚ Device โ”‚ USB โ”‚ โ”‚ - โ”‚ :8080/:8443 โ”‚ HID โ”‚ โ”‚ - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -Access Methods: -1. Local: http://one-kvm.local:8080 -2. HTTPS: https://one-kvm.local:8443 -3. RustDesk: Via RustDesk client with device ID -``` - ---- - -## 9. ๆ‰ฉๅฑ•็‚น - -### 9.1 ๆทปๅŠ ๆ–ฐ็ผ–็ ๅ™จ - -```rust -// 1. ๅฎž็Žฐ Encoder trait -impl Encoder for MyEncoder { - fn encode(&mut self, frame: &VideoFrame) -> Result>; - fn codec(&self) -> Codec; - fn bitrate(&self) -> u32; - // ... -} - -// 2. ๅœจ registry ไธญๆณจๅ†Œ -encoder_registry.register("my-encoder", || Box::new(MyEncoder::new())); -``` - -### 9.2 ๆทปๅŠ ๆ–ฐ HID ๅŽ็ซฏ - -```rust -// 1. ๅœจ backend.rs ไธญๅฎšไน‰ๆ–ฐๅŽ็ซฏ็ฑปๅž‹ -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum HidBackendType { - Otg, - Ch9329 { port: String, baud_rate: u32 }, - MyBackend { /* ้…็ฝฎๅ‚ๆ•ฐ */ }, // ๆ–ฐๅขž - None, -} - -// 2. ๅฎž็Žฐ HidBackend trait -#[async_trait] -impl HidBackend for MyBackend { - fn name(&self) -> &'static str { "MyBackend" } - async fn init(&self) -> Result<()> { /* ... */ } - async fn send_keyboard(&self, event: KeyboardEvent) -> Result<()> { /* ... */ } - async fn send_mouse(&self, event: MouseEvent) -> Result<()> { /* ... */ } - async fn send_consumer(&self, event: ConsumerEvent) -> Result<()> { /* ... */ } - async fn reset(&self) -> Result<()> { /* ... */ } - async fn shutdown(&self) -> Result<()> { /* ... */ } - fn supports_absolute_mouse(&self) -> bool { true } - fn screen_resolution(&self) -> Option<(u32, u32)> { None } - fn set_screen_resolution(&mut self, width: u32, height: u32) { /* ... */ } -} - -// 3. ๅœจ HidController::init() ไธญๆทปๅŠ ๅˆ†ๆ”ฏ -match backend_type { - HidBackendType::MyBackend { /* params */ } => { - Box::new(MyBackend::new(/* params */)?) - } - // ... -} -``` - -### 9.3 ๆทปๅŠ ๆ–ฐๆ‰ฉๅฑ• - -```rust -// ้€š่ฟ‡ ExtensionManager ็ฎก็†ๅค–้ƒจ่ฟ›็จ‹ -extension_manager.register("my-extension", ExtensionConfig { - command: "my-binary", - args: vec!["--port", "9000"], - health_check: HealthCheckConfig::Http { url: "http://localhost:9000/health" }, -}); -``` - ---- - -## 10. ๅ‚่€ƒ่ต„ๆ–™ - -- [Axum Web Framework](https://github.com/tokio-rs/axum) -- [webrtc-rs](https://github.com/webrtc-rs/webrtc) -- [V4L2 Documentation](https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/v4l2.html) -- [Linux USB Gadget](https://www.kernel.org/doc/html/latest/usb/gadget_configfs.html) -- [RustDesk Protocol](https://github.com/rustdesk/rustdesk) diff --git a/docs/tech-stack.md b/docs/tech-stack.md deleted file mode 100644 index f03291fb..00000000 --- a/docs/tech-stack.md +++ /dev/null @@ -1,1007 +0,0 @@ -# One-KVM ๆŠ€ๆœฏๆ ˆๆ–‡ๆกฃ - -## 1. ๆฆ‚่ฟฐ - -One-KVM ้‡‡็”จ็ŽฐไปฃๅŒ–็š„ Rust + Vue3 ๆŠ€ๆœฏๆ ˆ๏ผŒ่ฟฝๆฑ‚้ซ˜ๆ€ง่ƒฝใ€ไฝŽ่ต„ๆบๅ ็”จๅ’Œ็ฑปๅž‹ๅฎ‰ๅ…จใ€‚ๆœฌๆ–‡ๆกฃ่ฏฆ็ป†ไป‹็ป้กน็›ฎไฝฟ็”จ็š„ๆŠ€ๆœฏใ€ๅบ“ๅ’Œๅผ€ๅ‘่ง„่Œƒใ€‚ - ---- - -## 2. ๅŽ็ซฏๆŠ€ๆœฏๆ ˆ - -### 2.1 ๆ ธๅฟƒ่ฏญ่จ€ๅ’Œ่ฟ่กŒๆ—ถ - -| ๆŠ€ๆœฏ | ็‰ˆๆœฌ | ็”จ้€” | -|------|------|------| -| **Rust** | Edition 2021 | ไธป่ฆๅผ€ๅ‘่ฏญ่จ€ | -| **Tokio** | 1.x | ๅผ‚ๆญฅ่ฟ่กŒๆ—ถ | - -#### Rust ็‰นๆ€งไฝฟ็”จ - -```rust -// Edition 2021 ็‰นๆ€ง -- async/await ๅผ‚ๆญฅ็ผ–็จ‹ -- ๆจกๅผๅŒน้… (match, if let) -- ้”™่ฏฏๅค„็† (Result, ?) -- ๆ™บ่ƒฝๆŒ‡้’ˆ (Arc, Mutex, RwLock) -- trait ็ณป็ปŸ -- ็”Ÿๅ‘ฝๅ‘จๆœŸ -- ๆณ›ๅž‹ -``` - -### 2.2 Web ๆก†ๆžถ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **axum** | 0.7 | Web ๆก†ๆžถ | -| **axum-extra** | 0.9 | Cookieใ€TypedHeader ๆ”ฏๆŒ | -| **tower-http** | 0.5 | CORSใ€ๅŽ‹็ผฉใ€่ฟฝ่ธชไธญ้—ดไปถ | -| **axum-server** | 0.7 | TLS/HTTPS ๆœๅŠกๅ™จ | - -#### Axum ไฝฟ็”จๆจกๅผ - -```rust -// ่ทฏ็”ฑๅฎšไน‰ -Router::new() - .route("/api/stream/start", post(handlers::stream_start)) - .route("/api/stream/stop", post(handlers::stream_stop)) - .with_state(app_state) - -// ๅค„็†ๅ™จๅ‡ฝๆ•ฐ -async fn stream_start( - State(state): State>, - Json(payload): Json, -) -> Result, AppError> - -// ไธญ้—ดไปถ -.layer(CorsLayer::permissive()) -.layer(CompressionLayer::new()) -.layer(TraceLayer::new_for_http()) -``` - -### 2.3 ๆ•ฐๆฎๅบ“ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **SQLx** | 0.8 | ๅผ‚ๆญฅ SQL ๅทฅๅ…ทๅŒ… | -| **SQLite** | (bundled) | ๅตŒๅ…ฅๅผๆ•ฐๆฎๅบ“ | - -#### ๆ•ฐๆฎๅบ“่ฎพ่ฎก - -```sql --- ้…็ฝฎ่กจ (JSON blob ๅญ˜ๅ‚จ) -CREATE TABLE IF NOT EXISTS config ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - updated_at TEXT NOT NULL -); - --- ็”จๆˆท่กจ -CREATE TABLE IF NOT EXISTS users ( - id TEXT PRIMARY KEY, - username TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, - role TEXT NOT NULL, - created_at TEXT NOT NULL -); -``` - -### 2.4 ๅบๅˆ—ๅŒ– - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **serde** | 1.x | ๅบๅˆ—ๅŒ–ๆก†ๆžถ | -| **serde_json** | 1.x | JSON ๅบๅˆ—ๅŒ– | -| **prost** | 0.13 | Protobuf ๅบๅˆ—ๅŒ– (RustDesk) | - -### 2.5 ๆ—ฅๅฟ—ๅ’Œ่ฟฝ่ธช - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **tracing** | 0.1 | ็ป“ๆž„ๅŒ–ๆ—ฅๅฟ— | -| **tracing-subscriber** | 0.3 | ๆ—ฅๅฟ—่ฎข้˜…ๅ™จ | - -#### ๆ—ฅๅฟ—็บงๅˆซ - -```bash --v # WARN + INFO --vv # + DEBUG --vvv # + TRACE -``` - -### 2.6 ้”™่ฏฏๅค„็† - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **thiserror** | 1.x | ้”™่ฏฏ็ฑปๅž‹ๆดพ็”Ÿ | -| **anyhow** | 1.x | ้€š็”จ้”™่ฏฏๅค„็† | - -#### ้”™่ฏฏๅค„็†ๆจกๅผ - -```rust -#[derive(Debug, thiserror::Error)] -pub enum AppError { - #[error("Authentication failed")] - AuthError, - - #[error("Resource not found: {0}")] - NotFound(String), - - #[error("Internal error: {0}")] - Internal(#[from] anyhow::Error), -} - -impl IntoResponse for AppError { - fn into_response(self) -> Response { - let (status, message) = match self { - AppError::AuthError => (StatusCode::UNAUTHORIZED, self.to_string()), - AppError::NotFound(_) => (StatusCode::NOT_FOUND, self.to_string()), - AppError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal error".into()), - }; - (status, Json(json!({ "error": message }))).into_response() - } -} -``` - -### 2.7 ่ฎค่ฏๅ’Œๅฎ‰ๅ…จ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **argon2** | 0.5 | ๅฏ†็ ๅ“ˆๅธŒ | -| **rand** | 0.8 | ้šๆœบๆ•ฐ็”Ÿๆˆ | -| **rustls** | 0.23 | TLS ๅฎž็Žฐ | -| **rcgen** | 0.13 | ่ฏไนฆ็”Ÿๆˆ | -| **sodiumoxide** | 0.2 | NaCl ๅŠ ๅฏ† (RustDesk) | -| **sha2** | 0.10 | SHA-256 ๅ“ˆๅธŒ | - -#### ๅฏ†็ ๅ“ˆๅธŒ - -```rust -use argon2::{Argon2, PasswordHasher, PasswordVerifier}; - -// ๅ“ˆๅธŒๅฏ†็  -let salt = SaltString::generate(&mut OsRng); -let hash = Argon2::default() - .hash_password(password.as_bytes(), &salt)? - .to_string(); - -// ้ชŒ่ฏๅฏ†็  -Argon2::default() - .verify_password(password.as_bytes(), &parsed_hash)?; -``` - -### 2.8 ่ง†้ข‘ๅค„็† - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **v4l** | 0.14 | V4L2 ่ง†้ข‘้‡‡้›† | -| **turbojpeg** | 1.1 | JPEG ็ผ–็  (SIMD) | -| **hwcodec** | (vendored) | ็กฌไปถ่ง†้ข‘็ผ–็  | -| **libyuv** | (vendored) | YUV ๆ ผๅผ่ฝฌๆข | - -#### ่ง†้ข‘็ผ–็ ไผ˜ๅ…ˆ็บง - -``` -1. VAAPI (Intel/AMD GPU) -2. RKMPP (Rockchip) -3. V4L2 M2M (้€š็”จ็กฌไปถ) -4. Software (libx264/libvpx) -``` - -#### ๆ”ฏๆŒ็š„ๅƒ็ด ๆ ผๅผ - -```rust -pub enum PixelFormat { - // ๅŽ‹็ผฉๆ ผๅผ (ไผ˜ๅ…ˆ) - Mjpeg, // Motion JPEG - Jpeg, // Static JPEG - - // YUV 4:2:2 ๆ‰“ๅŒ…ๆ ผๅผ - Yuyv, // YUYV (ๆœ€ๅธธ่ง) - Yvyu, // YVYU - Uyvy, // UYVY - - // YUV ๅŠๅนณ้ขๆ ผๅผ - Nv12, // NV12 (ๅธธ่ง) - Nv16, // NV16 - Nv24, // NV24 - - // YUV ๅนณ้ขๆ ผๅผ - Yuv420, // I420/YU12 - Yvu420, // YV12 - - // RGB ๆ ผๅผ - Rgb565, // RGB565 - Rgb24, // RGB24 - Bgr24, // BGR24 - - // ็ฐๅบฆ - Grey, // 8-bit grayscale -} -``` - -### 2.9 ้Ÿณ้ข‘ๅค„็† - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **alsa** | 0.9 | ALSA ้Ÿณ้ข‘้‡‡้›† | -| **audiopus** | 0.2 | Opus ็ผ–็  | - -#### ้Ÿณ้ข‘้…็ฝฎ - -```rust -// ้‡‡ๆ ทๅ‚ๆ•ฐ -const SAMPLE_RATE: u32 = 48000; -const CHANNELS: u16 = 2; -const FRAME_SIZE: usize = 960; // 20ms at 48kHz - -// ่ดจ้‡้…็ฝฎ -pub enum AudioQuality { - VeryLow, // 24 kbps - Low, // 48 kbps - Medium, // 64 kbps - High, // 96 kbps -} -``` - -### 2.10 WebRTC - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **webrtc** | 0.14 | WebRTC ๅฎž็Žฐ | -| **rtp** | 0.14 | RTP ๅ่ฎฎ | - -#### WebRTC ้…็ฝฎ - -```rust -// ICE ๆœๅŠกๅ™จ้…็ฝฎ -pub struct IceServerConfig { - pub stun_servers: Vec, // STUN ๆœๅŠกๅ™จ - pub turn_servers: Vec, // TURN ๆœๅŠกๅ™จ -} - -// ้ป˜่ฎค STUN -"stun:stun.l.google.com:19302" -``` - -### 2.11 ็กฌไปถไบคไบ’ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **nix** | 0.29 | Unix ็ณป็ปŸ่ฐƒ็”จ | -| **gpio-cdev** | 0.6 | GPIO ๆŽงๅˆถ | -| **serialport** | 4.x | ไธฒๅฃ้€šไฟก | -| **libc** | 0.2 | C ๅบ“็ป‘ๅฎš | - -#### GPIO ๆ“ไฝœ - -```rust -// ไฝฟ็”จ gpio-cdev -let chip = Chip::new("/dev/gpiochip0")?; -let line = chip.get_line(pin)?; -let handle = line.request(LineRequestFlags::OUTPUT, 0, "one-kvm")?; -handle.set_value(1)?; // ่ฎพ็ฝฎ้ซ˜็”ตๅนณ -``` - -#### ไธฒๅฃ้€šไฟก (CH9329) - -```rust -// ๆ‰“ๅผ€ไธฒๅฃ -let port = serialport::new(device, baud_rate) - .timeout(Duration::from_millis(100)) - .open()?; - -// ๅ‘้€ HID ๆŠฅๅ‘Š -port.write(&hid_report)?; -``` - -### 2.12 ๅนถๅ‘ๅŒๆญฅ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **parking_lot** | 0.12 | ้ซ˜ๆ€ง่ƒฝ้” | -| **arc-swap** | 1.7 | ๅŽŸๅญๅผ•็”จไบคๆข | -| **tokio** | 1.x | ๅผ‚ๆญฅ้€š้“ | - -#### ๅŒๆญฅๆจกๅผ - -```rust -// ๅ…ฑไบซ็Šถๆ€ -Arc> // ่ฏปๅคšๅ†™ๅฐ‘ -Arc> // ไบ’ๆ–ฅ่ฎฟ้—ฎ -Arc // ๅŽŸๅญๆ“ไฝœ - -// ้€š้“ -tokio::sync::broadcast // ๅคš็”Ÿไบง่€…ๅคšๆถˆ่ดน่€… -tokio::sync::mpsc // ๅคš็”Ÿไบง่€…ๅ•ๆถˆ่ดน่€… -tokio::sync::oneshot // ไธ€ๆฌกๆ€ง้€š็Ÿฅ -``` - -### 2.13 ็ฝ‘็ปœๅ’Œ HTTP - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **reqwest** | 0.12 | HTTP ๅฎขๆˆท็ซฏ | -| **tokio-tungstenite** | 0.24 | WebSocket ๅฎขๆˆท็ซฏ | -| **urlencoding** | 2.x | URL ็ผ–็  | - -### 2.14 ๅทฅๅ…ทๅบ“ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **uuid** | 1.x | UUID ็”Ÿๆˆ | -| **chrono** | 0.4 | ๆ—ถ้—ดๅค„็† | -| **base64** | 0.22 | Base64 ็ผ–็  | -| **bytes** | 1.x | ๅญ—่Š‚็ผ“ๅ†ฒๅŒบ | -| **bytemuck** | 1.14 | ้›ถๆ‹ท่ด็ฑปๅž‹่ฝฌๆข | -| **xxhash-rust** | 0.8 | ๅฟซ้€Ÿๅ“ˆๅธŒ | -| **futures** | 0.3 | Future ๅทฅๅ…ท | -| **async-trait** | 0.1 | ๅผ‚ๆญฅ trait | - -### 2.15 ้™ๆ€่ต„ๆบๅตŒๅ…ฅ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **rust-embed** | 8.x | ่ต„ๆบๅตŒๅ…ฅ | -| **mime_guess** | 2.x | MIME ็ฑปๅž‹ๆŽจๆ–ญ | - -#### ่ต„ๆบๅตŒๅ…ฅๆจกๅผ - -```rust -#[derive(RustEmbed)] -#[folder = "web/dist"] -#[include = "*.html"] -#[include = "*.js"] -#[include = "*.css"] -#[include = "assets/*"] -struct Assets; - -// Debug: ไปŽๆ–‡ไปถ็ณป็ปŸ่ฏปๅ– -// Release: ๅตŒๅ…ฅไบŒ่ฟ›ๅˆถ (gzip ๅŽ‹็ผฉ) -``` - -### 2.16 CLI - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **clap** | 4.x | ๅ‘ฝไปค่กŒ่งฃๆž | - -#### CLI ๅ‚ๆ•ฐ - -```rust -#[derive(Parser)] -struct Args { - #[arg(short, long, default_value = "0.0.0.0")] - address: String, - - #[arg(short, long, default_value = "8080")] - port: u16, - - #[arg(short, long)] - data_dir: Option, - - #[arg(long)] - enable_https: bool, - - #[arg(short, long, action = clap::ArgAction::Count)] - verbose: u8, -} -``` - -### 2.17 ็ฑปๅž‹็”Ÿๆˆ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **typeshare** | 1.0 | TypeScript ็ฑปๅž‹็”Ÿๆˆ | - -#### ็ฑปๅž‹ๅ…ฑไบซ - -```rust -#[derive(Serialize, Deserialize)] -#[typeshare] -pub struct VideoConfig { - pub device: Option, - pub width: u32, - pub height: u32, - pub fps: u32, -} - -// ็”Ÿๆˆ TypeScript: -// export interface VideoConfig { -// device?: string; -// width: number; -// height: number; -// fps: number; -// } -``` - ---- - -## 3. ๅ‰็ซฏๆŠ€ๆœฏๆ ˆ - -### 3.1 ๆ ธๅฟƒๆก†ๆžถ - -| ๆŠ€ๆœฏ | ็‰ˆๆœฌ | ็”จ้€” | -|------|------|------| -| **Vue 3** | 3.5.x | UI ๆก†ๆžถ | -| **Vue Router** | 4.6.x | ่ทฏ็”ฑ | -| **Pinia** | 3.0.x | ็Šถๆ€็ฎก็† | -| **TypeScript** | 5.9.x | ็ฑปๅž‹็ณป็ปŸ | - -#### Vue 3 ็ป„ๅˆๅผ API - -```vue - -``` - -### 3.2 UI ็ป„ไปถๅบ“ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **Radix Vue** | 1.9.x | ๆ— ๅคด UI ็ป„ไปถ | -| **Reka UI** | 2.6.x | UI ็ป„ไปถ | -| **shadcn-vue** | - | ็ป„ไปถๆ ทๅผ | -| **Lucide Vue** | 0.556.x | ๅ›พๆ ‡ๅบ“ | - -### 3.3 ๆ ทๅผ - -| ๆŠ€ๆœฏ | ็‰ˆๆœฌ | ็”จ้€” | -|------|------|------| -| **Tailwind CSS** | 4.1.x | ๅŽŸๅญๅŒ– CSS | -| **tailwind-merge** | 3.4.x | ็ฑปๅๅˆๅนถ | -| **class-variance-authority** | 0.7.x | ๅ˜ไฝ“็ฎก็† | -| **tw-animate-css** | 1.4.x | ๅŠจ็”ป | - -#### Tailwind ้…็ฝฎ - -```javascript -// tailwind.config.js -export default { - darkMode: 'class', - content: ['./src/**/*.{vue,ts}'], - theme: { - extend: { - colors: { - border: 'hsl(var(--border))', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - }, - }, - }, -} -``` - -### 3.4 ๆž„ๅปบๅทฅๅ…ท - -| ๅทฅๅ…ท | ็‰ˆๆœฌ | ็”จ้€” | -|------|------|------| -| **Vite** | 7.2.x | ๆž„ๅปบๅทฅๅ…ท | -| **vue-tsc** | 3.1.x | ็ฑปๅž‹ๆฃ€ๆŸฅ | -| **PostCSS** | 8.5.x | CSS ๅค„็† | -| **Autoprefixer** | 10.4.x | CSS ๅ‰็ผ€ | - -#### Vite ้…็ฝฎ - -```typescript -// vite.config.ts -export default defineConfig({ - plugins: [vue()], - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } - }, - build: { - outDir: 'dist', - sourcemap: false, - } -}) -``` - -### 3.5 ๅŠŸ่ƒฝๅบ“ - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **@vueuse/core** | 14.1.x | Vue ็ป„ๅˆๅผๅทฅๅ…ท | -| **simple-keyboard** | 3.8.x | ่™šๆ‹Ÿ้”ฎ็›˜ | -| **opus-decoder** | 0.7.x | Opus ้Ÿณ้ข‘่งฃ็  | -| **uplot** | 1.6.x | ๅฎžๆ—ถๅ›พ่กจ | -| **vue-sonner** | 2.0.x | Toast ้€š็Ÿฅ | - -### 3.6 ๅ›ฝ้™…ๅŒ– - -| ๅบ“ | ็‰ˆๆœฌ | ็”จ้€” | -|----|------|------| -| **vue-i18n** | 9.14.x | ๅ›ฝ้™…ๅŒ– | - -#### ๅคš่ฏญ่จ€ๆ”ฏๆŒ - -```typescript -// i18n/en-US.ts -export default { - common: { - start: 'Start', - stop: 'Stop', - settings: 'Settings', - }, - video: { - noSignal: 'No Signal', - streaming: 'Streaming', - }, -} - -// i18n/zh-CN.ts -export default { - common: { - start: 'ๅฏๅŠจ', - stop: 'ๅœๆญข', - settings: '่ฎพ็ฝฎ', - }, - video: { - noSignal: 'ๆ— ไฟกๅท', - streaming: 'ๆญฃๅœจๆŽจๆต', - }, -} -``` - ---- - -## 4. ๅค–้ƒจไพ่ต–ๅบ“ - -### 4.1 hwcodec (ๆฅ่‡ช RustDesk) - -็กฌไปถ่ง†้ข‘็ผ–็ ๅบ“๏ผŒๆ”ฏๆŒๅคš็ง็กฌไปถๅŠ ้€Ÿ๏ผš - -| ๅŽ็ซฏ | ๅนณๅฐ | ็ผ–็ ๆ ผๅผ | -|------|------|---------| -| **VAAPI** | Intel/AMD Linux | H264, H265, VP8, VP9 | -| **RKMPP** | Rockchip | H264, H265 | -| **V4L2 M2M** | ้€š็”จ Linux | H264 | -| **Software** | ้€š็”จ | H264 (x264) | - -### 4.2 libyuv (ๆฅ่‡ช Google) - -้ซ˜ๆ€ง่ƒฝ YUV/RGB ่ฝฌๆขๅบ“๏ผš - -```rust -// ๆ”ฏๆŒ็š„่ฝฌๆข -MJPEG โ†’ YUV420 -YUYV โ†’ YUV420 -NV12 โ†’ YUV420 -RGB24 โ†’ YUV420 - -// SIMD ๅŠ ้€Ÿ -- SSE2/SSE4.1/AVX2 (x86) -- NEON (ARM) -``` - -### 4.3 ventoy-img-rs - -Ventoy ๅฏๅŠจ็›˜ๆ”ฏๆŒๅบ“๏ผš - -- ๅˆ›ๅปบๅฏๅฏๅŠจ USB ้•œๅƒ -- ๆ”ฏๆŒๅคš ISO ๆ–‡ไปถ -- exFAT ๆ–‡ไปถ็ณป็ปŸ - ---- - -## 5. ๅ่ฎฎๅ’Œ่ง„่Œƒ - -### 5.1 RustDesk ๅ่ฎฎ - -ไฝฟ็”จ Protobuf ๅฎšไน‰็š„ๆถˆๆฏๆ ผๅผ๏ผš - -```protobuf -// protos/message.proto -message VideoFrame { - bytes data = 1; - int32 width = 2; - int32 height = 3; - VideoCodec codec = 4; -} - -message MouseEvent { - int32 x = 1; - int32 y = 2; - MouseButton button = 3; -} -``` - -### 5.2 WebRTC ่ง„่Œƒ - -้ตๅพชๆ ‡ๅ‡† WebRTC ๅ่ฎฎ๏ผš - -- **ICE** (RFC 8445) - ่ฟžๆŽฅๅปบ็ซ‹ -- **DTLS** (RFC 6347) - ๅฎ‰ๅ…จไผ ่พ“ -- **SRTP** (RFC 3711) - ๅช’ไฝ“ๅŠ ๅฏ† -- **RTP** (RFC 3550) - ๅช’ไฝ“ไผ ่พ“ - -### 5.3 HID ๆŠฅๅ‘Šๆ่ฟฐ็ฌฆ - -USB HID ๆŠฅๅ‘Šๆ ผๅผ๏ผš - -```rust -// ้”ฎ็›˜ๆŠฅๅ‘Š (8 ๅญ—่Š‚) -struct KeyboardReport { - modifiers: u8, // Ctrl, Shift, Alt, GUI - reserved: u8, - keys: [u8; 6], // ๆœ€ๅคš 6 ไธชๆŒ‰้”ฎ -} - -// ้ผ ๆ ‡ๆŠฅๅ‘Š (็›ธๅฏนๆจกๅผ, 4 ๅญ—่Š‚) -struct MouseRelativeReport { - buttons: u8, - x: i8, - y: i8, - wheel: i8, -} - -// ้ผ ๆ ‡ๆŠฅๅ‘Š (็ปๅฏนๆจกๅผ, 6 ๅญ—่Š‚) -struct MouseAbsoluteReport { - buttons: u8, - x: u16, // 0-32767 - y: u16, // 0-32767 - wheel: i8, -} -``` - -### 5.4 V4L2 ๆŽฅๅฃ - -Video4Linux2 ้‡‡้›†ๆŽฅๅฃ๏ผš - -```rust -// ่ฎพๅค‡่ƒฝๅŠ›ๆŸฅ่ฏข -VIDIOC_QUERYCAP - -// ๆ ผๅผ่ฎพ็ฝฎ -VIDIOC_S_FMT -VIDIOC_G_FMT - -// ็ผ“ๅ†ฒๅŒบ็ฎก็† -VIDIOC_REQBUFS -VIDIOC_QUERYBUF -VIDIOC_QBUF -VIDIOC_DQBUF - -// ๆตๆŽงๅˆถ -VIDIOC_STREAMON -VIDIOC_STREAMOFF -``` - ---- - -## 6. ๅผ€ๅ‘่ง„่Œƒ - -### 6.1 Rust ไปฃ็ ่ง„่Œƒ - -#### ๅ‘ฝๅ็บฆๅฎš - -```rust -// ็ป“ๆž„ไฝ“: PascalCase -pub struct VideoConfig { } - -// ๅ‡ฝๆ•ฐ/ๆ–นๆณ•: snake_case -fn start_streaming() { } - -// ๅธธ้‡: SCREAMING_SNAKE_CASE -const MAX_FRAME_SIZE: usize = 1920 * 1080 * 4; - -// ๆจกๅ—: snake_case -mod video_capture; - -// trait: PascalCase -trait Encoder { } -``` - -#### ้”™่ฏฏๅค„็† - -```rust -// ไฝฟ็”จ Result ่ฟ”ๅ›žๅฏ่ƒฝๅคฑ่ดฅ็š„ๆ“ไฝœ -fn open_device() -> Result; - -// ไฝฟ็”จ ? ไผ ๆ’ญ้”™่ฏฏ -let device = open_device()?; - -// ่‡ชๅฎšไน‰้”™่ฏฏ็ฑปๅž‹ไฝฟ็”จ thiserror -#[derive(Debug, thiserror::Error)] -pub enum DeviceError { - #[error("Device not found: {0}")] - NotFound(String), -} -``` - -#### ๅผ‚ๆญฅไปฃ็  - -```rust -// ไฝฟ็”จ async/await -async fn fetch_frame(&self) -> Result { - let frame = self.capture.read_frame().await?; - Ok(frame) -} - -// ไฝฟ็”จ tokio::spawn ๅฏๅŠจๅŽๅฐไปปๅŠก -tokio::spawn(async move { - loop { - // ๅŽๅฐๅทฅไฝœ - } -}); -``` - -### 6.2 TypeScript ไปฃ็ ่ง„่Œƒ - -#### ็ฑปๅž‹ๅฎšไน‰ - -```typescript -// ไฝฟ็”จ interface ๅฎšไน‰ๆ•ฐๆฎ็ป“ๆž„ -interface VideoConfig { - device?: string - width: number - height: number - fps: number -} - -// ไฝฟ็”จ type ๅฎšไน‰่”ๅˆ็ฑปๅž‹ -type StreamState = 'idle' | 'starting' | 'streaming' | 'stopping' -``` - -#### ็ป„ๅˆๅผ API - -```typescript -// ไฝฟ็”จ -``` - -### 6.3 Git ๆไบค่ง„่Œƒ - -``` -(): - - - -