├── .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\n

301 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 | --------------------------------------------------------------------------------