diff options
Diffstat (limited to 'sip')
-rw-r--r-- | sip/Cargo.toml | 19 | ||||
-rw-r--r-- | sip/examples/Cargo.lock | 687 | ||||
-rw-r--r-- | sip/examples/server.rs | 83 | ||||
-rw-r--r-- | sip/src/encoding/headermap.rs | 60 | ||||
-rw-r--r-- | sip/src/encoding/headers.rs | 182 | ||||
-rw-r--r-- | sip/src/encoding/method.rs | 39 | ||||
-rw-r--r-- | sip/src/encoding/mod.rs | 46 | ||||
-rw-r--r-- | sip/src/encoding/request.rs | 57 | ||||
-rw-r--r-- | sip/src/encoding/response.rs | 53 | ||||
-rw-r--r-- | sip/src/encoding/status.rs | 63 | ||||
-rw-r--r-- | sip/src/encoding/uri.rs | 55 | ||||
-rw-r--r-- | sip/src/lib.rs | 4 | ||||
-rw-r--r-- | sip/src/transaction/auth.rs | 51 | ||||
-rw-r--r-- | sip/src/transaction/mod.rs | 87 | ||||
-rw-r--r-- | sip/src/transport/mod.rs | 11 | ||||
-rw-r--r-- | sip/src/transport/tcp.rs | 48 | ||||
-rw-r--r-- | sip/src/transport/udp.rs | 35 |
17 files changed, 1580 insertions, 0 deletions
diff --git a/sip/Cargo.toml b/sip/Cargo.toml new file mode 100644 index 0000000..c91634b --- /dev/null +++ b/sip/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "sip" +version = "0.1.0" +edition = "2021" + +[dependencies] +tokio = { version = "1.38.0", features = ["full"] } +anyhow = "1.0.86" +log = "0.4.22" +rand = "0.9.0-alpha.1" +base64 = "0.22.1" +md5 = "0.7.0" +hex = "0.4.3" + +sdp = { workspace = true } +rtp = { workspace = true } + +[dev-dependencies] +env_logger = "0.11.3" diff --git a/sip/examples/Cargo.lock b/sip/examples/Cargo.lock new file mode 100644 index 0000000..9d21de3 --- /dev/null +++ b/sip/examples/Cargo.lock @@ -0,0 +1,687 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +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" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +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" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +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 = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + +[[package]] +name = "libc" +version = "0.2.155" +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" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[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" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + +[[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 1.13.2", + "windows-targets 0.52.5", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d31e63ea85be51c423e52ba8f2e68a3efd53eed30203ee029dd09947333693e" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78674ef918c19451dbd250f8201f8619b494f64c9aa6f3adb28fd8a0f1f6da46" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc89dffba8377c5ec847d12bb41492bda235dba31a25e8b695cd0fe6589eb8c9" +dependencies = [ + "getrandom", + "zerocopy", +] + +[[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" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rtp" +version = "0.1.0" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +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 = "sdp" +version = "0.1.0" +dependencies = [ + "anyhow", +] + +[[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 = [ + "anyhow", + "base64", + "env_logger", + "hex", + "log", + "md5", + "rand", + "rtp", + "sdp", + "smallvec 2.0.0-alpha.7", + "tokio", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smallvec" +version = "2.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a96cb564e12be8458b004bd829787ebe887de197d94516d2ba5a2a32235a0c" + +[[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" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tokio" +version = "1.38.0" +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]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +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 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]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "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" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" + +[[package]] +name = "zerocopy" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db678a6ee512bd06adf35c35be471cae2f9c82a5aed2b5d15e03628c98bddd57" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201585ea96d37ee69f2ac769925ca57160cef31acb137c16f38b02b76f4c1e62" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/sip/examples/server.rs b/sip/examples/server.rs new file mode 100644 index 0000000..02c06b4 --- /dev/null +++ b/sip/examples/server.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use log::{info, warn}; +use sip::{ + encoding::{ + headermap::HeaderMap, + headers::{Contact, From, To, UserAgent, Via}, + method::Method, + response::Response, + status::Status, + }, + transaction::TransactionUser, + transport::tcp::TcpTransport, +}; +use std::net::SocketAddr; +use tokio::{ + net::{TcpListener, TcpStream}, + spawn, +}; + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init_from_env("LOG"); + let listener = TcpListener::bind("0.0.0.0:5060").await?; + info!("tcp listener bound to {}", listener.local_addr().unwrap()); + + loop { + let (stream, addr) = listener.accept().await?; + info!("connect {addr}"); + + spawn(async move { + if let Err(e) = handle_client(stream, addr).await { + warn!("client error: {e}") + } + info!("disconnect {addr}") + }); + } +} + +async fn handle_client(stream: TcpStream, addr: SocketAddr) -> Result<()> { + let transport = TcpTransport::new(stream).await?; + let tu = TransactionUser::new(transport); + loop { + let req = tu.process_incoming().await?; + + if req.method == Method::Register { + let from: From = req.headers.get_res()?; + let to: To = req.headers.get_res()?; + let via: Via = req.headers.get_res()?; + let contact: Contact = req.headers.get_res()?; + info!("Registered {}", contact.uri.localpart.as_ref().unwrap()); + + tu.respond( + &req, + Response { + status: Status::Ok, + headers: HeaderMap::new() + .add(contact) + .add(via) + .add(to) + .add(from) + .add(UserAgent("siptest v0.1.0".to_string())), + body: String::new(), + }, + ) + .await?; + } + if req.method == Method::Invite {} + } +} + +/* +[2024-07-05T22:22:40Z DEBUG sip::transport::udp] SIP/2.0 200 OK + Via: SIP/2.0/UDP 198.18.1.135:52125;branch=Uz7r7ysvrS91q9j9;rport=52125 + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:40708>;expires=379;+sip.instance="<urn:uuid:9b2421ef-43ed-3b3b-9181-607ba5c13fb9>" + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:47412>;expires=411 + Contact: <sip:metatest-0x5e11f89f7900@198.18.1.135:52125>;expires=600 + To: <sip:metatest@198.18.0.220>;tag=81756f4e + From: <sip:metatest@198.18.0.220>;tag=-AK4OkphTFVZv50h + Call-ID: U3Fyb6vT1BhWxiKG + CSeq: 1 REGISTER + User-Agent: AGFEO SIP V3.00.15 n (MAC=00094070069C) + Content-Length: 0 +*/ diff --git a/sip/src/encoding/headermap.rs b/sip/src/encoding/headermap.rs new file mode 100644 index 0000000..01e1962 --- /dev/null +++ b/sip/src/encoding/headermap.rs @@ -0,0 +1,60 @@ +use super::headers::Header; +use anyhow::{anyhow, Result}; +use std::fmt::Display; + +#[derive(Debug, Clone)] +pub struct HeaderMap(pub Vec<(String, String)>); + +impl HeaderMap { + pub fn new() -> Self { + Self(vec![]) + } + pub fn add<H: Header>(mut self, h: H) -> Self { + self.0.push((H::NAME.to_string(), format!("{h}"))); + self + } + 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.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 get_res<H: Header>(&self) -> Result<H> { + self.get().ok_or(anyhow!("{} header missing", H::NAME))? + } + pub fn insert_raw(&mut self, key: String, value: String) { + self.0.push((key, value)) + } +} + +impl Display for HeaderMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for (k, v) in &self.0 { + write!(f, "{k}: {v}\r\n")?; + } + Ok(()) + } +} +impl HeaderMap { + pub fn parse<'a>(lines: &mut impl Iterator<Item = &'a str>) -> Result<Self> { + let mut headers = HeaderMap::new(); + for line in lines { + // TODO multiline values + let (key, value) = line.split_once(":").ok_or(anyhow!("header malformed"))?; + headers.insert_raw(key.trim().to_string(), value.trim().to_string()) + } + Ok(headers) + } +} + +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/sip/src/encoding/headers.rs b/sip/src/encoding/headers.rs new file mode 100644 index 0000000..e880739 --- /dev/null +++ b/sip/src/encoding/headers.rs @@ -0,0 +1,182 @@ +use super::{headermap::HeaderMap, method::Method, uri::Uri}; +use anyhow::{anyhow, bail, Result}; +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; + } + impl Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } + } + impl FromStr for $name { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok($name(<$type>::from_str(s)?)) + } + } + }; +} + +pub trait Header: FromStr<Err = anyhow::Error> + Display { + const NAME: &'static str; +} + +header!("Content-Length", struct ContentLength(usize)); +header!("Content-Type", struct ContentType(String)); +header!("Call-ID", struct CallID(String)); +header!("Via", struct Via(String)); +header!("Max-Forwards", struct MaxForwards(usize)); +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.trim().to_string(), v.trim().to_string())) + }) + .try_collect::<HeaderMap>()?; + Ok(WWWAuthenticate { + realm: unquote( + &kvs.get_raw("realm") + .ok_or(anyhow!("realm missing"))? + .to_string(), + )?, + nonce: unquote( + &kvs.get_raw("nonce") + .ok_or(anyhow!("nonce missing"))? + .to_string(), + )?, + }) + } +} + +#[derive(Debug, Clone)] +pub struct Authorization { + pub username: String, + pub realm: String, + pub uri: String, + pub nonce: String, + pub response: String, +} +impl Header for Authorization { + const NAME: &'static str = "Authorization"; +} +impl Display for Authorization { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + username, + realm, + nonce, + uri, + response, + } = self; + write!( + f, + "Digest username={username:?},\r\n realm={realm:?},\r\n nonce={nonce:?},\r\n uri={uri:?},\r\n response={response:?},\r\n algorithm=MD5" + ) + } +} +impl FromStr for Authorization { + type Err = anyhow::Error; + fn from_str(_s: &str) -> Result<Self, Self::Err> { + todo!() + } +} + +pub fn unquote(v: &str) -> Result<String> { + Ok(v.strip_prefix("\"") + .ok_or(anyhow!("start quote missing"))? + .strip_suffix("\"") + .ok_or(anyhow!("end quote missing"))? + .to_string()) +} + +#[derive(Debug)] +pub struct Contact { + pub display_name: Option<String>, + pub uri: Uri, + pub params: String, +} + +impl Header for Contact { + const NAME: &'static str = "Contact"; +} +impl Display for Contact { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + display_name, + uri, + params, + } = self; + if let Some(display_name) = display_name { + write!(f, "{display_name} <{uri}>{params}") + } else { + write!(f, "<{uri}>{params}") + } + } +} +impl FromStr for Contact { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (display_name, rest) = s.split_once("<").ok_or(anyhow!("malformed contact"))?; + let (uri, params) = rest.split_once(">;").ok_or(anyhow!("malformed contact"))?; + Ok(Self { + display_name: if display_name.is_empty() { + None + } else { + Some(display_name.to_string()) + }, + params: params.to_string(), + uri: Uri::from_str(uri)?, + }) + } +} diff --git a/sip/src/encoding/method.rs b/sip/src/encoding/method.rs new file mode 100644 index 0000000..6d38cab --- /dev/null +++ b/sip/src/encoding/method.rs @@ -0,0 +1,39 @@ +use anyhow::bail; +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Method { + Register, + Invite, + Ack, + Options, + Cancel, + Bye, +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Method::Register => "REGISTER", + Method::Invite => "INVITE", + Method::Ack => "ACK", + Method::Options => "OPTIONS", + Method::Cancel => "CANCEL", + Method::Bye => "BYE", + }) + } +} +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, + "OPTIONS" => Method::Options, + "CANCEL" => Method::Cancel, + "BYE" => Method::Bye, + _ => bail!("unknown method"), + }) + } +} diff --git a/sip/src/encoding/mod.rs b/sip/src/encoding/mod.rs new file mode 100644 index 0000000..816aa01 --- /dev/null +++ b/sip/src/encoding/mod.rs @@ -0,0 +1,46 @@ +use std::{fmt::Display, str::FromStr}; + +use request::Request; +use response::Response; + +pub mod headermap; +pub mod headers; +pub mod method; +pub mod request; +pub mod response; +pub mod status; +pub mod uri; + +#[derive(Debug, Clone)] +pub enum Message { + Request(Request), + Response(Response), +} + +impl Display for Message { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Message::Request(r) => write!(f, "{r}"), + Message::Response(r) => write!(f, "{r}"), + } + } +} +impl FromStr for Message { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + if s.starts_with("SIP/") { + Response::from_str(s).map(Message::Response) + } else { + Request::from_str(s).map(Message::Request) + } + } +} + +impl Message { + pub fn body_mut(&mut self) -> &mut String { + match self { + Message::Request(r) => &mut r.body, + Message::Response(r) => &mut r.body, + } + } +} diff --git a/sip/src/encoding/request.rs b/sip/src/encoding/request.rs new file mode 100644 index 0000000..ab41b7c --- /dev/null +++ b/sip/src/encoding/request.rs @@ -0,0 +1,57 @@ +use super::{headermap::HeaderMap, method::Method, uri::Uri}; +use anyhow::{anyhow, bail}; +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug, Clone)] +pub struct Request { + pub method: Method, + pub uri: Uri, + pub headers: HeaderMap, + pub body: String, +} + +impl Display for Request { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + headers, + method, + uri, + .. + } = self; + write!(f, "{method} {uri} SIP/2.0\r\n")?; + write!(f, "{headers}\r\n")?; + Ok(()) + } +} +impl FromStr for Request { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut lines = s.lines(); + let statusline = lines.next().ok_or(anyhow!("status line missing"))?; + let (method, rest) = statusline + .split_once(" ") + .ok_or(anyhow!("status line malformed"))?; + let (uri, sipver) = rest + .split_once(" ") + .ok_or(anyhow!("status line malformed"))?; + + let Some(ver) = sipver.strip_prefix("SIP/") else { + bail!("sip version malformed"); + }; + if ver != "2.0" { + bail!("sip version {ver:?} is not supported"); + } + + let uri = Uri::from_str(uri)?; + + let headers = HeaderMap::parse(&mut lines)?; + let method = Method::from_str(method)?; + + Ok(Self { + body: String::new(), + headers, + method, + uri, + }) + } +} diff --git a/sip/src/encoding/response.rs b/sip/src/encoding/response.rs new file mode 100644 index 0000000..0b7996c --- /dev/null +++ b/sip/src/encoding/response.rs @@ -0,0 +1,53 @@ +use super::{headermap::HeaderMap, status::Status}; +use anyhow::{anyhow, bail, Context}; +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug, Clone)] +pub struct Response { + pub status: Status, + pub headers: HeaderMap, + pub body: String, +} + +impl FromStr for Response { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut lines = s.lines(); + let statusline = lines.next().ok_or(anyhow!("status line missing"))?; + let (sipver, rest) = statusline + .split_once(" ") + .ok_or(anyhow!("status line malformed"))?; + let (code, _status_str) = rest + .split_once(" ") + .ok_or(anyhow!("status line malformed"))?; + let code = u16::from_str(code).context("status code")?; + + let Some(ver) = sipver.strip_prefix("SIP/") else { + bail!("sip version malformed"); + }; + if ver != "2.0" { + bail!("sip version {ver:?} is not supported"); + } + + let headers = HeaderMap::parse(&mut lines)?; + + let status = Status::from_code(code); + Ok(Self { + status, + headers, + body: String::new(), + }) + } +} +impl Display for Response { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + status, + headers, + body, + } = self; + write!(f, "SIP/2.0 {} {status:?}\r\n", status.to_code())?; + write!(f, "{headers}\r\n{body}")?; + Ok(()) + } +} diff --git a/sip/src/encoding/status.rs b/sip/src/encoding/status.rs new file mode 100644 index 0000000..61b2d2a --- /dev/null +++ b/sip/src/encoding/status.rs @@ -0,0 +1,63 @@ +macro_rules! status_enum { + ($v:vis enum $name:ident { $($variant:ident = $value:literal),*, }) => { + #[derive(Debug, Clone, Eq, PartialEq, Hash)] + $v enum $name { $($variant),*, Other(u16) } + impl $name { pub fn from_code(c: u16) -> Self { match c { $($value => Self::$variant),*, x => Self::Other(x) } } } + impl $name { pub fn to_code(&self) -> u16 { match self { $(Self::$variant => $value),*, Self::Other(x) => *x } } } + }; +} + +status_enum!( + pub enum Status { + Trying = 100, + Ringing = 180, + CallIsBeingForwarded = 181, + Queued = 182, + SessionProgress = 183, + Ok = 200, + MultipleChoices = 300, + MovedPermanently = 301, + MovedTemporarily = 302, + UseProxy = 305, + AlternativeService = 380, + BadRequest = 400, + Unauthorized = 401, + PaymentRequired = 402, + Forbidden = 403, + NotFound = 404, + MethodNotAllowed = 405, + NotAcceptable = 406, + ProxyAuthenticationRequired = 407, + RequestTimeout = 408, + Gone = 410, + RequestEntityTooLarge = 413, + RequestURITooLarge = 414, + UnsupportedMediaType = 415, + UnsupportedURIScheme = 416, + BadExtension = 420, + ExtensionRequired = 421, + IntervalTooBrief = 423, + TemporarilyNotAvailable = 480, + CallLegTransactionDoesNotExist = 481, + LoopDetected = 482, + TooManyHops = 483, + AddressIncomplete = 484, + Ambiguous = 485, + BusyHere = 486, + RequestTerminated = 487, + NotAcceptableHere = 488, + RequestPending = 491, + Undecipherable = 493, + InternalServerError = 500, + NotImplemented = 501, + BadGateway = 502, + ServiceUnavailable = 503, + ServerTimeout = 504, + SIPVersionNotSupported = 505, + MessageTooLarge = 513, + BusyEverywhere = 600, + Decline = 603, + DoesNotExistAnywhere = 604, + GlobalNotAcceptable = 606, + } +); diff --git a/sip/src/encoding/uri.rs b/sip/src/encoding/uri.rs new file mode 100644 index 0000000..b1a1282 --- /dev/null +++ b/sip/src/encoding/uri.rs @@ -0,0 +1,55 @@ +use anyhow::anyhow; +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug, Clone)] +pub struct Uri { + pub protocol: String, + pub localpart: Option<String>, + pub addr: String, + pub params: String, +} + +impl Display for Uri { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + protocol, + localpart, + addr, + params, + } = self; + write!( + f, + "{protocol}:{}{addr}{}", + if let Some(localpart) = localpart { + format!("{localpart}@") + } else { + "".to_string() + }, + if params.is_empty() { + "".to_string() + } else { + format!(";{params}") + } + )?; + Ok(()) + } +} +impl FromStr for Uri { + type Err = anyhow::Error; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let (pr, s) = s.split_once(":").ok_or(anyhow!("protocol sep"))?; + let (lp, s) = s.split_once("@").unwrap_or(("", s)); + let (addr, params) = s.split_once(";").unwrap_or((s, "")); + + Ok(Self { + addr: addr.to_owned(), + localpart: if lp.is_empty() { + None + } else { + Some(lp.to_string()) + }, + params: params.to_string(), + protocol: pr.to_string(), + }) + } +} diff --git a/sip/src/lib.rs b/sip/src/lib.rs new file mode 100644 index 0000000..6c6cc3b --- /dev/null +++ b/sip/src/lib.rs @@ -0,0 +1,4 @@ +#![feature(iterator_try_collect)] +pub mod encoding; +pub mod transport; +pub mod transaction; diff --git a/sip/src/transaction/auth.rs b/sip/src/transaction/auth.rs new file mode 100644 index 0000000..4c46f9b --- /dev/null +++ b/sip/src/transaction/auth.rs @@ -0,0 +1,51 @@ +use crate::encoding::{ + headers::{Authorization, WWWAuthenticate}, + method::Method, + request::Request, + response::Response, + uri::Uri, +}; +use anyhow::Result; + +impl Authorization { + pub fn construct( + request: &Request, + failed_response: &Response, + username: &str, + password: &str, + ) -> Result<Authorization> { + let challenge = failed_response.headers.get_res::<WWWAuthenticate>()?; + + Ok(Authorization { + response: response_digest( + username.to_string(), + challenge.realm.clone(), + password.to_string(), + request.method, + challenge.nonce.clone(), + request.uri.clone(), + ), + nonce: challenge.nonce, + realm: challenge.realm, + uri: request.uri.to_string(), + username: username.to_string(), + }) + } +} + +fn response_digest( + username: String, + realm: String, + password: String, + method: Method, + nonce: String, + uri: Uri, +) -> String { + let h = |s: String| hex::encode(md5::compute(s.as_bytes()).0); + let kd = |secret, data| h(format!("{secret}:{data}")); + + let a1 = format!("{username}:{realm}:{password}"); + let a2 = format!("{method}:{uri}"); + let response_digest = kd(h(a1), format!("{nonce}:{}", h(a2))); + return response_digest; +} diff --git a/sip/src/transaction/mod.rs b/sip/src/transaction/mod.rs new file mode 100644 index 0000000..3368c47 --- /dev/null +++ b/sip/src/transaction/mod.rs @@ -0,0 +1,87 @@ +pub mod auth; + +use crate::{ + encoding::{ + headers::{CSeq, CallID, ContentLength}, + request::Request, + response::Response, + Message, + }, + 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_incoming(&self) -> Result<Request> { + loop { + let mesg = self.transport.recv().await?; + match mesg { + Message::Request(req) => break Ok(req), + Message::Response(resp) => { + 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?; + } + } + } + } + pub async fn respond(&self, req: &Request, mut resp: Response) -> Result<()> { + resp.headers.insert( + req.headers + .get::<CSeq>() + .ok_or(anyhow!("cseq is mandatory"))??, + ); + resp.headers.insert( + req.headers + .get::<CallID>() + .ok_or(anyhow!("call-id is mandatory"))??, + ); + resp.headers.insert(ContentLength(resp.body.len())); + self.transport.send(Message::Response(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); + request.headers.insert(ContentLength(request.body.len())); + + let (tx, rx) = channel(4); + + self.transport.send(Message::Request(request)).await?; + self.pending_requests.write().await.insert(cseq, tx); + + Ok(rx) + } +} diff --git a/sip/src/transport/mod.rs b/sip/src/transport/mod.rs new file mode 100644 index 0000000..b4c512b --- /dev/null +++ b/sip/src/transport/mod.rs @@ -0,0 +1,11 @@ +use crate::encoding::Message; +use anyhow::Result; + +pub mod tcp; +pub mod udp; + +#[allow(async_fn_in_trait)] +pub trait Transport { + async fn recv(&self) -> Result<Message>; + async fn send(&self, message: Message) -> Result<()>; +} diff --git a/sip/src/transport/tcp.rs b/sip/src/transport/tcp.rs new file mode 100644 index 0000000..efe433d --- /dev/null +++ b/sip/src/transport/tcp.rs @@ -0,0 +1,48 @@ +use super::Transport; +use crate::encoding::Message; +use anyhow::Result; +use log::debug; +use std::str::FromStr; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, + net::{ + tcp::{OwnedReadHalf, OwnedWriteHalf}, + TcpStream, + }, + sync::Mutex, +}; + +pub struct TcpTransport { + write: Mutex<BufWriter<OwnedWriteHalf>>, + read: Mutex<BufReader<OwnedReadHalf>>, +} + +impl TcpTransport { + pub async fn new(stream: TcpStream) -> Result<Self> { + let (read, write) = stream.into_split(); + Ok(Self { + write: BufWriter::new(write).into(), + read: BufReader::new(read).into(), + }) + } +} + +impl Transport for TcpTransport { + async fn recv(&self) -> Result<Message> { + let mut g = self.read.lock().await; + let mut message = String::new(); + while !message.ends_with("\r\n\r\n") { + g.read_line(&mut message).await?; + } + let mesg = Message::from_str(message.trim())?; + debug!("<- {mesg}"); + Ok(mesg) + } + async fn send(&self, request: Message) -> Result<()> { + debug!("-> {request}"); + let mut g = self.write.lock().await; + g.write_all(format!("{request}").as_bytes()).await?; + g.flush().await?; + Ok(()) + } +} diff --git a/sip/src/transport/udp.rs b/sip/src/transport/udp.rs new file mode 100644 index 0000000..c0d7829 --- /dev/null +++ b/sip/src/transport/udp.rs @@ -0,0 +1,35 @@ +use super::Transport; +use crate::encoding::Message; +use anyhow::{anyhow, Result}; +use log::debug; +use std::str::FromStr; +use tokio::net::UdpSocket; + +pub struct UdpTransport { + sock: UdpSocket, +} + +impl UdpTransport { + pub async fn new(sock: UdpSocket) -> Result<Self> { + Ok(Self { sock }) + } +} +impl Transport for UdpTransport { + async fn recv(&self) -> Result<Message> { + let mut buf = [0; 1024]; + let size = self.sock.recv(&mut buf).await?; + let message = String::from_utf8(buf[..size].to_vec())?; + let (head, body) = message + .split_once("\r\n\r\n") + .ok_or(anyhow!("header end missing"))?; + debug!("<- {head}\n\n{body}"); + let mut mesg = Message::from_str(head.trim_end())?; + *mesg.body_mut() = body.to_string(); + Ok(mesg) + } + async fn send(&self, request: Message) -> Result<()> { + debug!("-> {request}"); + self.sock.send(format!("{request}").as_bytes()).await?; + Ok(()) + } +} |