Skip to content

Commit d82cd4d

Browse files
committed
feat: tuic proto
1 parent 83ce3c6 commit d82cd4d

10 files changed

Lines changed: 753 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 339 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
# wind
1+
# wind
2+

crates/wind-tuic/Cargo.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,17 @@ edition.workspace = true
66
description.workspace = true
77
license = "MIT OR Apache-2.0"
88

9+
[features]
10+
default = ["server", "client"]
11+
server = []
12+
client = []
13+
914
[dependencies]
10-
tokio-util = { version = "*", features = ["codec"]}
15+
tokio-util = { version = "*", features = ["codec"] }
16+
bytes = "*"
17+
uuid = "*"
18+
hex = { package = "const-hex", version = "1" }
19+
20+
# Patterns
21+
num_enum = "0.7"
22+
snafu = "0.8"

crates/wind-tuic/src/lib.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1-
fn main() {
2-
println!("Hello, world!");
1+
pub mod proto;
2+
3+
use std::{backtrace::Backtrace, str::Utf8Error};
4+
5+
use snafu::{IntoError as _, prelude::*};
6+
7+
#[derive(Debug, Snafu)]
8+
pub enum Error {
9+
VersionDismatch {
10+
expect: u8,
11+
current: u8,
12+
backtrace: Backtrace,
13+
},
14+
UnknownCommandType {
15+
value: u8,
16+
backtrace: Backtrace,
17+
},
18+
UnknownAddressType {
19+
value: u8,
20+
backtrace: Backtrace,
21+
},
22+
FailParseDomain {
23+
// HEX
24+
raw: String,
25+
source: Utf8Error,
26+
backtrace: Backtrace,
27+
},
28+
DomainTooLong {
29+
domain: String,
30+
backtrace: Backtrace,
31+
},
32+
Io {
33+
// #[snafu(backtrace)]
34+
source: std::io::Error,
35+
backtrace: Backtrace,
36+
},
37+
}
38+
39+
impl From<std::io::Error> for Error {
40+
fn from(source: std::io::Error) -> Self {
41+
IoSnafu.into_error(source)
42+
}
343
}

crates/wind-tuic/src/proto/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod v5;
2+
3+
pub use v5::*;
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
use std::net::{Ipv4Addr, Ipv6Addr};
2+
3+
use bytes::{Buf, BufMut};
4+
use num_enum::{FromPrimitive, IntoPrimitive};
5+
use snafu::{ResultExt, ensure};
6+
use tokio_util::codec::{Decoder, Encoder};
7+
8+
use crate::{DomainTooLongSnafu, FailParseDomainSnafu, UnknownAddressTypeSnafu};
9+
10+
#[derive(Debug, Clone, Copy)]
11+
pub struct AddressCodec;
12+
13+
#[derive(Debug, Clone, PartialEq)]
14+
pub enum Address {
15+
None,
16+
FQDN(String, u16),
17+
IPv4(Ipv4Addr, u16),
18+
IPv6(Ipv6Addr, u16),
19+
}
20+
21+
#[derive(IntoPrimitive, FromPrimitive, Copy, Clone, Debug, PartialEq)]
22+
#[repr(u8)]
23+
pub enum AddressType {
24+
None = u8::MAX,
25+
FQDN = 0,
26+
IPv4 = 1,
27+
IPv6 = 2,
28+
#[num_enum(catch_all)]
29+
Other(u8),
30+
}
31+
// https://github.com/zephry-works/wind/blob/main/crates/wind-tuic/SPEC.md#5-address-encoding
32+
#[cfg(feature = "server")]
33+
impl Decoder for AddressCodec {
34+
type Error = crate::Error;
35+
type Item = Address;
36+
37+
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
38+
if src.len() < 1 {
39+
return Ok(None);
40+
}
41+
let addr_type = AddressType::from(src.get_u8());
42+
43+
ensure!(!matches!(addr_type, AddressType::Other(..)), UnknownAddressTypeSnafu { value: u8::from(addr_type) });
44+
match addr_type {
45+
AddressType::None => {
46+
Ok(Some(Address::None))
47+
}
48+
AddressType::IPv4 => {
49+
if src.len() < 4 + 2 {
50+
return Ok(None);
51+
}
52+
let mut octets = [0; 4];
53+
src.copy_to_slice(&mut octets);
54+
let ip = Ipv4Addr::from(octets);
55+
let port = src.get_u16();
56+
Ok(Some(Address::IPv4(ip, port)))
57+
}
58+
AddressType::IPv6 => {
59+
if src.len() < 16 + 2 {
60+
return Ok(None);
61+
}
62+
let mut octets = [0; 16];
63+
src.copy_to_slice(&mut octets);
64+
let ip = Ipv6Addr::from(octets);
65+
let port = src.get_u16();
66+
Ok(Some(Address::IPv6(ip, port)))
67+
}
68+
AddressType::FQDN => {
69+
if src.len() < 1 {
70+
return Ok(None);
71+
}
72+
let domain_len = src.get_u8() as usize;
73+
if src.len() < domain_len + 2 {
74+
return Ok(None);
75+
}
76+
77+
let domain = &src[..domain_len];
78+
let domain = str::from_utf8(domain)
79+
.context(FailParseDomainSnafu {
80+
raw: hex::encode(domain),
81+
})?
82+
.to_string();
83+
let port = src.get_u16();
84+
Ok(Some(Address::FQDN(domain, port)))
85+
}
86+
_ => unreachable!(),
87+
}
88+
}
89+
}
90+
91+
#[cfg(feature = "client")]
92+
impl Encoder<Address> for AddressCodec {
93+
type Error = crate::Error;
94+
95+
fn encode(&mut self, item: Address, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> {
96+
match item {
97+
Address::None => {
98+
dst.reserve(1);
99+
dst.put_u8(AddressType::None.into());
100+
}
101+
Address::IPv4(ip, port) => {
102+
dst.reserve(1 + 4 + 2);
103+
dst.put_slice(&ip.octets());
104+
dst.put_u16(port);
105+
}
106+
Address::IPv6(ip, port) => {
107+
dst.reserve(1 + 16 + 2);
108+
dst.put_slice(&ip.octets());
109+
dst.put_u16(port);
110+
}
111+
Address::FQDN(domain, port) => {
112+
if domain.len() > u8::MAX as usize {
113+
return DomainTooLongSnafu { domain }.fail();
114+
}
115+
dst.reserve(1 + domain.len() + 2);
116+
dst.put_u8(domain.len() as u8);
117+
dst.put_slice(domain.as_bytes());
118+
dst.put_u16(port);
119+
}
120+
}
121+
Ok(())
122+
}
123+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use bytes::{Buf, BufMut as _};
2+
use tokio_util::codec::{Decoder, Encoder};
3+
use uuid::Uuid;
4+
5+
use super::CommandType;
6+
use crate::UnknownCommandTypeSnafu;
7+
8+
#[derive(Debug, Clone, Copy)]
9+
pub struct CommandCodec(CommandType);
10+
11+
#[derive(Debug, Clone, PartialEq)]
12+
pub enum Command {
13+
Authenticate {
14+
uuid: uuid::Uuid,
15+
token: [u8; 32],
16+
},
17+
Connect,
18+
Packet {
19+
assos_id: u16,
20+
pkt_id: u16,
21+
frag_total: u8,
22+
frag_id: u8,
23+
size: u16,
24+
},
25+
Dissociate {
26+
assos_id: u16,
27+
},
28+
Heartbeat,
29+
}
30+
31+
// https://github.com/zephry-works/wind/blob/main/crates/wind-tuic/SPEC.md#3-command-specifications
32+
#[cfg(feature = "server")]
33+
impl Decoder for CommandCodec {
34+
type Error = crate::Error;
35+
type Item = Command;
36+
37+
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
38+
match self.0 {
39+
CommandType::Authenticate => {
40+
if src.len() < 16 + 32 {
41+
return Ok(None);
42+
}
43+
let mut uuid = [0; 16];
44+
src.copy_to_slice(&mut uuid);
45+
let uuid = Uuid::from_bytes(uuid);
46+
let mut token = [0; 32];
47+
src.copy_to_slice(&mut token);
48+
Ok(Some(Command::Authenticate { uuid, token }))
49+
}
50+
CommandType::Connect => Ok(Some(Command::Connect)),
51+
CommandType::Packet => {
52+
if src.len() < 8 {
53+
return Ok(None);
54+
}
55+
let assos_id = src.get_u16();
56+
let pkt_id = src.get_u16();
57+
let frag_total = src.get_u8();
58+
let frag_id = src.get_u8();
59+
let size = src.get_u16();
60+
Ok(Some(Command::Packet {
61+
assos_id,
62+
pkt_id,
63+
frag_total,
64+
frag_id,
65+
size,
66+
}))
67+
}
68+
CommandType::Dissociate => {
69+
if src.len() < 2 {
70+
return Ok(None);
71+
}
72+
let assos_id = src.get_u16();
73+
Ok(Some(Command::Dissociate { assos_id }))
74+
}
75+
CommandType::Heartbeat => Ok(Some(Command::Heartbeat)),
76+
CommandType::Other(value) => UnknownCommandTypeSnafu { value }.fail(),
77+
}
78+
}
79+
}
80+
81+
#[cfg(feature = "client")]
82+
impl Encoder<Command> for CommandCodec {
83+
type Error = crate::Error;
84+
85+
fn encode(&mut self, item: Command, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> {
86+
match item {
87+
Command::Authenticate { uuid, token } => {
88+
dst.reserve(16 + 32);
89+
dst.put_slice(uuid.as_bytes());
90+
dst.put_slice(&token);
91+
},
92+
Command::Connect => {},
93+
Command::Packet { assos_id, pkt_id, frag_total, frag_id, size } => {
94+
dst.reserve(8);
95+
dst.put_u16(assos_id);
96+
dst.put_u16(pkt_id);
97+
dst.put_u8(frag_total);
98+
dst.put_u8(frag_id);
99+
dst.put_u16(size);
100+
},
101+
Command::Dissociate { assos_id } => {
102+
dst.reserve(2);
103+
dst.put_u16(assos_id);
104+
},
105+
Command::Heartbeat => {},
106+
}
107+
Ok(())
108+
}
109+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use bytes::{Buf, BufMut};
2+
use num_enum::{FromPrimitive, IntoPrimitive};
3+
use snafu::ensure;
4+
use tokio_util::codec::{Decoder, Encoder};
5+
6+
use super::VER;
7+
use crate::{UnknownCommandTypeSnafu, VersionDismatchSnafu};
8+
9+
#[derive(Debug, Clone, Copy)]
10+
pub struct HeaderCodec;
11+
12+
#[derive(Debug, Clone, PartialEq)]
13+
pub struct Header {
14+
pub version: u8,
15+
pub command: CommandType,
16+
}
17+
18+
#[derive(IntoPrimitive, FromPrimitive, Copy, Clone, Debug, PartialEq)]
19+
#[repr(u8)]
20+
pub enum CommandType {
21+
Authenticate = 0,
22+
Connect = 1,
23+
Packet = 2,
24+
Dissociate = 3,
25+
Heartbeat = 4,
26+
#[num_enum(catch_all)]
27+
Other(u8),
28+
}
29+
30+
impl Header {
31+
pub fn new(command: CommandType) -> Self {
32+
Self {
33+
version: VER,
34+
command,
35+
}
36+
}
37+
}
38+
39+
#[cfg(feature = "server")]
40+
impl Decoder for HeaderCodec {
41+
type Error = crate::Error;
42+
type Item = Header;
43+
44+
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result<Option<Self::Item>, Self::Error> {
45+
if src.len() < 2 {
46+
return Ok(None);
47+
}
48+
let ver = src.get_u8();
49+
ensure!(ver == VER, VersionDismatchSnafu { expect: VER, current: ver });
50+
51+
let cmd = CommandType::from(src.get_u8());
52+
53+
ensure!(!matches!(cmd, CommandType::Other(..)), UnknownCommandTypeSnafu { value: u8::from(cmd) });
54+
55+
Ok(Some(Header::new(cmd)))
56+
}
57+
}
58+
59+
#[cfg(feature = "client")]
60+
impl Encoder<Header> for HeaderCodec {
61+
type Error = crate::Error;
62+
63+
fn encode(&mut self, item: Header, dst: &mut bytes::BytesMut) -> Result<(), Self::Error> {
64+
dst.reserve(2);
65+
dst.put_u8(item.version);
66+
dst.put_u8(item.command.into());
67+
Ok(())
68+
}
69+
}

0 commit comments

Comments
 (0)