├── .gitignore
├── tests
├── requirements-freeze.txt
└── test_integration.py
├── src
├── lib.rs
├── config.rs
├── main.rs
├── proxy.rs
├── aead.rs
├── hash.rs
├── utils.rs
└── vmess.rs
├── config
├── config.toml
└── config.json
├── Dockerfile
├── docker-compose.yaml
├── Cargo.toml
├── README.md
├── .github
└── workflows
│ └── ci.yaml
└── Cargo.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/__pycache__
3 |
--------------------------------------------------------------------------------
/tests/requirements-freeze.txt:
--------------------------------------------------------------------------------
1 | pytest==8.1.1
2 | requests==2.31.0
3 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | pub mod utils;
3 | pub mod aead;
4 | pub mod config;
5 | pub mod hash;
6 | pub mod proxy;
7 | pub mod vmess;
8 |
--------------------------------------------------------------------------------
/config/config.toml:
--------------------------------------------------------------------------------
1 | [inbound]
2 | address = "0.0.0.0:1090"
3 |
4 | [outbound]
5 | address = "127.0.0.1:1094"
6 | uuid = "96850032-1b92-46e9-a4f2-b99631456894"
7 | aead = true
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rust:1.76-alpine3.18
2 |
3 | COPY src ./src
4 | COPY Cargo.lock Cargo.toml .
5 |
6 | RUN apk update && apk add musl-dev
7 | RUN cargo build --release
8 |
9 | WORKDIR ./target/release
10 |
11 | CMD ["./vmessy", "--config", "/etc/config/config.toml"]
12 |
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | vmessy:
5 | build:
6 | context: .
7 | volumes:
8 | - ./config:/etc/config
9 | network_mode: "host"
10 | depends_on:
11 | - xray
12 |
13 | xray:
14 | image: teddysun/xray:1.7.5
15 | volumes:
16 | - ./config:/etc/xray
17 | network_mode: "host"
18 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "vmessy"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | tokio = { version = "1", features = ["full"] }
8 | clap = { version = "3.2", features = ["derive"] }
9 | serde = { version = "1.0", features = ["derive"] }
10 | uuid = { version = "1.7", features = ["serde"] }
11 | pretty_env_logger = "0.5"
12 | const-fnv1a-hash = "1.1"
13 | cfb-mode = "0.8"
14 | toml = "0.8"
15 | log = "0.4"
16 | anyhow = "1.0"
17 | hmac = "0.12"
18 | md-5 = "0.10"
19 | aes = "0.8"
20 | rand = "0.8"
21 | crc32fast = "1.4"
22 | sha2 = "0.10"
23 | aes-gcm = "0.10"
24 |
--------------------------------------------------------------------------------
/tests/test_integration.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import os
3 |
4 | VMESSY_HOST = os.environ.get("VMESSY_HOST", "localhost")
5 | VMESSY_PORT = 1090
6 |
7 | def test_basic_response():
8 | response = requests.get('http://google.com', allow_redirects=False, proxies={
9 | 'http': f'http://{VMESSY_HOST}:{VMESSY_PORT}',
10 | }).text
11 |
12 | expected = ('
\n'
13 | '301 Moved\n301 Moved
\nThe document has moved\n'
14 | 'here.\r\n\r\n')
15 | assert response == expected
16 |
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vmessy
2 | a messy implementation of vmess protocol for educational purposes.
3 |
4 | ## Usage
5 | ```bash
6 | $ xray --config ./config/config.json
7 | $ cargo run -- --config ./config/config.toml
8 | ```
9 |
10 | run xray:
11 | ```bash
12 | $ xray --config ./config/config.json
13 | ```
14 |
15 | if you set `aead = false` in [`config.toml`](./config/config.toml) you might need to disable aead:
16 | ```bash
17 | $ env "xray.vmess.aead.forced=false" xray --config ./config/config.json
18 | ```
19 |
20 | or simply using docker-compose:
21 | ```bash
22 | $ docker-compose up
23 | ```
24 |
25 | and proxy your requests:
26 | ```bash
27 | $ curl google.com --proxy http://127.0.0.1:1090
28 | ```
29 |
--------------------------------------------------------------------------------
/src/config.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Result};
2 | use serde::Deserialize;
3 | use uuid::Uuid;
4 |
5 | #[derive(Debug, Deserialize)]
6 | pub struct Config {
7 | pub inbound: Inbound,
8 | pub outbound: Outbound,
9 | }
10 |
11 | #[derive(Debug, Deserialize)]
12 | pub struct Inbound {
13 | pub address: String,
14 | }
15 |
16 | #[derive(Debug, Deserialize)]
17 | pub struct Outbound {
18 | pub address: String,
19 | pub uuid: Uuid,
20 | pub aead: bool,
21 | }
22 |
23 | impl Config {
24 | pub fn new(config: &str) -> Result {
25 | match toml::from_str(config) {
26 | Ok(c) => Ok(c),
27 | Err(e) => Err(anyhow!("could not parse config file {}", e)),
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use vmessy::{config::Config, proxy};
2 |
3 | use anyhow::{anyhow, Result};
4 | use clap::Parser;
5 |
6 | #[derive(Debug, Parser)]
7 | #[clap(author, version)]
8 | pub struct Args {
9 | #[clap(short, long)]
10 | pub config: String,
11 | }
12 |
13 | #[tokio::main]
14 | async fn main() -> Result<()> {
15 | pretty_env_logger::formatted_builder()
16 | .filter_level(log::LevelFilter::Info)
17 | .init();
18 |
19 | let args = Args::parse();
20 |
21 | let config = match std::fs::read_to_string(args.config) {
22 | Ok(c) => Config::new(&c),
23 | _ => panic!("could not find the file"),
24 | }?;
25 |
26 | match proxy::run(&config).await {
27 | Err(e) => Err(anyhow!("{}", e)),
28 | _ => Ok(()),
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - 'README.md'
9 | pull_request:
10 | branches:
11 | - master
12 | paths-ignore:
13 | - 'README.md'
14 |
15 | env:
16 | CARGO_TERM_COLOR: always
17 |
18 | jobs:
19 | unit-test:
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v3
24 | - name: Run unit tests
25 | run: cargo test --verbose
26 |
27 | integration-test:
28 | runs-on: ubuntu-latest
29 |
30 | steps:
31 | - uses: actions/checkout@v3
32 | - name: Install dependencies
33 | run: python -m pip install -r tests/requirements-freeze.txt
34 | - name: Run service
35 | run: docker-compose up -d
36 | - name: Run integration tests
37 | run: VMESSY_HOST='127.0.0.1' python3 -m pytest tests
38 |
--------------------------------------------------------------------------------
/config/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "log": {
3 | "loglevel": "warning"
4 | },
5 | "inbounds": [
6 | {
7 | "tag": "vmess",
8 | "port": 1094,
9 | "listen": "0.0.0.0",
10 | "protocol": "vmess",
11 | "settings": {
12 | "clients": [
13 | {
14 | "id": "96850032-1b92-46e9-a4f2-b99631456894",
15 | "security": "none"
16 | }
17 | ]
18 | }
19 |
20 | }
21 | ],
22 | "outbounds": [
23 | {
24 | "protocol": "freedom",
25 | "tag": "direct"
26 | }
27 | ],
28 | "routing": {
29 | "domainMatcher": "mph",
30 | "domainStrategy": "IPIfNonMatch",
31 | "rules": [
32 | {
33 | "type": "field",
34 | "inboundTag": ["vmess"],
35 | "outboundTag": "direct"
36 | }
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/proxy.rs:
--------------------------------------------------------------------------------
1 | use crate::{config::Config, copy, vmess::Vmess};
2 |
3 | use tokio::{
4 | io::{self, AsyncReadExt, AsyncWriteExt},
5 | net::{TcpListener, TcpStream},
6 | };
7 |
8 | pub async fn run(config: &Config) -> io::Result<()> {
9 | let listener = TcpListener::bind(&config.inbound.address).await?;
10 | log::info!("Listening {}", config.inbound.address);
11 |
12 | loop {
13 | let (conn, _) = listener.accept().await?;
14 |
15 | let upstream = TcpStream::connect(&config.outbound.address).await?;
16 | let vmess = Vmess::new(
17 | upstream,
18 | *config.outbound.uuid.as_bytes(),
19 | config.outbound.aead,
20 | );
21 |
22 | let (mut reader, mut writer) = conn.into_split();
23 | let (mut ureader, mut uwriter) = vmess.into_split();
24 |
25 | tokio::spawn(copy!(reader, uwriter));
26 | tokio::spawn(copy!(ureader, writer));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/aead.rs:
--------------------------------------------------------------------------------
1 | use crate::vmess::VmessWriter;
2 | use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit};
3 | use aes::Aes128;
4 | use md5::{Digest, Md5};
5 | use rand::Rng;
6 | use tokio::io::AsyncWrite;
7 |
8 | impl VmessWriter {
9 | pub(crate) fn create_auth_id(&self, time: &[u8; 8]) -> [u8; 16] {
10 | let mut buf = [0u8; 16];
11 |
12 | buf[..8].copy_from_slice(time);
13 |
14 | let mut salt = [0u8; 4];
15 | rand::thread_rng().fill(&mut salt);
16 | buf[8..12].copy_from_slice(&salt);
17 |
18 | let crc = crc32fast::hash(&buf[..12]);
19 | buf[12..].copy_from_slice(&crc.to_be_bytes());
20 |
21 | let key = md5!(&self.uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21");
22 | let key = crate::hash::kdf(&key, &[b"AES Auth ID Encryption"]);
23 | let cipher = Aes128::new((&key[..16]).into());
24 |
25 | let mut b = GenericArray::from([0u8; 16]);
26 | cipher.encrypt_block_b2b(&buf.into(), &mut b);
27 |
28 | b.into()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/hash.rs:
--------------------------------------------------------------------------------
1 | use sha2::{Digest, Sha256};
2 |
3 | trait Hasher {
4 | fn clone(&self) -> Box;
5 | fn update(&mut self, data: &[u8]);
6 | fn finalize(&mut self) -> [u8; 32];
7 | }
8 |
9 | struct Sha256Hash(Sha256);
10 |
11 | impl Sha256Hash {
12 | fn new() -> Self {
13 | Self(Sha256::new())
14 | }
15 | }
16 |
17 | impl Hasher for Sha256Hash {
18 | fn clone(&self) -> Box {
19 | Box::new(Self(self.0.clone()))
20 | }
21 |
22 | fn update(&mut self, data: &[u8]) {
23 | self.0.update(data);
24 | }
25 |
26 | fn finalize(&mut self) -> [u8; 32] {
27 | self.0.clone().finalize().into()
28 | }
29 | }
30 |
31 | struct RecursiveHash {
32 | inner: Box,
33 | outer: Box,
34 | ipad: [u8; 64],
35 | opad: [u8; 64],
36 | }
37 |
38 | impl RecursiveHash {
39 | fn new(key: &[u8], hash: Box) -> Self {
40 | let mut ipad = [0u8; 64];
41 | let mut opad = [0u8; 64];
42 |
43 | ipad[..key.len()].copy_from_slice(&key);
44 | opad[..key.len()].copy_from_slice(&key);
45 |
46 | for b in ipad.iter_mut() {
47 | *b ^= 0x36;
48 | }
49 |
50 | for b in opad.iter_mut() {
51 | *b ^= 0x5c;
52 | }
53 |
54 | let mut inner = hash.clone();
55 | let outer = hash;
56 |
57 | inner.update(&ipad);
58 | Self {
59 | inner,
60 | outer,
61 | ipad,
62 | opad,
63 | }
64 | }
65 | }
66 |
67 | impl Hasher for RecursiveHash {
68 | fn clone(&self) -> Box {
69 | let inner = self.inner.clone();
70 | let outer = self.outer.clone();
71 | let ipad = self.ipad.clone();
72 | let opad = self.opad.clone();
73 |
74 | Box::new(Self {
75 | inner,
76 | outer,
77 | ipad,
78 | opad,
79 | })
80 | }
81 |
82 | fn update(&mut self, data: &[u8]) {
83 | self.inner.update(data);
84 | }
85 |
86 | fn finalize(&mut self) -> [u8; 32] {
87 | let result: [u8; 32] = self.inner.finalize().into();
88 | self.outer.update(&self.opad);
89 | self.outer.update(&result);
90 | self.outer.finalize().into()
91 | }
92 | }
93 |
94 | pub fn kdf(key: &[u8], path: &[&[u8]]) -> [u8; 32] {
95 | let mut current = Box::new(RecursiveHash::new(
96 | b"VMess AEAD KDF",
97 | Box::new(Sha256Hash::new()),
98 | ));
99 |
100 | for p in path.into_iter() {
101 | current = Box::new(RecursiveHash::new(p, current));
102 | }
103 |
104 | current.update(key);
105 | current.finalize()
106 | }
107 |
108 | #[cfg(test)]
109 | mod tests {
110 | use super::*;
111 | use md5::Md5;
112 |
113 | #[test]
114 | fn test_kdf() {
115 | let uuid = uuid::uuid!("96850032-1b92-46e9-a4f2-b99631456894").as_bytes();
116 | let key = md5!(&uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21");
117 |
118 | let res = kdf(&key, &[b"AES Auth ID Encryption"]);
119 |
120 | assert_eq!(
121 | res[..16],
122 | [117, 82, 144, 159, 147, 65, 74, 253, 91, 74, 70, 84, 114, 118, 203, 30]
123 | );
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/utils.rs:
--------------------------------------------------------------------------------
1 | use std::fmt;
2 | use tokio::io::{Error, ErrorKind, Result};
3 |
4 | pub type Aes128CfbEnc = cfb_mode::Encryptor;
5 | pub type Aes128CfbDec = cfb_mode::BufDecryptor;
6 |
7 | #[macro_export]
8 | macro_rules! copy {
9 | ($r:ident, $w:ident) => {
10 | async move {
11 | let mut buf = [0; 16384]; // TODO: optimized chunk size
12 |
13 | loop {
14 | let len = match $r.read(&mut buf).await {
15 | Ok(x) => {
16 | if x == 0 {
17 | break;
18 | }
19 | x
20 | }
21 | _ => break,
22 | };
23 |
24 | let _ = $w.write(&buf[..len]).await;
25 | }
26 | }
27 | };
28 | }
29 |
30 | macro_rules! sha256 {
31 | ( $($v:expr),+ ) => {
32 | {
33 | let mut hash = Sha256::new();
34 | $(
35 | hash.update($v);
36 | )*
37 | hash.finalize()
38 | }
39 | }
40 | }
41 |
42 | macro_rules! md5 {
43 | ( $($v:expr),+ ) => {
44 | {
45 | let mut hash = Md5::new();
46 | $(
47 | hash.update($v);
48 | )*
49 | hash.finalize()
50 | }
51 | }
52 | }
53 |
54 | pub(crate) struct Addr<'a> {
55 | host: Option<&'a [u8]>,
56 | port: Option,
57 | }
58 |
59 | impl<'a> fmt::Debug for Addr<'a> {
60 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 | let mut addr = String::new();
62 |
63 | if let Some(host) = self.host {
64 | let host = String::from_utf8_lossy(host);
65 | addr.push_str(&host);
66 | }
67 |
68 | if let Some(port) = self.port {
69 | let port = format!(":{}", port);
70 | addr.push_str(&port);
71 | }
72 |
73 | write!(f, "{}", addr)
74 | }
75 | }
76 |
77 | impl<'a> Addr<'a> {
78 | pub(crate) fn host(&self) -> &'a [u8] {
79 | self.host.unwrap_or_default()
80 | }
81 |
82 | pub(crate) fn port(&self) -> u16 {
83 | self.port.unwrap_or(80)
84 | }
85 | }
86 |
87 | pub(crate) fn extract_addr<'a>(buf: &'a [u8]) -> Result> {
88 | let header = &[72, 111, 115, 116, 58, 32]; // "Host: "
89 |
90 | let mut addr = Addr {
91 | host: None,
92 | port: None,
93 | };
94 |
95 | let mut start = buf
96 | .windows(header.len())
97 | .position(|w| w == header)
98 | .map(|x| x + header.len())
99 | .ok_or(Error::new(ErrorKind::Other, "could not extract address"))?;
100 |
101 | let offset = buf[start..]
102 | .iter()
103 | .position(|&x| x == b'\r')
104 | .ok_or(Error::new(ErrorKind::Other, "could not extract address"))?;
105 |
106 | let port_offset = buf[start..start + offset].iter().position(|&x| x == b':');
107 | if let Some(port_offset) = port_offset {
108 | addr.host = Some(&buf[start..start + port_offset]);
109 |
110 | let end = start + offset;
111 | start += port_offset + 1; // skip colon
112 | let port = String::from_utf8_lossy(&buf[start..end]);
113 | addr.port = u16::from_str_radix(&port, 10).ok();
114 | } else {
115 | addr.host = Some(&buf[start..start + offset]);
116 | }
117 |
118 | Ok(addr)
119 | }
120 |
121 | #[cfg(test)]
122 | mod tests {
123 | use super::*;
124 |
125 | #[test]
126 | fn test_extract_addr() {
127 | let buf = b"GET http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: curl/7.85.0";
128 | let addr = extract_addr(buf).unwrap();
129 | assert_eq!(addr.host(), b"google.com");
130 | assert_eq!(addr.port(), 80);
131 |
132 | let buf =
133 | b"GET http://google.com/ HTTP/1.1\r\nHost: google.com:443\r\nUser-Agent: curl/7.85.0";
134 | let addr = extract_addr(buf).unwrap();
135 | assert_eq!(addr.host(), b"google.com");
136 | assert_eq!(addr.port(), 443);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/vmess.rs:
--------------------------------------------------------------------------------
1 | use crate::utils::{extract_addr, Addr, Aes128CfbDec, Aes128CfbEnc};
2 |
3 | use std::marker::Unpin;
4 | use std::time::{SystemTime, UNIX_EPOCH};
5 |
6 | use aes::cipher::{AsyncStreamCipher, KeyInit, KeyIvInit};
7 | use aes_gcm::{
8 | aead::{Aead, Payload},
9 | Aes128Gcm,
10 | };
11 |
12 | use const_fnv1a_hash::fnv1a_hash_32;
13 | use hmac::{Hmac, Mac};
14 | use md5::{Digest, Md5};
15 |
16 | use rand::Rng;
17 | use sha2::Sha256;
18 | use tokio::{
19 | io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, Result},
20 | net::{
21 | tcp::{OwnedReadHalf, OwnedWriteHalf},
22 | TcpStream,
23 | },
24 | };
25 |
26 | #[derive(Clone, Copy)]
27 | pub struct Encoder {
28 | pub iv: [u8; 16],
29 | pub key: [u8; 16],
30 | }
31 |
32 | impl Encoder {
33 | pub fn new() -> Self {
34 | let mut key = [0u8; 16];
35 | let mut iv = [0u8; 16];
36 |
37 | rand::thread_rng().fill(&mut key);
38 | rand::thread_rng().fill(&mut iv);
39 |
40 | Self { iv, key }
41 | }
42 | }
43 |
44 | pub struct Vmess {
45 | stream: Option,
46 | uuid: [u8; 16],
47 | aead: bool,
48 | }
49 |
50 | impl Vmess {
51 | pub fn new(stream: TcpStream, uuid: [u8; 16], aead: bool) -> Self {
52 | Self {
53 | uuid,
54 | aead,
55 | stream: Some(stream),
56 | }
57 | }
58 |
59 | pub fn into_split(self) -> (VmessReader, VmessWriter) {
60 | let stream = self.stream.expect("stream should contain a value");
61 | let (reader, writer) = stream.into_split();
62 | let encoder = Encoder::new();
63 |
64 | let r = VmessReader {
65 | reader,
66 | encoder,
67 | aead: self.aead,
68 | };
69 | let w = VmessWriter {
70 | encoder,
71 | writer,
72 | uuid: self.uuid,
73 | handshaked: false,
74 | aead: self.aead,
75 | };
76 |
77 | (r, w)
78 | }
79 | }
80 |
81 | pub struct VmessReader {
82 | reader: R,
83 | encoder: Encoder,
84 | aead: bool,
85 | }
86 |
87 | impl VmessReader {
88 | pub async fn read(&mut self, buf: &mut [u8]) -> Result {
89 | if !self.aead {
90 | // The header data is encrypted using AES-128-CFB encryption
91 | // The IV is MD5 of the data encryption IV, and the Key is MD5 of the data encryption Key
92 | //
93 | // +---------------------------+------------+-------------+------------------+-----------------+----------------------+
94 | // | 1 Byte | 1 Byte | 1 Byte | 1 Byte | M Bytes | Remaining Part |
95 | // +---------------------------+------------+-------------+------------------+-----------------+----------------------+
96 | // | Response Authentication V | Option Opt | Command Cmd | Command Length M | Command Content | Actual Response Data |
97 | // +---------------------------+------------+-------------+------------------+-----------------+----------------------+
98 |
99 | let key = md5!(&self.encoder.key);
100 | let iv = md5!(&self.encoder.iv);
101 | let mut decoder = Aes128CfbDec::new(&key.into(), &iv.into());
102 |
103 | let mut header = [0u8; 4];
104 | self.reader.read_exact(&mut header).await?;
105 | decoder.decrypt(&mut header); // ignore the header for now
106 | // just decrypt it because our decoder is stateful
107 |
108 | // https://xtls.github.io/en/development/protocols/vmess.html#data-section
109 | //
110 | // +----------+-------------+
111 | // | 2 Bytes | L Bytes |
112 | // +----------+-------------+
113 | // | Length L | Data Packet |
114 | // +----------+-------------+
115 | //
116 | // - Length L: A big-endian integer with a maximum value of 2^14
117 | // - Packet: A data packet encrypted by the specified encryption method
118 |
119 | // AES-128-CFB:
120 | // The entire data section is encrypted using AES-128-CFB
121 | // - 4 bytes: FNV1a hash of actual data
122 | // - L - 4 bytes: actual data
123 | let mut length = [0u8; 2];
124 | self.reader.read_exact(&mut length).await?;
125 | decoder.decrypt(&mut length);
126 |
127 | // When Opt(M) is enabled, the value of L is equal to the true value xor Mask
128 | // Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte()
129 | let length = (length[0] as usize) << 8 | (length[1] as usize) - 4; // 4bytes checksum
130 |
131 | let mut checksum = [0u8; 4];
132 | self.reader.read(&mut checksum).await?;
133 | decoder.decrypt(&mut checksum); // ignore the checksum for now
134 | // just decrypt it because our decoder is stateful
135 |
136 | self.reader.read(&mut buf[..length]).await?;
137 | decoder.decrypt(&mut buf[..length]);
138 |
139 | Ok(length)
140 | } else {
141 | let key = &sha256!(&self.encoder.key)[..16];
142 | let iv = &sha256!(&self.encoder.iv)[..16];
143 |
144 | let length_key = &crate::hash::kdf(&key, &[b"AEAD Resp Header Len Key"])[..16];
145 | let length_iv = &crate::hash::kdf(&iv, &[b"AEAD Resp Header Len IV"])[..12];
146 |
147 | let mut header_length = [0u8; 18];
148 | self.reader.read_exact(&mut header_length).await?;
149 |
150 | // header length
151 | let length = Aes128Gcm::new(length_key.into())
152 | .decrypt(length_iv.into(), &header_length[..])
153 | .unwrap();
154 | let length = ((length[0] as u16) << 8) | (length[1] as u16) + 16; // TODO: document
155 |
156 | let mut header = vec![0u8; length as usize];
157 | self.reader.read_exact(&mut header).await?; // ignore the header for now
158 |
159 | // read next 2bytes to retrive the payload length
160 | let mut length = [0u8; 2];
161 | self.reader.read(&mut length).await?;
162 | let length = ((length[0] as usize) << 8) | (length[1] as usize);
163 |
164 | self.reader.read(&mut buf[..length]).await
165 | }
166 | }
167 | }
168 |
169 | pub struct VmessWriter {
170 | pub(crate) writer: W,
171 | pub(crate) encoder: Encoder,
172 | pub(crate) uuid: [u8; 16],
173 | pub(crate) handshaked: bool,
174 | pub(crate) aead: bool,
175 | }
176 |
177 | impl VmessWriter {
178 | async fn handshake<'a>(&mut self, addr: Addr<'a>) -> Result<()> {
179 | // https://xtls.github.io/en/development/protocols/vmess.html#authentication-information
180 | //
181 | // +----------------------------+
182 | // | 16 Bytes |
183 | // +----------------------------+
184 | // | Authentication Information |
185 | // +----------------------------+
186 | //
187 | // H = MD5
188 | // K = User ID (16 bytes)
189 | // M = UTC time accurate to seconds, with a random value of ±30 seconds from the current time (8 bytes, Big Endian)
190 | // Hash = HMAC(H, K, M)
191 |
192 | let time = SystemTime::now()
193 | .duration_since(UNIX_EPOCH)
194 | .unwrap() // safe to unwrap: always later than UNIX_EPOCH
195 | .as_secs()
196 | .to_be_bytes();
197 |
198 | if !self.aead {
199 | let mut hash = as KeyInit>::new_from_slice(&self.uuid).unwrap(); // safe to unwrap: always valid length.
200 | hash.update(&time);
201 | let auth = hash.finalize().into_bytes();
202 | self.writer.write_all(&auth).await?;
203 | }
204 |
205 | // https://xtls.github.io/en/development/protocols/vmess.html#command-section
206 | //
207 | // +---------+--------------------+---------------------+-------------------------------+---------+----------+-------------------+----------+---------+---------+--------------+---------+--------------+----------+
208 | // | 1 Byte | 16 Bytes | 16 Bytes | 1 Byte | 1 Byte | 4 bits | 4 bits | 1 Byte | 1 Byte | 2 Bytes | 1 Byte | N Bytes | P Bytes | 4 Bytes |
209 | // +---------+--------------------+---------------------+-------------------------------+---------+----------+-------------------+----------+---------+---------+--------------+---------+--------------+----------+
210 | // | Version | Data Encryption IV | Data Encryption Key | Response Authentication Value | Options | Reserved | Encryption Method | Reserved | Command | Port | Address Type | Address | Random Value | Checksum |
211 | // +---------+--------------------+---------------------+-------------------------------+---------+----------+-------------------+----------+---------+---------+--------------+---------+--------------+----------+
212 |
213 | let mut cmd = vec![0x1u8]; // version is always 1
214 |
215 | cmd.extend_from_slice(&self.encoder.iv); // Data Encryption IV
216 | cmd.extend_from_slice(&self.encoder.key); // Data Encryption Key
217 |
218 | let enc_method = if !self.aead {
219 | 0x00 // AES-128-CFB
220 | } else {
221 | 0x05 // None
222 | };
223 | cmd.extend_from_slice(&[
224 | 0x00, // Response Authentication Value
225 | 0x01, // Option S(0x01): Standard format data stream (recommended)
226 | enc_method, // 4bits Reserved + Encryption Method
227 | 0x00, // 1byte Reserved
228 | 0x01, // Command: 0x01 TCP
229 | ]);
230 |
231 | // TODO: extract port from request. for now we use 80 for all requests
232 | cmd.extend_from_slice(&addr.port().to_be_bytes()); // Port
233 |
234 | // TODO: support ipv4/ipv6. for now we just support domain name
235 | cmd.extend_from_slice(&[0x02]); // Address Type: Domain name
236 |
237 | let mut address = vec![addr.host().len() as _];
238 | address.extend_from_slice(addr.host());
239 | cmd.extend_from_slice(&address);
240 |
241 | // P bytes random value -> assume p = 0, so we don't push data for it
242 |
243 | let checksum = fnv1a_hash_32(&cmd, None);
244 | cmd.extend_from_slice(&checksum.to_be_bytes()); // 4bytes checksum
245 |
246 | let iv = md5!(&time, &time, &time, &time);
247 | let key = md5!(&self.uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21");
248 |
249 | if !self.aead {
250 | // Non-AEAD
251 | // encrypted using AES-128-CFB
252 | // Key: MD5(user ID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21'))
253 | // IV: MD5(X + X + X + X), X = []byte(time generated by authentication information) (8 bytes, Big Endian)
254 | Aes128CfbEnc::new(&key.into(), &iv.into()).encrypt(&mut cmd);
255 | self.writer.write_all(&cmd).await
256 | } else {
257 | // AEAD
258 | let auth_id = self.create_auth_id(&time);
259 |
260 | let mut nonce = [0u8; 8];
261 | rand::thread_rng().fill(&mut nonce);
262 |
263 | // header length
264 | let payload = Payload {
265 | msg: &(cmd.len() as u16).to_be_bytes(),
266 | aad: &auth_id,
267 | };
268 | let header_length_key =
269 | &crate::hash::kdf(&key, &[b"VMess Header AEAD Key_Length", &auth_id, &nonce])[..16];
270 | let header_length_nonce =
271 | &crate::hash::kdf(&key, &[b"VMess Header AEAD Nonce_Length", &auth_id, &nonce])
272 | [..12];
273 |
274 | let header_length = Aes128Gcm::new(header_length_key.into())
275 | .encrypt(header_length_nonce.into(), payload)
276 | .unwrap(); // TODO: unwrap
277 |
278 | // header payload
279 | let payload = Payload {
280 | msg: &cmd,
281 | aad: &auth_id,
282 | };
283 | let header_payload_key =
284 | &crate::hash::kdf(&key, &[b"VMess Header AEAD Key", &auth_id, &nonce])[..16];
285 | let header_payload_nonce =
286 | &crate::hash::kdf(&key, &[b"VMess Header AEAD Nonce", &auth_id, &nonce])[..12];
287 |
288 | let header_payload = Aes128Gcm::new(header_payload_key.into())
289 | .encrypt(header_payload_nonce.into(), payload)
290 | .unwrap();
291 |
292 | self.writer.write_all(&auth_id).await?;
293 | self.writer.write_all(&header_length).await?;
294 | self.writer.write_all(&nonce).await?;
295 | self.writer.write_all(&header_payload).await
296 | }
297 | }
298 |
299 | pub async fn write(&mut self, buf: &[u8]) -> Result<()> {
300 | if !self.handshaked {
301 | let addr = extract_addr(buf)?;
302 | log::info!("accepted {:?}", addr);
303 |
304 | self.handshake(addr).await?;
305 | self.handshaked = true;
306 | }
307 |
308 | // https://xtls.github.io/en/development/protocols/vmess.html#data-section
309 | //
310 | // +----------+-------------+
311 | // | 2 Bytes | L Bytes |
312 | // +----------+-------------+
313 | // | Length L | Data Packet |
314 | // +----------+-------------+
315 | //
316 | // - Length L: A big-endian integer with a maximum value of 2^14
317 | // - Packet: A data packet encrypted by the specified encryption method
318 |
319 | // AES-128-CFB:
320 | // The entire data section is encrypted using AES-128-CFB
321 | // - 4 bytes: FNV1a hash of actual data
322 | // - L - 4 bytes: actual data
323 | let mut vmess_buf = Vec::new();
324 |
325 | let length = buf.len() as u16;
326 | if !self.aead {
327 | let checksum = fnv1a_hash_32(&buf, None);
328 |
329 | vmess_buf.extend_from_slice(&(length + 4).to_be_bytes()); // 4bytes fnv1a
330 | vmess_buf.extend_from_slice(&checksum.to_be_bytes());
331 | vmess_buf.extend_from_slice(buf);
332 |
333 | Aes128CfbEnc::new(&self.encoder.key.into(), &self.encoder.iv.into())
334 | .encrypt(&mut vmess_buf);
335 | } else {
336 | vmess_buf.extend_from_slice(&length.to_be_bytes());
337 | vmess_buf.extend_from_slice(buf);
338 | }
339 |
340 | self.writer.write_all(&vmess_buf).await
341 | }
342 | }
343 |
344 | #[cfg(test)]
345 | mod tests {
346 | use super::*;
347 | use std::pin::Pin;
348 | use std::task::{Context, Poll};
349 | use tokio::io::Result;
350 |
351 | struct MockWriter {
352 | written: Vec,
353 | }
354 |
355 | impl AsyncWrite for MockWriter {
356 | fn poll_write(
357 | mut self: Pin<&mut Self>,
358 | _: &mut Context<'_>,
359 | buf: &[u8],
360 | ) -> Poll> {
361 | self.written.extend_from_slice(buf);
362 | Poll::Ready(Ok(buf.len()))
363 | }
364 |
365 | fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> {
366 | todo!()
367 | }
368 |
369 | fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> {
370 | todo!()
371 | }
372 | }
373 |
374 | #[tokio::test]
375 | async fn test_vmess_write() {
376 | let w = MockWriter { written: vec![] };
377 |
378 | let uuid = [0; 16];
379 | let encoder = Encoder::new();
380 | let mut vwriter = VmessWriter {
381 | writer: w,
382 | handshaked: false,
383 | aead: false,
384 | uuid,
385 | encoder,
386 | };
387 |
388 | let buf = b"GET http://google.com/ HTTP/1.1\r\nHost: google.com\r\nUser-Agent: curl/7.85.0";
389 | let _ = vwriter.write(buf).await;
390 |
391 | assert_eq!(vwriter.handshaked, true);
392 |
393 | let header_length = 72;
394 | let data = vwriter.writer.written.as_mut_slice();
395 | Aes128CfbDec::new(&encoder.key.into(), &encoder.iv.into())
396 | .decrypt(&mut data[header_length..]);
397 |
398 | let payload = &data[header_length..];
399 |
400 | let payload_length = u16::from_be_bytes([payload[0], payload[1]]);
401 | assert_eq!(payload_length, 78);
402 |
403 | let checksum = fnv1a_hash_32(buf, None);
404 | assert_eq!(checksum.to_be_bytes(), payload[2..6]);
405 |
406 | // actual data
407 | assert_eq!(&payload[6..], buf);
408 | }
409 | }
410 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.21.0"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler"
16 | version = "1.0.2"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19 |
20 | [[package]]
21 | name = "aead"
22 | version = "0.5.2"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
25 | dependencies = [
26 | "crypto-common",
27 | "generic-array",
28 | ]
29 |
30 | [[package]]
31 | name = "aes"
32 | version = "0.8.4"
33 | source = "registry+https://github.com/rust-lang/crates.io-index"
34 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
35 | dependencies = [
36 | "cfg-if",
37 | "cipher",
38 | "cpufeatures",
39 | ]
40 |
41 | [[package]]
42 | name = "aes-gcm"
43 | version = "0.10.3"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
46 | dependencies = [
47 | "aead",
48 | "aes",
49 | "cipher",
50 | "ctr",
51 | "ghash",
52 | "subtle",
53 | ]
54 |
55 | [[package]]
56 | name = "aho-corasick"
57 | version = "1.1.2"
58 | source = "registry+https://github.com/rust-lang/crates.io-index"
59 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
60 | dependencies = [
61 | "memchr",
62 | ]
63 |
64 | [[package]]
65 | name = "anyhow"
66 | version = "1.0.80"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
69 |
70 | [[package]]
71 | name = "atty"
72 | version = "0.2.14"
73 | source = "registry+https://github.com/rust-lang/crates.io-index"
74 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
75 | dependencies = [
76 | "hermit-abi 0.1.19",
77 | "libc",
78 | "winapi",
79 | ]
80 |
81 | [[package]]
82 | name = "autocfg"
83 | version = "1.1.0"
84 | source = "registry+https://github.com/rust-lang/crates.io-index"
85 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
86 |
87 | [[package]]
88 | name = "backtrace"
89 | version = "0.3.69"
90 | source = "registry+https://github.com/rust-lang/crates.io-index"
91 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
92 | dependencies = [
93 | "addr2line",
94 | "cc",
95 | "cfg-if",
96 | "libc",
97 | "miniz_oxide",
98 | "object",
99 | "rustc-demangle",
100 | ]
101 |
102 | [[package]]
103 | name = "bitflags"
104 | version = "1.3.2"
105 | source = "registry+https://github.com/rust-lang/crates.io-index"
106 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
107 |
108 | [[package]]
109 | name = "block-buffer"
110 | version = "0.10.4"
111 | source = "registry+https://github.com/rust-lang/crates.io-index"
112 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
113 | dependencies = [
114 | "generic-array",
115 | ]
116 |
117 | [[package]]
118 | name = "bytes"
119 | version = "1.5.0"
120 | source = "registry+https://github.com/rust-lang/crates.io-index"
121 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
122 |
123 | [[package]]
124 | name = "cc"
125 | version = "1.0.88"
126 | source = "registry+https://github.com/rust-lang/crates.io-index"
127 | checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc"
128 |
129 | [[package]]
130 | name = "cfb-mode"
131 | version = "0.8.2"
132 | source = "registry+https://github.com/rust-lang/crates.io-index"
133 | checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330"
134 | dependencies = [
135 | "cipher",
136 | ]
137 |
138 | [[package]]
139 | name = "cfg-if"
140 | version = "1.0.0"
141 | source = "registry+https://github.com/rust-lang/crates.io-index"
142 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
143 |
144 | [[package]]
145 | name = "cipher"
146 | version = "0.4.4"
147 | source = "registry+https://github.com/rust-lang/crates.io-index"
148 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
149 | dependencies = [
150 | "crypto-common",
151 | "inout",
152 | ]
153 |
154 | [[package]]
155 | name = "clap"
156 | version = "3.2.25"
157 | source = "registry+https://github.com/rust-lang/crates.io-index"
158 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
159 | dependencies = [
160 | "atty",
161 | "bitflags",
162 | "clap_derive",
163 | "clap_lex",
164 | "indexmap 1.9.3",
165 | "once_cell",
166 | "strsim",
167 | "termcolor",
168 | "textwrap",
169 | ]
170 |
171 | [[package]]
172 | name = "clap_derive"
173 | version = "3.2.25"
174 | source = "registry+https://github.com/rust-lang/crates.io-index"
175 | checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
176 | dependencies = [
177 | "heck",
178 | "proc-macro-error",
179 | "proc-macro2",
180 | "quote",
181 | "syn 1.0.109",
182 | ]
183 |
184 | [[package]]
185 | name = "clap_lex"
186 | version = "0.2.4"
187 | source = "registry+https://github.com/rust-lang/crates.io-index"
188 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
189 | dependencies = [
190 | "os_str_bytes",
191 | ]
192 |
193 | [[package]]
194 | name = "const-fnv1a-hash"
195 | version = "1.1.0"
196 | source = "registry+https://github.com/rust-lang/crates.io-index"
197 | checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca"
198 |
199 | [[package]]
200 | name = "cpufeatures"
201 | version = "0.2.12"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
204 | dependencies = [
205 | "libc",
206 | ]
207 |
208 | [[package]]
209 | name = "crc32fast"
210 | version = "1.4.0"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
213 | dependencies = [
214 | "cfg-if",
215 | ]
216 |
217 | [[package]]
218 | name = "crypto-common"
219 | version = "0.1.6"
220 | source = "registry+https://github.com/rust-lang/crates.io-index"
221 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
222 | dependencies = [
223 | "generic-array",
224 | "rand_core",
225 | "typenum",
226 | ]
227 |
228 | [[package]]
229 | name = "ctr"
230 | version = "0.9.2"
231 | source = "registry+https://github.com/rust-lang/crates.io-index"
232 | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
233 | dependencies = [
234 | "cipher",
235 | ]
236 |
237 | [[package]]
238 | name = "digest"
239 | version = "0.10.7"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
242 | dependencies = [
243 | "block-buffer",
244 | "crypto-common",
245 | "subtle",
246 | ]
247 |
248 | [[package]]
249 | name = "env_logger"
250 | version = "0.10.2"
251 | source = "registry+https://github.com/rust-lang/crates.io-index"
252 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
253 | dependencies = [
254 | "humantime",
255 | "is-terminal",
256 | "log",
257 | "regex",
258 | "termcolor",
259 | ]
260 |
261 | [[package]]
262 | name = "equivalent"
263 | version = "1.0.1"
264 | source = "registry+https://github.com/rust-lang/crates.io-index"
265 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
266 |
267 | [[package]]
268 | name = "generic-array"
269 | version = "0.14.7"
270 | source = "registry+https://github.com/rust-lang/crates.io-index"
271 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
272 | dependencies = [
273 | "typenum",
274 | "version_check",
275 | ]
276 |
277 | [[package]]
278 | name = "getrandom"
279 | version = "0.2.12"
280 | source = "registry+https://github.com/rust-lang/crates.io-index"
281 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
282 | dependencies = [
283 | "cfg-if",
284 | "libc",
285 | "wasi",
286 | ]
287 |
288 | [[package]]
289 | name = "ghash"
290 | version = "0.5.1"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
293 | dependencies = [
294 | "opaque-debug",
295 | "polyval",
296 | ]
297 |
298 | [[package]]
299 | name = "gimli"
300 | version = "0.28.1"
301 | source = "registry+https://github.com/rust-lang/crates.io-index"
302 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
303 |
304 | [[package]]
305 | name = "hashbrown"
306 | version = "0.12.3"
307 | source = "registry+https://github.com/rust-lang/crates.io-index"
308 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
309 |
310 | [[package]]
311 | name = "hashbrown"
312 | version = "0.14.3"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
315 |
316 | [[package]]
317 | name = "heck"
318 | version = "0.4.1"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
321 |
322 | [[package]]
323 | name = "hermit-abi"
324 | version = "0.1.19"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
327 | dependencies = [
328 | "libc",
329 | ]
330 |
331 | [[package]]
332 | name = "hermit-abi"
333 | version = "0.3.9"
334 | source = "registry+https://github.com/rust-lang/crates.io-index"
335 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
336 |
337 | [[package]]
338 | name = "hmac"
339 | version = "0.12.1"
340 | source = "registry+https://github.com/rust-lang/crates.io-index"
341 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
342 | dependencies = [
343 | "digest",
344 | ]
345 |
346 | [[package]]
347 | name = "humantime"
348 | version = "2.1.0"
349 | source = "registry+https://github.com/rust-lang/crates.io-index"
350 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
351 |
352 | [[package]]
353 | name = "indexmap"
354 | version = "1.9.3"
355 | source = "registry+https://github.com/rust-lang/crates.io-index"
356 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
357 | dependencies = [
358 | "autocfg",
359 | "hashbrown 0.12.3",
360 | ]
361 |
362 | [[package]]
363 | name = "indexmap"
364 | version = "2.2.5"
365 | source = "registry+https://github.com/rust-lang/crates.io-index"
366 | checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
367 | dependencies = [
368 | "equivalent",
369 | "hashbrown 0.14.3",
370 | ]
371 |
372 | [[package]]
373 | name = "inout"
374 | version = "0.1.3"
375 | source = "registry+https://github.com/rust-lang/crates.io-index"
376 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
377 | dependencies = [
378 | "generic-array",
379 | ]
380 |
381 | [[package]]
382 | name = "is-terminal"
383 | version = "0.4.12"
384 | source = "registry+https://github.com/rust-lang/crates.io-index"
385 | checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
386 | dependencies = [
387 | "hermit-abi 0.3.9",
388 | "libc",
389 | "windows-sys 0.52.0",
390 | ]
391 |
392 | [[package]]
393 | name = "libc"
394 | version = "0.2.153"
395 | source = "registry+https://github.com/rust-lang/crates.io-index"
396 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
397 |
398 | [[package]]
399 | name = "lock_api"
400 | version = "0.4.11"
401 | source = "registry+https://github.com/rust-lang/crates.io-index"
402 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
403 | dependencies = [
404 | "autocfg",
405 | "scopeguard",
406 | ]
407 |
408 | [[package]]
409 | name = "log"
410 | version = "0.4.21"
411 | source = "registry+https://github.com/rust-lang/crates.io-index"
412 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
413 |
414 | [[package]]
415 | name = "md-5"
416 | version = "0.10.6"
417 | source = "registry+https://github.com/rust-lang/crates.io-index"
418 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
419 | dependencies = [
420 | "cfg-if",
421 | "digest",
422 | ]
423 |
424 | [[package]]
425 | name = "memchr"
426 | version = "2.7.1"
427 | source = "registry+https://github.com/rust-lang/crates.io-index"
428 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
429 |
430 | [[package]]
431 | name = "miniz_oxide"
432 | version = "0.7.2"
433 | source = "registry+https://github.com/rust-lang/crates.io-index"
434 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
435 | dependencies = [
436 | "adler",
437 | ]
438 |
439 | [[package]]
440 | name = "mio"
441 | version = "0.8.11"
442 | source = "registry+https://github.com/rust-lang/crates.io-index"
443 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
444 | dependencies = [
445 | "libc",
446 | "wasi",
447 | "windows-sys 0.48.0",
448 | ]
449 |
450 | [[package]]
451 | name = "num_cpus"
452 | version = "1.16.0"
453 | source = "registry+https://github.com/rust-lang/crates.io-index"
454 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
455 | dependencies = [
456 | "hermit-abi 0.3.9",
457 | "libc",
458 | ]
459 |
460 | [[package]]
461 | name = "object"
462 | version = "0.32.2"
463 | source = "registry+https://github.com/rust-lang/crates.io-index"
464 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
465 | dependencies = [
466 | "memchr",
467 | ]
468 |
469 | [[package]]
470 | name = "once_cell"
471 | version = "1.19.0"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
474 |
475 | [[package]]
476 | name = "opaque-debug"
477 | version = "0.3.1"
478 | source = "registry+https://github.com/rust-lang/crates.io-index"
479 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
480 |
481 | [[package]]
482 | name = "os_str_bytes"
483 | version = "6.6.1"
484 | source = "registry+https://github.com/rust-lang/crates.io-index"
485 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
486 |
487 | [[package]]
488 | name = "parking_lot"
489 | version = "0.12.1"
490 | source = "registry+https://github.com/rust-lang/crates.io-index"
491 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
492 | dependencies = [
493 | "lock_api",
494 | "parking_lot_core",
495 | ]
496 |
497 | [[package]]
498 | name = "parking_lot_core"
499 | version = "0.9.9"
500 | source = "registry+https://github.com/rust-lang/crates.io-index"
501 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
502 | dependencies = [
503 | "cfg-if",
504 | "libc",
505 | "redox_syscall",
506 | "smallvec",
507 | "windows-targets 0.48.5",
508 | ]
509 |
510 | [[package]]
511 | name = "pin-project-lite"
512 | version = "0.2.13"
513 | source = "registry+https://github.com/rust-lang/crates.io-index"
514 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
515 |
516 | [[package]]
517 | name = "polyval"
518 | version = "0.6.2"
519 | source = "registry+https://github.com/rust-lang/crates.io-index"
520 | checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
521 | dependencies = [
522 | "cfg-if",
523 | "cpufeatures",
524 | "opaque-debug",
525 | "universal-hash",
526 | ]
527 |
528 | [[package]]
529 | name = "ppv-lite86"
530 | version = "0.2.17"
531 | source = "registry+https://github.com/rust-lang/crates.io-index"
532 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
533 |
534 | [[package]]
535 | name = "pretty_env_logger"
536 | version = "0.5.0"
537 | source = "registry+https://github.com/rust-lang/crates.io-index"
538 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
539 | dependencies = [
540 | "env_logger",
541 | "log",
542 | ]
543 |
544 | [[package]]
545 | name = "proc-macro-error"
546 | version = "1.0.4"
547 | source = "registry+https://github.com/rust-lang/crates.io-index"
548 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
549 | dependencies = [
550 | "proc-macro-error-attr",
551 | "proc-macro2",
552 | "quote",
553 | "syn 1.0.109",
554 | "version_check",
555 | ]
556 |
557 | [[package]]
558 | name = "proc-macro-error-attr"
559 | version = "1.0.4"
560 | source = "registry+https://github.com/rust-lang/crates.io-index"
561 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
562 | dependencies = [
563 | "proc-macro2",
564 | "quote",
565 | "version_check",
566 | ]
567 |
568 | [[package]]
569 | name = "proc-macro2"
570 | version = "1.0.78"
571 | source = "registry+https://github.com/rust-lang/crates.io-index"
572 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
573 | dependencies = [
574 | "unicode-ident",
575 | ]
576 |
577 | [[package]]
578 | name = "quote"
579 | version = "1.0.35"
580 | source = "registry+https://github.com/rust-lang/crates.io-index"
581 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
582 | dependencies = [
583 | "proc-macro2",
584 | ]
585 |
586 | [[package]]
587 | name = "rand"
588 | version = "0.8.5"
589 | source = "registry+https://github.com/rust-lang/crates.io-index"
590 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
591 | dependencies = [
592 | "libc",
593 | "rand_chacha",
594 | "rand_core",
595 | ]
596 |
597 | [[package]]
598 | name = "rand_chacha"
599 | version = "0.3.1"
600 | source = "registry+https://github.com/rust-lang/crates.io-index"
601 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
602 | dependencies = [
603 | "ppv-lite86",
604 | "rand_core",
605 | ]
606 |
607 | [[package]]
608 | name = "rand_core"
609 | version = "0.6.4"
610 | source = "registry+https://github.com/rust-lang/crates.io-index"
611 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
612 | dependencies = [
613 | "getrandom",
614 | ]
615 |
616 | [[package]]
617 | name = "redox_syscall"
618 | version = "0.4.1"
619 | source = "registry+https://github.com/rust-lang/crates.io-index"
620 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
621 | dependencies = [
622 | "bitflags",
623 | ]
624 |
625 | [[package]]
626 | name = "regex"
627 | version = "1.10.3"
628 | source = "registry+https://github.com/rust-lang/crates.io-index"
629 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
630 | dependencies = [
631 | "aho-corasick",
632 | "memchr",
633 | "regex-automata",
634 | "regex-syntax",
635 | ]
636 |
637 | [[package]]
638 | name = "regex-automata"
639 | version = "0.4.5"
640 | source = "registry+https://github.com/rust-lang/crates.io-index"
641 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
642 | dependencies = [
643 | "aho-corasick",
644 | "memchr",
645 | "regex-syntax",
646 | ]
647 |
648 | [[package]]
649 | name = "regex-syntax"
650 | version = "0.8.2"
651 | source = "registry+https://github.com/rust-lang/crates.io-index"
652 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
653 |
654 | [[package]]
655 | name = "rustc-demangle"
656 | version = "0.1.23"
657 | source = "registry+https://github.com/rust-lang/crates.io-index"
658 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
659 |
660 | [[package]]
661 | name = "scopeguard"
662 | version = "1.2.0"
663 | source = "registry+https://github.com/rust-lang/crates.io-index"
664 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
665 |
666 | [[package]]
667 | name = "serde"
668 | version = "1.0.197"
669 | source = "registry+https://github.com/rust-lang/crates.io-index"
670 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
671 | dependencies = [
672 | "serde_derive",
673 | ]
674 |
675 | [[package]]
676 | name = "serde_derive"
677 | version = "1.0.197"
678 | source = "registry+https://github.com/rust-lang/crates.io-index"
679 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
680 | dependencies = [
681 | "proc-macro2",
682 | "quote",
683 | "syn 2.0.52",
684 | ]
685 |
686 | [[package]]
687 | name = "serde_spanned"
688 | version = "0.6.5"
689 | source = "registry+https://github.com/rust-lang/crates.io-index"
690 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
691 | dependencies = [
692 | "serde",
693 | ]
694 |
695 | [[package]]
696 | name = "sha2"
697 | version = "0.10.8"
698 | source = "registry+https://github.com/rust-lang/crates.io-index"
699 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
700 | dependencies = [
701 | "cfg-if",
702 | "cpufeatures",
703 | "digest",
704 | ]
705 |
706 | [[package]]
707 | name = "signal-hook-registry"
708 | version = "1.4.1"
709 | source = "registry+https://github.com/rust-lang/crates.io-index"
710 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
711 | dependencies = [
712 | "libc",
713 | ]
714 |
715 | [[package]]
716 | name = "smallvec"
717 | version = "1.13.1"
718 | source = "registry+https://github.com/rust-lang/crates.io-index"
719 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
720 |
721 | [[package]]
722 | name = "socket2"
723 | version = "0.5.6"
724 | source = "registry+https://github.com/rust-lang/crates.io-index"
725 | checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
726 | dependencies = [
727 | "libc",
728 | "windows-sys 0.52.0",
729 | ]
730 |
731 | [[package]]
732 | name = "strsim"
733 | version = "0.10.0"
734 | source = "registry+https://github.com/rust-lang/crates.io-index"
735 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
736 |
737 | [[package]]
738 | name = "subtle"
739 | version = "2.5.0"
740 | source = "registry+https://github.com/rust-lang/crates.io-index"
741 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
742 |
743 | [[package]]
744 | name = "syn"
745 | version = "1.0.109"
746 | source = "registry+https://github.com/rust-lang/crates.io-index"
747 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
748 | dependencies = [
749 | "proc-macro2",
750 | "quote",
751 | "unicode-ident",
752 | ]
753 |
754 | [[package]]
755 | name = "syn"
756 | version = "2.0.52"
757 | source = "registry+https://github.com/rust-lang/crates.io-index"
758 | checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
759 | dependencies = [
760 | "proc-macro2",
761 | "quote",
762 | "unicode-ident",
763 | ]
764 |
765 | [[package]]
766 | name = "termcolor"
767 | version = "1.4.1"
768 | source = "registry+https://github.com/rust-lang/crates.io-index"
769 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
770 | dependencies = [
771 | "winapi-util",
772 | ]
773 |
774 | [[package]]
775 | name = "textwrap"
776 | version = "0.16.1"
777 | source = "registry+https://github.com/rust-lang/crates.io-index"
778 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
779 |
780 | [[package]]
781 | name = "tokio"
782 | version = "1.36.0"
783 | source = "registry+https://github.com/rust-lang/crates.io-index"
784 | checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
785 | dependencies = [
786 | "backtrace",
787 | "bytes",
788 | "libc",
789 | "mio",
790 | "num_cpus",
791 | "parking_lot",
792 | "pin-project-lite",
793 | "signal-hook-registry",
794 | "socket2",
795 | "tokio-macros",
796 | "windows-sys 0.48.0",
797 | ]
798 |
799 | [[package]]
800 | name = "tokio-macros"
801 | version = "2.2.0"
802 | source = "registry+https://github.com/rust-lang/crates.io-index"
803 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
804 | dependencies = [
805 | "proc-macro2",
806 | "quote",
807 | "syn 2.0.52",
808 | ]
809 |
810 | [[package]]
811 | name = "toml"
812 | version = "0.8.10"
813 | source = "registry+https://github.com/rust-lang/crates.io-index"
814 | checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
815 | dependencies = [
816 | "serde",
817 | "serde_spanned",
818 | "toml_datetime",
819 | "toml_edit",
820 | ]
821 |
822 | [[package]]
823 | name = "toml_datetime"
824 | version = "0.6.5"
825 | source = "registry+https://github.com/rust-lang/crates.io-index"
826 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
827 | dependencies = [
828 | "serde",
829 | ]
830 |
831 | [[package]]
832 | name = "toml_edit"
833 | version = "0.22.6"
834 | source = "registry+https://github.com/rust-lang/crates.io-index"
835 | checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
836 | dependencies = [
837 | "indexmap 2.2.5",
838 | "serde",
839 | "serde_spanned",
840 | "toml_datetime",
841 | "winnow",
842 | ]
843 |
844 | [[package]]
845 | name = "typenum"
846 | version = "1.17.0"
847 | source = "registry+https://github.com/rust-lang/crates.io-index"
848 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
849 |
850 | [[package]]
851 | name = "unicode-ident"
852 | version = "1.0.12"
853 | source = "registry+https://github.com/rust-lang/crates.io-index"
854 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
855 |
856 | [[package]]
857 | name = "universal-hash"
858 | version = "0.5.1"
859 | source = "registry+https://github.com/rust-lang/crates.io-index"
860 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
861 | dependencies = [
862 | "crypto-common",
863 | "subtle",
864 | ]
865 |
866 | [[package]]
867 | name = "uuid"
868 | version = "1.7.0"
869 | source = "registry+https://github.com/rust-lang/crates.io-index"
870 | checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
871 | dependencies = [
872 | "serde",
873 | ]
874 |
875 | [[package]]
876 | name = "version_check"
877 | version = "0.9.4"
878 | source = "registry+https://github.com/rust-lang/crates.io-index"
879 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
880 |
881 | [[package]]
882 | name = "vmessy"
883 | version = "0.1.0"
884 | dependencies = [
885 | "aes",
886 | "aes-gcm",
887 | "anyhow",
888 | "cfb-mode",
889 | "clap",
890 | "const-fnv1a-hash",
891 | "crc32fast",
892 | "hmac",
893 | "log",
894 | "md-5",
895 | "pretty_env_logger",
896 | "rand",
897 | "serde",
898 | "sha2",
899 | "tokio",
900 | "toml",
901 | "uuid",
902 | ]
903 |
904 | [[package]]
905 | name = "wasi"
906 | version = "0.11.0+wasi-snapshot-preview1"
907 | source = "registry+https://github.com/rust-lang/crates.io-index"
908 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
909 |
910 | [[package]]
911 | name = "winapi"
912 | version = "0.3.9"
913 | source = "registry+https://github.com/rust-lang/crates.io-index"
914 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
915 | dependencies = [
916 | "winapi-i686-pc-windows-gnu",
917 | "winapi-x86_64-pc-windows-gnu",
918 | ]
919 |
920 | [[package]]
921 | name = "winapi-i686-pc-windows-gnu"
922 | version = "0.4.0"
923 | source = "registry+https://github.com/rust-lang/crates.io-index"
924 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
925 |
926 | [[package]]
927 | name = "winapi-util"
928 | version = "0.1.6"
929 | source = "registry+https://github.com/rust-lang/crates.io-index"
930 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
931 | dependencies = [
932 | "winapi",
933 | ]
934 |
935 | [[package]]
936 | name = "winapi-x86_64-pc-windows-gnu"
937 | version = "0.4.0"
938 | source = "registry+https://github.com/rust-lang/crates.io-index"
939 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
940 |
941 | [[package]]
942 | name = "windows-sys"
943 | version = "0.48.0"
944 | source = "registry+https://github.com/rust-lang/crates.io-index"
945 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
946 | dependencies = [
947 | "windows-targets 0.48.5",
948 | ]
949 |
950 | [[package]]
951 | name = "windows-sys"
952 | version = "0.52.0"
953 | source = "registry+https://github.com/rust-lang/crates.io-index"
954 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
955 | dependencies = [
956 | "windows-targets 0.52.4",
957 | ]
958 |
959 | [[package]]
960 | name = "windows-targets"
961 | version = "0.48.5"
962 | source = "registry+https://github.com/rust-lang/crates.io-index"
963 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
964 | dependencies = [
965 | "windows_aarch64_gnullvm 0.48.5",
966 | "windows_aarch64_msvc 0.48.5",
967 | "windows_i686_gnu 0.48.5",
968 | "windows_i686_msvc 0.48.5",
969 | "windows_x86_64_gnu 0.48.5",
970 | "windows_x86_64_gnullvm 0.48.5",
971 | "windows_x86_64_msvc 0.48.5",
972 | ]
973 |
974 | [[package]]
975 | name = "windows-targets"
976 | version = "0.52.4"
977 | source = "registry+https://github.com/rust-lang/crates.io-index"
978 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
979 | dependencies = [
980 | "windows_aarch64_gnullvm 0.52.4",
981 | "windows_aarch64_msvc 0.52.4",
982 | "windows_i686_gnu 0.52.4",
983 | "windows_i686_msvc 0.52.4",
984 | "windows_x86_64_gnu 0.52.4",
985 | "windows_x86_64_gnullvm 0.52.4",
986 | "windows_x86_64_msvc 0.52.4",
987 | ]
988 |
989 | [[package]]
990 | name = "windows_aarch64_gnullvm"
991 | version = "0.48.5"
992 | source = "registry+https://github.com/rust-lang/crates.io-index"
993 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
994 |
995 | [[package]]
996 | name = "windows_aarch64_gnullvm"
997 | version = "0.52.4"
998 | source = "registry+https://github.com/rust-lang/crates.io-index"
999 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
1000 |
1001 | [[package]]
1002 | name = "windows_aarch64_msvc"
1003 | version = "0.48.5"
1004 | source = "registry+https://github.com/rust-lang/crates.io-index"
1005 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
1006 |
1007 | [[package]]
1008 | name = "windows_aarch64_msvc"
1009 | version = "0.52.4"
1010 | source = "registry+https://github.com/rust-lang/crates.io-index"
1011 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
1012 |
1013 | [[package]]
1014 | name = "windows_i686_gnu"
1015 | version = "0.48.5"
1016 | source = "registry+https://github.com/rust-lang/crates.io-index"
1017 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
1018 |
1019 | [[package]]
1020 | name = "windows_i686_gnu"
1021 | version = "0.52.4"
1022 | source = "registry+https://github.com/rust-lang/crates.io-index"
1023 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
1024 |
1025 | [[package]]
1026 | name = "windows_i686_msvc"
1027 | version = "0.48.5"
1028 | source = "registry+https://github.com/rust-lang/crates.io-index"
1029 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
1030 |
1031 | [[package]]
1032 | name = "windows_i686_msvc"
1033 | version = "0.52.4"
1034 | source = "registry+https://github.com/rust-lang/crates.io-index"
1035 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
1036 |
1037 | [[package]]
1038 | name = "windows_x86_64_gnu"
1039 | version = "0.48.5"
1040 | source = "registry+https://github.com/rust-lang/crates.io-index"
1041 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
1042 |
1043 | [[package]]
1044 | name = "windows_x86_64_gnu"
1045 | version = "0.52.4"
1046 | source = "registry+https://github.com/rust-lang/crates.io-index"
1047 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
1048 |
1049 | [[package]]
1050 | name = "windows_x86_64_gnullvm"
1051 | version = "0.48.5"
1052 | source = "registry+https://github.com/rust-lang/crates.io-index"
1053 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
1054 |
1055 | [[package]]
1056 | name = "windows_x86_64_gnullvm"
1057 | version = "0.52.4"
1058 | source = "registry+https://github.com/rust-lang/crates.io-index"
1059 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
1060 |
1061 | [[package]]
1062 | name = "windows_x86_64_msvc"
1063 | version = "0.48.5"
1064 | source = "registry+https://github.com/rust-lang/crates.io-index"
1065 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
1066 |
1067 | [[package]]
1068 | name = "windows_x86_64_msvc"
1069 | version = "0.52.4"
1070 | source = "registry+https://github.com/rust-lang/crates.io-index"
1071 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
1072 |
1073 | [[package]]
1074 | name = "winnow"
1075 | version = "0.6.5"
1076 | source = "registry+https://github.com/rust-lang/crates.io-index"
1077 | checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
1078 | dependencies = [
1079 | "memchr",
1080 | ]
1081 |
--------------------------------------------------------------------------------