# ภาพรวมทางเทคนิค

> HTML: https://remote.ivjn.us/docs/th/technical/

เอกสารนี้อธิบายกลไกหลักในโค้ดปัจจุบัน ช่องข้อความ รูปแบบเฟรม และลำดับการจับมือที่สมบูรณ์อยู่ใน [Wire protocol](/docs/th/websocket-protocol.md)

## ผู้ปฏิบัติงานและอุปกรณ์ต่างเชื่อมต่อขาออกกับ exchange

อุปกรณ์ภายในบ้านและสำนักงานสาขามักจะอยู่หลัง NAT ดังนั้น exchange จึงไม่สามารถโทรเข้าไปได้ ทั้งสองฝ่ายเริ่มต้นการเชื่อมต่อ WebSocket กับ exchange:

- อุปกรณ์เชื่อมต่อกับ `wss://<主机>/ws/device` (`ws://` สำหรับการดีบักข้อความธรรมดา) โดยลงทะเบียนกับโทเค็นอุปกรณ์ในเฟรมแรก
- ตัวดำเนินการเชื่อมต่อกับ `wss://<主机>/ws/operator` โดยใช้โทเค็นตัวดำเนินการสำหรับการจับมือ

exchange จัดการการรับรองความถูกต้อง การกำหนดเส้นทาง และเส้นทางการตรวจสอบ `--listen` หรือ `REMOTE_LISTEN` กำหนดที่อยู่การฟัง พร็อกซีย้อนกลับที่อยู่ด้านหน้าจะยุติ TLS

## ช่องว่าง ID เซสชันสองช่อง

ผู้ปฏิบัติงานและอุปกรณ์รักษา ID เซสชันที่เป็นอิสระ ผู้ดำเนินการสามารถรวม ID ของตนเองในคำขอโดยไม่ต้องรอการจัดสรรเซิร์ฟเวอร์ก่อนจึงจะส่งเฟรมตามมา ID ฝั่งอุปกรณ์จะได้รับการจัดสรรแยกกันสำหรับการเชื่อมต่ออุปกรณ์แต่ละเครื่องในปัจจุบัน โดยเพิ่มขึ้นจาก 1 แม้ว่าผู้ปฏิบัติงานสองคนจะใช้ ID ภายในเครื่อง 1 ทั้งคู่ก็จะได้รับ ID ที่แตกต่างกันบนอุปกรณ์

ID 0 ถูกสงวนไว้สำหรับข้อความระดับการเชื่อมต่อ การลงทะเบียนล้มเหลวโดยไม่ต้องใส่เส้นทางถ้า ID ฝั่งตัวดำเนินการเป็น 0 หรือใช้อยู่แล้วในการเชื่อมต่อของตัวดำเนินการนั้น

ตารางการแปลอยู่ใน `crates/exchange/src/hub/session_table.rs` `hub.rs` แนบคำแปลเข้ากับการเชื่อมต่อแต่ละรายการ: ID อุปกรณ์ → ID ผู้ปฏิบัติงานที่ฝั่งอุปกรณ์ และ ID ผู้ปฏิบัติงาน → (การเชื่อมต่ออุปกรณ์ ID อุปกรณ์) ที่ฝั่งผู้ปฏิบัติงาน

## การส่งต่อจะเขียนใหม่เฉพาะรหัสเซสชันเท่านั้น

ไบต์ PTY และคำสั่ง stdout/stderr ใช้เฟรมไบนารี ส่วนหัวเก้าไบต์ประกอบด้วยประเภทสตรีมหนึ่งไบต์ `session_id` สี่ไบต์ และความยาวเพย์โหลดสี่ไบต์ ตามด้วยเพย์โหลดดิบ คำจำกัดความอยู่ใน `crates/proto/src/frame.rs`

exchange แทนที่เฉพาะ `session_id` เมื่อส่งต่อ:

- ตัวดำเนินการไปยังอุปกรณ์: `crates/exchange/src/ws/operator.rs` ใช้ `DataFrame::with_session_id` เพื่อแทนที่ ID ฝั่งอุปกรณ์ จากนั้นวางไบต์ที่เข้ารหัสไว้ในคิวการส่งอุปกรณ์
- อุปกรณ์ต่อผู้ปฏิบัติงาน: `handle_data` ใน `crates/exchange/src/ws/device.rs` จะแทนที่ ID ฝั่งอุปกรณ์ด้วย ID ดั้งเดิมของผู้ปฏิบัติงาน ความคิดเห็นแหล่งที่มาอธิบายการเขียนใหม่นี้

เพย์โหลดผ่านไปไม่เปลี่ยนแปลง การกำหนดเส้นทางไม่ได้ตรวจสอบการกดแป้น PTY หรือเอาต์พุตคำสั่ง เฟรมที่ไม่มีเซสชันที่ตรงกันจะถูกละทิ้ง ข้อความควบคุมคือกรอบข้อความ JSON การเปิดและปิดเซสชั่นจะแปล ID ลงในพื้นที่ ID ของผู้รับในขณะที่ยังคงรักษาสาขาธุรกิจไว้

## การเชื่อมต่ออุปกรณ์ใหม่จะแทนที่การเชื่อมต่อเก่า

