aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock224
-rw-r--r--Cargo.toml2
-rw-r--r--src/encoding/headermap.rs18
-rw-r--r--src/encoding/headers.rs71
-rw-r--r--src/encoding/method.rs18
-rw-r--r--src/encoding/request.rs1
-rw-r--r--src/encoding/uri.rs1
-rw-r--r--src/lib.rs2
-rw-r--r--src/transaction/mod.rs58
-rw-r--r--src/transport/client.rs0
-rw-r--r--src/transport/mod.rs11
-rw-r--r--src/transport/tcp.rs58
12 files changed, 447 insertions, 17 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f391134..68e3129 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -62,7 +62,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
dependencies = [
- "windows-sys",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -72,7 +72,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
- "windows-sys",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -82,6 +82,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -103,6 +109,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
name = "cc"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -161,6 +179,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -179,6 +203,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -200,6 +234,27 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
name = "object"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -209,6 +264,29 @@ dependencies = [
]
[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -270,6 +348,15 @@ dependencies = [
]
[[package]]
+name = "redox_syscall"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -305,6 +392,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "sip"
version = "0.1.0"
dependencies = [
@@ -317,6 +419,22 @@ dependencies = [
]
[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "syn"
version = "2.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -334,7 +452,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "parking_lot",
"pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
@@ -357,11 +495,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
- "windows-targets",
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
]
[[package]]
@@ -370,30 +532,48 @@ version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
]
[[package]]
name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
@@ -406,24 +586,48 @@ checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/Cargo.toml b/Cargo.toml
index 8e4b3ab..e9dcc23 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-tokio = "1.38.0"
+tokio = { version = "1.38.0", features = ["full"] }
anyhow = "1.0.86"
log = "0.4.22"
rand = "0.9.0-alpha.1"
diff --git a/src/encoding/headermap.rs b/src/encoding/headermap.rs
index 313d8b5..2f9f097 100644
--- a/src/encoding/headermap.rs
+++ b/src/encoding/headermap.rs
@@ -13,11 +13,17 @@ impl HeaderMap {
self.0.push((H::NAME.to_string(), format!("{h}")));
self
}
- pub fn get<H: Header>(&self) -> Option<Result<H>> {
+ pub fn insert<H: Header>(&mut self, h: H) {
+ self.0.push((H::NAME.to_string(), format!("{h}")));
+ }
+ pub fn get_raw(&self, name: &str) -> Option<&str> {
self.0
.iter()
- .find(|(k, _)| k == H::NAME)
- .map(|(_, v)| H::from_str(v))
+ .find(|(k, _)| k.eq_ignore_ascii_case(name))
+ .map(|(_, v)| v.as_str())
+ }
+ pub fn get<H: Header>(&self) -> Option<Result<H>> {
+ self.get_raw(H::NAME).map(H::from_str)
}
pub fn insert_raw(&mut self, key: String, value: String) {
self.0.push((key, value))
@@ -32,3 +38,9 @@ impl Display for HeaderMap {
Ok(())
}
}
+
+impl FromIterator<(String, String)> for HeaderMap {
+ fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
+ Self(Vec::from_iter(iter))
+ }
+}
diff --git a/src/encoding/headers.rs b/src/encoding/headers.rs
index d837ee5..6bffc7e 100644
--- a/src/encoding/headers.rs
+++ b/src/encoding/headers.rs
@@ -1,7 +1,10 @@
+use super::{headermap::HeaderMap, method::Method};
+use anyhow::{anyhow, bail};
use std::{fmt::Display, str::FromStr};
macro_rules! header {
($hname:literal, struct $name:ident($type:ty)) => {
+ #[derive(Debug)]
pub struct $name(pub $type);
impl Header for $name {
const NAME: &'static str = $hname;
@@ -26,7 +29,6 @@ pub trait Header: FromStr<Err = anyhow::Error> + Display {
header!("Content-Length", struct ContentLength(usize));
header!("Call-ID", struct CallID(String));
-header!("CSeq", struct CSeq(String));
header!("Via", struct Via(String));
header!("Contact", struct Contact(String));
header!("Max-Forwards", struct MaxForwards(usize));
@@ -34,3 +36,70 @@ header!("From", struct From(String));
header!("To", struct To(String));
header!("User-Agent", struct UserAgent(String));
header!("Allow", struct Allow(String));
+
+#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
+pub struct CSeq(pub u32, pub Method);
+
+impl Header for CSeq {
+ const NAME: &'static str = "CSeq";
+}
+impl Display for CSeq {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{} {}", self.0, self.1)
+ }
+}
+impl FromStr for CSeq {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let (seq, method) = s.split_once(" ").ok_or(anyhow!("method missing"))?;
+ Ok(CSeq(seq.parse()?, method.parse()?))
+ }
+}
+
+#[derive(Debug)]
+pub struct WWWAuthenticate {
+ pub realm: String,
+ pub nonce: String,
+}
+impl Header for WWWAuthenticate {
+ const NAME: &'static str = "WWW-Authenticate";
+}
+impl Display for WWWAuthenticate {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Digest realm={:?}, nonce={:?}", self.realm, self.nonce)
+ }
+}
+impl FromStr for WWWAuthenticate {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ // TODO this is totally wrong
+ let kvs = s
+ .strip_prefix("Digest ")
+ .ok_or(anyhow!("type not digest"))?
+ .split(", ")
+ .map(|e| {
+ let Some((k, v)) = e.split_once("=") else {
+ bail!("not a KV-pair")
+ };
+ Ok((
+ k.to_string(),
+ v.strip_prefix("\"")
+ .ok_or(anyhow!("start quote missing"))?
+ .strip_suffix("\"")
+ .ok_or(anyhow!("end quote missing"))?
+ .to_string(),
+ ))
+ })
+ .try_collect::<HeaderMap>()?;
+ Ok(WWWAuthenticate {
+ realm: kvs
+ .get_raw("realm")
+ .ok_or(anyhow!("realm missing"))?
+ .to_string(),
+ nonce: kvs
+ .get_raw("nonce")
+ .ok_or(anyhow!("nonce missing"))?
+ .to_string(),
+ })
+ }
+}
diff --git a/src/encoding/method.rs b/src/encoding/method.rs
index 5f8110a..73829b0 100644
--- a/src/encoding/method.rs
+++ b/src/encoding/method.rs
@@ -1,5 +1,7 @@
-use std::fmt::Display;
+use anyhow::bail;
+use std::{fmt::Display, str::FromStr};
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Method {
Register,
Invite,
@@ -21,3 +23,17 @@ impl Display for Method {
})
}
}
+impl FromStr for Method {
+ type Err = anyhow::Error;
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "REGISTER" => Method::Register,
+ "INVITE" => Method::Invite,
+ "ACK" => Method::Ack,
+ "OPTION" => Method::Option,
+ "CANCEL" => Method::Cancel,
+ "BYE" => Method::Bye,
+ _ => bail!("unknown method"),
+ })
+ }
+}
diff --git a/src/encoding/request.rs b/src/encoding/request.rs
index 03b93c9..124522c 100644
--- a/src/encoding/request.rs
+++ b/src/encoding/request.rs
@@ -1,6 +1,7 @@
use super::{headermap::HeaderMap, method::Method, uri::Uri};
use std::fmt::Display;
+#[derive(Debug)]
pub struct Request {
pub method: Method,
pub uri: Uri,
diff --git a/src/encoding/uri.rs b/src/encoding/uri.rs
index 2d77df2..64572ba 100644
--- a/src/encoding/uri.rs
+++ b/src/encoding/uri.rs
@@ -1,5 +1,6 @@
use std::fmt::Display;
+#[derive(Debug)]
pub struct Uri {
pub content: String,
}
diff --git a/src/lib.rs b/src/lib.rs
index d9935d3..6c6cc3b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,2 +1,4 @@
+#![feature(iterator_try_collect)]
pub mod encoding;
pub mod transport;
+pub mod transaction;
diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs
new file mode 100644
index 0000000..2953cb7
--- /dev/null
+++ b/src/transaction/mod.rs
@@ -0,0 +1,58 @@
+use crate::{
+ encoding::{headers::CSeq, request::Request, response::Response},
+ transport::Transport,
+};
+use anyhow::{anyhow, Result};
+use std::{
+ collections::HashMap,
+ sync::atomic::{AtomicU32, Ordering},
+};
+use tokio::sync::{
+ mpsc::{self, channel},
+ RwLock,
+};
+
+pub struct TransactionUser<T> {
+ transport: T,
+ sequence: AtomicU32,
+ pending_requests: RwLock<HashMap<CSeq, mpsc::Sender<Response>>>,
+}
+
+impl<T: Transport> TransactionUser<T> {
+ pub fn new(transport: T) -> Self {
+ Self {
+ sequence: 0.into(),
+ pending_requests: Default::default(),
+ transport,
+ }
+ }
+
+ pub async fn process_responses(&self) -> Result<()> {
+ let resp = self.transport.recv().await?;
+ let cseq = resp
+ .headers
+ .get()
+ .ok_or(anyhow!("response cseq missing"))??;
+ self.pending_requests
+ .write()
+ .await
+ .get_mut(&cseq)
+ .ok_or(anyhow!("message was not requested"))?
+ .send(resp)
+ .await?;
+ Ok(())
+ }
+
+ pub async fn transact(&self, mut request: Request) -> Result<mpsc::Receiver<Response>> {
+ let seq = self.sequence.fetch_add(1, Ordering::Relaxed);
+ let cseq = CSeq(seq, request.method);
+ request.headers.insert(cseq);
+
+ let (tx, rx) = channel(4);
+
+ self.transport.send(request).await?;
+ self.pending_requests.write().await.insert(cseq, tx);
+
+ Ok(rx)
+ }
+}
diff --git a/src/transport/client.rs b/src/transport/client.rs
deleted file mode 100644
index e69de29..0000000
--- a/src/transport/client.rs
+++ /dev/null
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index b9babe5..3fa82df 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -1 +1,10 @@
-pub mod client;
+use crate::encoding::{request::Request, response::Response};
+use anyhow::Result;
+
+pub mod tcp;
+
+#[allow(async_fn_in_trait)]
+pub trait Transport {
+ async fn recv(&self) -> Result<Response>;
+ async fn send(&self, request: Request) -> Result<()>;
+}
diff --git a/src/transport/tcp.rs b/src/transport/tcp.rs
new file mode 100644
index 0000000..e196db3
--- /dev/null
+++ b/src/transport/tcp.rs
@@ -0,0 +1,58 @@
+use crate::encoding::{request::Request, response::Response};
+use anyhow::{anyhow, Result};
+use log::debug;
+use std::str::FromStr;
+use tokio::{
+ io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
+ net::{tcp::OwnedWriteHalf, TcpStream},
+ sync::{
+ mpsc::{channel, Receiver},
+ Mutex,
+ },
+};
+
+use super::Transport;
+
+pub struct TcpTransport {
+ write: Mutex<BufWriter<OwnedWriteHalf>>,
+ read: Mutex<Receiver<Response>>,
+}
+
+impl TcpTransport {
+ pub async fn new(stream: TcpStream) -> Result<Self> {
+ let (read, write) = stream.into_split();
+
+ let (tx, rx) = channel(16);
+
+ tokio::task::spawn(async move {
+ let mut sock = BufReader::new(read);
+ let mut message = String::new();
+ loop {
+ while !message.ends_with("\r\n\r\n") {
+ sock.read_line(&mut message).await.unwrap();
+ }
+ let mesg = Response::from_str(message.trim()).unwrap();
+ debug!("<- {mesg:?}");
+ tx.send(mesg).await.unwrap();
+ message.clear();
+ }
+ });
+
+ Ok(Self {
+ write: BufWriter::new(write).into(),
+ read: rx.into(),
+ })
+ }
+}
+impl Transport for TcpTransport {
+ async fn recv(&self) -> Result<Response> {
+ self.read.lock().await.recv().await.ok_or(anyhow!("end"))
+ }
+ async fn send(&self, request: Request) -> Result<()> {
+ debug!("-> {request:?}");
+ let mut g = self.write.lock().await;
+ g.write_all(format!("{request}").as_bytes()).await?;
+ g.flush().await?;
+ Ok(())
+ }
+}