เมื่ออุปกรณ์เชื่อมต่อใหม่ การเชื่อมต่อใหม่จะเข้าสู่ตารางออนไลน์และแทนที่อันเก่าซึ่งผู้โทรจะปิดลง การปฏิเสธการเชื่อมต่อใหม่เนื่องจากซ้ำกันนั้นไม่เหมาะสม: การเชื่อมต่อเก่าที่เปิดไว้ครึ่งหนึ่งอาจยังคงลงทะเบียนอยู่ ทำให้อุปกรณ์ต้องรอจนหมดเวลา

การเชื่อมต่ออุปกรณ์แต่ละรายการมี `epoch` (UUID) ซึ่งสร้างขึ้นใน `Hub::register_device` ยกเลิกการเชื่อมต่อการล้างข้อมูลโดยใช้ `Hub::unregister_device` โดยถอดอุปกรณ์ออกและทำเครื่องหมายว่าออฟไลน์เฉพาะเมื่อยุคที่ลงทะเบียนตรงกับยุคการล้างข้อมูล ถ้าการล้างข้อมูลการเชื่อมต่อเก่ามาถึงหลังจากการลงทะเบียนการเชื่อมต่อใหม่ ยุคที่แตกต่างกัน ฟังก์ชันจะส่งกลับรายการว่าง และการเชื่อมต่อใหม่ยังคงลงทะเบียนอยู่

## คิวการส่งมีขอบเขต คิวเต็มจะปิดการเชื่อมต่อ

WebSocket แต่ละตัวมีคิวความจุ 512 ซึ่งใช้งานโดย `channel` ใน `crates/exchange/src/hub/outbound.rs` การกำหนดเส้นทางจะเก็บการล็อกตารางการเชื่อมต่อ ดังนั้นการนำส่งจะใช้เฉพาะ `Sender::try_send` โดยไม่ต้องรอให้เพียร์อยู่ใต้การล็อก

คิวเต็มทำให้ `try_send` ส่งคืน `SendError::Lagging` และตั้งค่าสัญญาณปิด ข้อความควบคุม `Close` จะข้ามคิวและตั้งค่าสัญญาณเดียวกัน ดังนั้นแม้แต่คิวที่ถูกบล็อกก็สามารถขอปิดระบบได้ ทั้งอุปกรณ์และผู้ปฏิบัติงานรับลูปรอ `sender.closed()` ภายใน `tokio::select!` (`crates/exchange/src/ws/device.rs`, `crates/exchange/src/ws/operator.rs`) เมื่อได้รับสัญญาณแล้ว การส่งต่อจะสิ้นสุด จากนั้นยกเลิกการลงทะเบียนอุปกรณ์ตามยุคหรือล้างเซสชันของผู้ปฏิบัติงาน เพียร์ที่ทิ้งเครื่องรับไปแล้วจะสร้าง `SendError::Closed` ซึ่งแตกต่างจากผู้บริโภคที่ไม่สามารถตามทันได้

## โทเค็นประกอบด้วยคำนำหน้าและความลับ เก็บเฉพาะแฮช argon2 ของความลับเท่านั้น

โทเค็นข้อความธรรมดามีรูปแบบ `<prefix>.<secret>` `split_token` ใน `crates/exchange/src/auth.rs` แยกที่ `.` แรก โดยปฏิเสธคำนำหน้าว่างหรือข้อมูลลับว่ามีรูปแบบไม่ถูกต้อง

ในการออก คำนำหน้าคือไบต์สุ่มแปดไบต์ที่เข้ารหัสเป็นเลขฐานสิบหก และความลับคือ 32 ไบต์สุ่มที่เข้ารหัสเป็น base64url ฐานข้อมูลจัดเก็บเพียงสองคอลัมน์:

- `token_prefix`: ข้อความธรรมดาที่มีดัชนีเฉพาะ ใช้สำหรับการค้นหาหนึ่งครั้งระหว่างการตรวจสอบ
- `token_hash`: สตริง PHC argon2id ของความลับ สร้างโดย `hash_secret`

บัญชีอุปกรณ์และผู้ให้บริการใช้รูปแบบนี้ การยืนยันจะดึงข้อมูลแฮชตามคำนำหน้า จากนั้นเปรียบเทียบข้อมูลลับกับ `verify_secret` ข้อความธรรมดาจะถูกส่งกลับเมื่อมีการออกเท่านั้น และไม่สามารถกู้คืนจากฐานข้อมูลได้

argon2 ใช้หน่วยความจำ 8 MiB การวนซ้ำสองครั้ง และความขนาน 1 ซึ่งต่ำกว่าค่าเริ่มต้นของไลบรารี ข้อมูลลับคือค่าสุ่ม 256 บิต; การแฮชที่ช้าเพิ่มเติมไม่ได้เพิ่มการป้องกันแบบเดรัจฉานในทางปฏิบัติ แต่เพิ่มความหน่วงของการจับมือเมื่ออุปกรณ์เชื่อมต่ออีกครั้ง พารามิเตอร์เหล่านี้ใช้กับโทเค็นแบบสุ่มประเภทนี้เท่านั้น

## เอกสารที่เกี่ยวข้อง

- [การพัฒนา](/docs/th/development.md)
- [การใช้งาน](/docs/th/getting-started.md)
- [โปรโตคอลแบบใช้สาย](/docs/th/websocket-protocol.md)
- คู่มือการปรับใช้: พื้นที่เก็บข้อมูล `docs/deployment.md` (ประกอบด้วยที่อยู่เซิร์ฟเวอร์ ไม่ได้เผยแพร่บนเว็บไซต์นี้)
