diff --git a/.github/workflows/tls_codec.yml b/.github/workflows/tls_codec.yml new file mode 100644 index 000000000..45de9080d --- /dev/null +++ b/.github/workflows/tls_codec.yml @@ -0,0 +1,73 @@ +name: tls_codec + +on: + pull_request: + paths: + - "tls_codec/**" + - "Cargo.*" + push: + branches: + - master + +defaults: + run: + working-directory: tls_codec + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.51.0 # MSRV + - stable + target: + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo build --target ${{ matrix.target }} --release + - run: cargo build --target ${{ matrix.target }} --release --features derive + - run: cargo build --target ${{ matrix.target }} --release --features serde_serialize + - run: cargo build --target ${{ matrix.target }} --release --all-features + + test: + runs-on: ubuntu-latest + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + rust: 1.51.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + - target: i686-unknown-linux-gnu + rust: stable + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + rust: 1.51.0 # MSRV + - target: x86_64-unknown-linux-gnu + rust: stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + profile: minimal + override: true + - run: ${{ matrix.deps }} + - run: cargo test --target ${{ matrix.target }} --release + - run: cargo test --target ${{ matrix.target }} --release --features derive + - run: cargo test --target ${{ matrix.target }} --release --features serde_serialize + - run: cargo test --target ${{ matrix.target }} --release --all-features diff --git a/Cargo.lock b/Cargo.lock index d56277f84..96b1ba04e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,10 +14,33 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "base64ct" version = "1.1.0" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.9.0" @@ -43,12 +66,39 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538" + [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cast" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" +dependencies = [ + "rustc_version", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -64,6 +114,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + [[package]] name = "const-oid" version = "0.6.1" @@ -80,6 +141,86 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crypto-bigint" version = "0.2.10" @@ -101,6 +242,28 @@ dependencies = [ "subtle", ] +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + [[package]] name = "der" version = "0.5.0-pre" @@ -141,6 +304,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "generic-array" version = "0.14.4" @@ -151,6 +320,21 @@ dependencies = [ "version_check", ] +[[package]] +name = "half" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hex-literal" version = "0.3.3" @@ -167,12 +351,91 @@ dependencies = [ "digest", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "js-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -237,6 +500,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "plotters" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" + +[[package]] +name = "plotters-svg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +dependencies = [ + "plotters-backend", +] + [[package]] name = "proc-macro2" version = "1.0.29" @@ -261,6 +552,67 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + [[package]] name = "salsa20" version = "0.8.1" @@ -270,6 +622,21 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "scrypt" version = "0.8.0" @@ -294,6 +661,53 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -359,12 +773,57 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tls_codec" +version = "0.1.5" +dependencies = [ + "criterion", + "serde", + "tls_codec_derive", + "zeroize", +] + +[[package]] +name = "tls_codec_derive" +version = "0.1.3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "tls_codec", +] + [[package]] name = "typenum" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode-xid" version = "0.2.2" @@ -377,6 +836,112 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" + +[[package]] +name = "web-sys" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "x509" version = "0.0.1" @@ -390,3 +955,18 @@ name = "zeroize" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index db82e499f..fa57c4f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,7 @@ members = [ "pkcs8", "sec1", "spki", + "tls_codec", + "tls_codec/derive", "x509" ] diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml new file mode 100644 index 000000000..67c047fe1 --- /dev/null +++ b/tls_codec/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "tls_codec" +version = "0.1.5" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +edition = "2018" +documentation = "https://docs.rs/tls_codec/" +description = "A pure Rust implementation of the TLS (de)serialization" +readme = "README.md" +repository = "https://github.com/RustCrypto/formats/tree/master/tls_codec" + +[dependencies] +zeroize = { version = "1.3", features = ["zeroize_derive"] } +tls_codec_derive = { version = "0.1", path = "derive", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } + +[dev-dependencies] +criterion = "0.3" + +[features] +derive = [ "tls_codec_derive" ] +serde_serialize = [ "serde" ] + +[[bench]] +name = "tls_vec" +harness = false diff --git a/tls_codec/LICENSE-APACHE b/tls_codec/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/tls_codec/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/tls_codec/LICENSE-MIT b/tls_codec/LICENSE-MIT new file mode 100644 index 000000000..2726e14a4 --- /dev/null +++ b/tls_codec/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/tls_codec/Readme.md b/tls_codec/Readme.md new file mode 100644 index 000000000..b1c3db5df --- /dev/null +++ b/tls_codec/Readme.md @@ -0,0 +1,51 @@ +# TLS Codec + +![MIT licensed][license-image] +[![Project Chat][chat-image]][chat-link] +[![][tls_codec-ci]][tls_codec-ci-link] + +| | crates.io | docs.rs | +| -------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ | +| [tls_codec](./tls_codec) | [![][tls_codec]][tls_codec-link] | [![Docs][tls_codec_docs]][tls_codec_docs-link] | +| [tls_codec_derive](./tls_codec_derive) | [![][tls_codec_derive]][tls_codec_derive-link] | [![Docs][tls_codec_derive_docs]][tls_codec_derive_docs-link] | + +This crate implements the TLS codec as defined in [RFC 8446] +as well as some extensions required by [MLS]. + +With the `derive` feature `TlsSerialize` and `TlsDeserialize` can be +derived. + +The crate also provides the following data structures that implement TLS +serialization/deserialization + +- `u8`, `u16`, `u32`, `u64` +- `TlsVecU8`, `TlsVecU16`, `TlsVecU32` +- `SecretTlsVecU8`, `SecretTlsVecU16`, `SecretTlsVecU32` + The same as the `TlsVec*` versions but it implements zeroize, requiring + the elements to implement zeroize as well. +- `TlsSliceU8`, `TlsSliceU16`, `TlsSliceU32` are lightweight wrapper for slices + that allow to serialize them without having to create a `TlsVec*`. +- `TlsByteSliceU8`, `TlsByteSliceU16`, `TlsByteSliceU32`, and + `TlsByteVecU8`, `TlsByteVecU16`, `TlsByteVecU32` + are provided with optimized implementations for byte vectors. +- `[u8; l]`, for `l ∈ [1..128]` +- Serialize for `Option` where `T: Serialize` +- Deserialize for `Option` where `T: Deserialize` +- Serialize for `(T, U)` and `(T, U, V)` where `T, U, V` implement Serialize` +- Deserialize for `(T, U)` and `(T, U, V)` where `T, U, V` implement Deserialize` + +[rfc 8446]: https://tools.ietf.org/html/rfc8446 +[mls]: https://messaginglayersecurity.rocks/mls-protocol/draft-ietf-mls-protocol.html +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg?style=for-the-badge +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats +[license-image]: https://img.shields.io/badge/license-Apache2.0-blue.svg?style=for-the-badge +[tls_codec-ci]: https://img.shields.io/github/workflow/status/RustCrypto/formats/tls_codec?style=for-the-badge +[tls_codec-ci-link]: https://github.com/RustCrypto/formats/actions/workflows/tls_codec.yml +[tls_codec]: https://img.shields.io/crates/v/tls_codec?style=for-the-badge +[tls_codec-link]: https://crates.io/crates/tls_codec +[tls_codec_docs]: https://img.shields.io/docsrs/tls_codec/latest?style=for-the-badge +[tls_codec_docs-link]: https://docs.rs/tls_codec/ +[tls_codec_derive]: https://img.shields.io/crates/v/tls_codec_derive?style=for-the-badge +[tls_codec_derive-link]: https://crates.io/crates/tls_codec_derive +[tls_codec_derive_docs]: https://img.shields.io/docsrs/tls_codec_derive/latest?style=for-the-badge +[tls_codec_derive_docs-link]: https://docs.rs/tls_codec_derive/ diff --git a/tls_codec/benches/tls_vec.rs b/tls_codec/benches/tls_vec.rs new file mode 100644 index 000000000..996a72c76 --- /dev/null +++ b/tls_codec/benches/tls_vec.rs @@ -0,0 +1,94 @@ +use criterion::{criterion_group, criterion_main}; +use criterion::{BatchSize, Criterion}; + +fn vector(c: &mut Criterion) { + use tls_codec::*; + c.bench_function("TLS Serialize Vector", |b| { + b.iter_batched( + || TlsVecU32::from(vec![77u8; 65535]), + |long_vector| { + let _serialized_long_vec = long_vector.tls_serialize_detached().unwrap(); + }, + BatchSize::SmallInput, + ) + }); + c.bench_function("TLS Deserialize Vector", |b| { + b.iter_batched( + || { + let long_vector = vec![77u8; 65535]; + TlsSliceU32(&long_vector).tls_serialize_detached().unwrap() + }, + |serialized_long_vec| { + let _deserialized_long_vec = + TlsVecU32::::tls_deserialize(&mut serialized_long_vec.as_slice()).unwrap(); + }, + BatchSize::SmallInput, + ) + }); +} + +fn byte_vector(c: &mut Criterion) { + use tls_codec::*; + c.bench_function("TLS Serialize Byte Vector", |b| { + b.iter_batched( + || TlsByteVecU32::from(vec![77u8; 65535]), + |long_vector| { + let _serialized_long_vec = long_vector.tls_serialize_detached().unwrap(); + }, + BatchSize::SmallInput, + ) + }); + c.bench_function("TLS Deserialize Byte Vector", |b| { + b.iter_batched( + || { + let long_vector = vec![77u8; 65535]; + TlsByteSliceU32(&long_vector) + .tls_serialize_detached() + .unwrap() + }, + |serialized_long_vec| { + let _deserialized_long_vec = + TlsVecU32::::tls_deserialize(&mut serialized_long_vec.as_slice()).unwrap(); + }, + BatchSize::SmallInput, + ) + }); +} + +fn byte_slice(c: &mut Criterion) { + use tls_codec::*; + c.bench_function("TLS Serialize Byte Slice", |b| { + b.iter_batched( + || vec![77u8; 65535], + |long_vector| { + let _serialized_long_vec = TlsByteSliceU32(&long_vector) + .tls_serialize_detached() + .unwrap(); + }, + BatchSize::SmallInput, + ) + }); +} + +fn slice(c: &mut Criterion) { + use tls_codec::*; + c.bench_function("TLS Serialize Slice", |b| { + b.iter_batched( + || vec![77u8; 65535], + |long_vector| { + let _serialized_long_vec = + TlsSliceU32(&long_vector).tls_serialize_detached().unwrap(); + }, + BatchSize::SmallInput, + ) + }); +} +fn benchmark(c: &mut Criterion) { + vector(c); + slice(c); + byte_vector(c); + byte_slice(c); +} + +criterion_group!(benches, benchmark); +criterion_main!(benches); diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml new file mode 100644 index 000000000..a17edc754 --- /dev/null +++ b/tls_codec/derive/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "tls_codec_derive" +version = "0.1.3" +authors = ["RustCrypto Developers"] +edition = "2018" +license = "Apache-2.0 OR MIT" +documentation = "https://docs.rs/tls_codec_derive/" +description = "Derive macros for the tls_codec trait" +readme = "../README.md" +repository = "https://github.com/RustCrypto/formats/tree/master/tls_codec/derive" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "1.0", features = ["parsing"] } +quote = "1.0" +proc-macro2 = "1.0" + +[dev-dependencies] +tls_codec = { path = "../" } diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs new file mode 100644 index 000000000..e2153621d --- /dev/null +++ b/tls_codec/derive/src/lib.rs @@ -0,0 +1,496 @@ +extern crate proc_macro; +extern crate proc_macro2; + +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn::{ + self, parenthesized, + parse::{ParseStream, Parser, Result}, + parse_macro_input, Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, Generics, Ident, + Index, +}; + +#[derive(Clone)] +struct Struct { + call_site: Span, + ident: Ident, + generics: Generics, + field_idents: Vec>, + field_paths: Vec, +} + +#[derive(Clone)] +struct TupleStruct { + call_site: Span, + ident: Ident, + generics: Generics, + field_indices: Vec, + field_paths: Vec, +} + +#[derive(Clone)] +struct Enum { + call_site: Span, + ident: Ident, + generics: Generics, + repr: Ident, + parsed_variants: Vec, + discriminants: Vec, + matched: Vec, +} + +#[derive(Clone)] +enum TlsStruct { + Struct(Struct), + TupleStruct(TupleStruct), + Enum(Enum), +} + +fn parse_ast(ast: DeriveInput) -> Result { + let call_site = Span::call_site(); + let ident = &ast.ident; + let generics = &ast.generics; + match ast.data { + Data::Struct(st) => match st.fields { + Fields::Named(FieldsNamed { named, .. }) => { + let field_idents: Vec> = + named.iter().map(|f| f.ident.clone()).collect(); + let paths = named.iter().map(|f| match f.ty.clone() { + syn::Type::Path(mut p) => { + let path = &mut p.path; + // Convert generic arguments in the path to const arguments. + path.segments.iter_mut().for_each(|mut p| { + if let syn::PathArguments::AngleBracketed(ab) = &mut p.arguments { + let mut ab = ab.clone(); + ab.colon2_token = Some(syn::token::Colon2::default()); + p.arguments = syn::PathArguments::AngleBracketed(ab); + } + }); + syn::Type::Path(p).to_token_stream() + } + syn::Type::Array(a) => { + quote! { <#a> } + } + #[allow(unused_variables)] + syn::Type::Reference(syn::TypeReference { + and_token, + lifetime, + mutability, + elem, + }) => { + // println!( + // "(Struct::Named) contains a type reference for field \"{}\"\nThis struct can not be deserialized", + // f.ident.clone().unwrap() + // ); + quote! {} + } + _ => panic!( + "(Struct::Named) Invalid field type for field \"{}\"", + f.ident.clone().unwrap() + ), + }); + let field_paths: Vec = paths.collect(); + Ok(TlsStruct::Struct(Struct { + call_site, + ident: ident.clone(), + generics: generics.clone(), + field_idents, + field_paths, + })) + } + #[allow(unused_variables)] + Fields::Unnamed(FieldsUnnamed { + paren_token, + unnamed, + }) => { + let iterator = unnamed.iter().enumerate(); + let field_indices: Vec = + iterator.map(|(i, _)| syn::Index::from(i)).collect(); + let paths = unnamed.iter().map(|f| match f.ty.clone() { + syn::Type::Path(mut p) => { + let path = &mut p.path; + // Convert generic arguments in the path to const arguments. + path.segments.iter_mut().for_each(|mut p| { + if let syn::PathArguments::AngleBracketed(ab) = &mut p.arguments { + let mut ab = ab.clone(); + ab.colon2_token = Some(syn::token::Colon2::default()); + p.arguments = syn::PathArguments::AngleBracketed(ab); + } + }); + syn::Type::Path(p).to_token_stream() + } + syn::Type::Array(a) => { + quote! { <#a> } + } + _ => panic!("(Struct::Unnamed) Invalid field type for {:?}", f.ident), + }); + + let field_paths: Vec = paths.collect(); + Ok(TlsStruct::TupleStruct(TupleStruct { + call_site, + ident: ident.clone(), + generics: generics.clone(), + field_indices, + field_paths, + })) + } + _ => unimplemented!(), + }, + // Enums. + // Note that they require a repr attribute. + Data::Enum(syn::DataEnum { variants, .. }) => { + let mut repr = None; + for attr in ast.attrs { + if attr.path.is_ident("repr") { + fn repr_arg(input: ParseStream) -> Result { + let content; + parenthesized!(content in input); + content.parse() + } + let ty = repr_arg.parse2(attr.tokens)?; + repr = Some(ty); + break; + } + } + let repr = + repr.ok_or_else(|| syn::Error::new(call_site, "missing #[repr(...)] attribute"))?; + let parsed_variants: Vec = variants + .iter() + .map(|variant| { + let variant = &variant.ident; + quote! { + #ident::#variant => #ident::#variant as #repr, + } + }) + .collect(); + + let discriminants: Vec = variants + .iter() + .map(|variant| { + let variant = &variant.ident; + quote! { + const #variant: #repr = #ident::#variant as #repr; + } + }) + .collect(); + + let matched: Vec = variants + .iter() + .map(|variant| { + let variant = &variant.ident; + quote! { + #variant => core::result::Result::Ok(#ident::#variant), + } + }) + .collect(); + + Ok(TlsStruct::Enum(Enum { + call_site, + ident: ident.clone(), + generics: generics.clone(), + repr, + parsed_variants, + discriminants, + matched, + })) + } + Data::Union(_) => unimplemented!(), + } +} + +#[proc_macro_derive(TlsSize)] +pub fn size_macro_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let parsed_ast = parse_ast(ast).unwrap(); + impl_tls_size(parsed_ast).into() +} + +#[proc_macro_derive(TlsSerialize)] +pub fn serialize_macro_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let parsed_ast = parse_ast(ast).unwrap(); + impl_serialize(parsed_ast).into() +} + +#[proc_macro_derive(TlsDeserialize)] +pub fn deserialize_macro_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let parsed_ast = parse_ast(ast).unwrap(); + impl_deserialize(parsed_ast).into() +} + +#[allow(unused_variables)] +fn impl_tls_size(parsed_ast: TlsStruct) -> TokenStream2 { + match parsed_ast { + TlsStruct::Struct(Struct { + call_site, + ident, + generics, + field_idents, + field_paths, + }) => { + quote! { + impl#generics tls_codec::Size for #ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + #(self.#field_idents.tls_serialized_len() + )* + 0 + } + } + + impl#generics tls_codec::Size for &#ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + #(self.#field_idents.tls_serialized_len() + )* + 0 + } + } + } + } + TlsStruct::TupleStruct(TupleStruct { + call_site, + ident, + generics, + field_indices, + field_paths, + }) => { + quote! { + impl#generics tls_codec::Size for #ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + #(self.#field_indices.tls_serialized_len() + )* + 0 + } + } + + impl#generics tls_codec::Size for &#ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + #(self.#field_indices.tls_serialized_len() + )* + 0 + } + } + } + } + TlsStruct::Enum(Enum { + call_site, + ident, + generics, + repr, + parsed_variants, + discriminants, + matched, + }) => { + quote! { + impl#generics tls_codec::Size for #ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + std::mem::size_of::<#repr>() + } + } + + impl#generics tls_codec::Size for &#ident#generics { + #[inline] + fn tls_serialized_len(&self) -> usize { + std::mem::size_of::<#repr>() + } + } + } + } + } +} + +#[allow(unused_variables)] +fn impl_serialize(parsed_ast: TlsStruct) -> TokenStream2 { + match parsed_ast { + TlsStruct::Struct(Struct { + call_site, + ident, + generics, + field_idents, + field_paths, + }) => { + quote! { + impl#generics tls_codec::Serialize for #ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let mut written = 0usize; + #( + written += self.#field_idents.tls_serialize(writer)?; + )* + if cfg!(debug_assertions) { + let expected_written = self.tls_serialized_len(); + debug_assert_eq!(written, expected_written, "Expected to serialize {} bytes but only {} were generated.", expected_written, written); + if written != expected_written { + Err(tls_codec::Error::EncodingError(format!("Expected to serialize {} bytes but only {} were generated.", expected_written, written))) + } else { + Ok(written) + } + } else { + Ok(written) + } + } + } + + impl#generics tls_codec::Serialize for &#ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let mut written = 0usize; + #(written += self.#field_idents.tls_serialize(writer)?;)* + if cfg!(debug_assertions) { + let expected_written = self.tls_serialized_len(); + debug_assert_eq!(written, expected_written, "Expected to serialize {} bytes but only {} were generated.", expected_written, written); + if written != expected_written { + Err(tls_codec::Error::EncodingError(format!("Expected to serialize {} bytes but only {} were generated.", expected_written, written))) + } else { + Ok(written) + } + } else { + Ok(written) + } + } + } + } + } + TlsStruct::TupleStruct(TupleStruct { + call_site, + ident, + generics, + field_indices, + field_paths, + }) => { + quote! { + impl#generics tls_codec::Serialize for #ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let mut written = 0usize; + #(written += self.#field_indices.tls_serialize(writer)?;)* + if cfg!(debug_assertions) { + let expected_written = self.tls_serialized_len(); + debug_assert_eq!(written, expected_written, "Expected to serialize {} bytes but only {} were generated.", expected_written, written); + if written != expected_written { + Err(tls_codec::Error::EncodingError(format!("Expected to serialize {} bytes but only {} were generated.", expected_written, written))) + } else { + Ok(written) + } + } else { + Ok(written) + } + } + } + + impl#generics tls_codec::Serialize for &#ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let mut written = 0usize; + #(written += self.#field_indices.tls_serialize(writer)?;)* + if cfg!(debug_assertions) { + let expected_written = self.tls_serialized_len(); + debug_assert_eq!(written, expected_written, "Expected to serialize {} bytes but only {} were generated.", expected_written, written); + if written != expected_written { + Err(tls_codec::Error::EncodingError(format!("Expected to serialize {} bytes but only {} were generated.", expected_written, written))) + } else { + Ok(written) + } + } else { + Ok(written) + } + } + } + } + } + TlsStruct::Enum(Enum { + call_site, + ident, + generics, + repr, + parsed_variants, + discriminants, + matched, + }) => { + quote! { + impl#generics tls_codec::Serialize for #ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let enum_value: #repr = match self { + #(#parsed_variants)* + }; + enum_value.tls_serialize(writer) + } + } + + impl#generics tls_codec::Serialize for &#ident#generics { + fn tls_serialize(&self, writer: &mut W) -> core::result::Result { + let enum_value: #repr = match self { + #(#parsed_variants)* + }; + enum_value.tls_serialize(writer) + } + } + } + } + } +} + +#[allow(unused_variables)] +fn impl_deserialize(parsed_ast: TlsStruct) -> TokenStream2 { + match parsed_ast { + TlsStruct::Struct(Struct { + call_site, + ident, + generics, + field_idents, + field_paths, + }) => { + quote! { + impl tls_codec::Deserialize for #ident { + fn tls_deserialize(bytes: &mut R) -> core::result::Result { + Ok(Self { + #(#field_idents: #field_paths::tls_deserialize(bytes)?,)* + }) + } + } + } + } + TlsStruct::TupleStruct(TupleStruct { + call_site, + ident, + generics, + field_indices, + field_paths, + }) => { + quote! { + impl tls_codec::Deserialize for #ident { + fn tls_deserialize(bytes: &mut R) -> core::result::Result { + Ok(Self( + #(#field_paths::tls_deserialize(bytes)?,)* + )) + } + } + } + } + TlsStruct::Enum(Enum { + call_site, + ident, + generics, + repr, + parsed_variants, + discriminants, + matched, + }) => { + quote! { + impl tls_codec::Deserialize for #ident { + #[allow(non_upper_case_globals)] + fn tls_deserialize(bytes: &mut R) -> core::result::Result { + #(#discriminants)* + + let value = #repr::tls_deserialize(bytes)?; + match value { + #(#matched)* + // XXX: This assumes non-exhaustive matches only. + _ => { + Err(tls_codec::Error::DecodingError(format!("Unmatched value {:?} in tls_deserialize", value))) + }, + } + } + } + } + } + } +} diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs new file mode 100644 index 000000000..efd9c67d7 --- /dev/null +++ b/tls_codec/derive/tests/decode.rs @@ -0,0 +1,222 @@ +use tls_codec::{Deserialize, Serialize, Size, TlsSliceU16, TlsVecU16, TlsVecU32, TlsVecU8}; +use tls_codec_derive::{TlsDeserialize, TlsSerialize, TlsSize}; + +#[derive(TlsDeserialize, Debug, PartialEq, Clone, Copy, TlsSize, TlsSerialize)] +#[repr(u16)] +pub enum ExtensionType { + Reserved = 0, + Capabilities = 1, + Lifetime = 2, + KeyId = 3, + ParentHash = 4, + RatchetTree = 5, + SomethingElse = 500, +} + +impl Default for ExtensionType { + fn default() -> Self { + Self::Reserved + } +} + +#[derive(TlsDeserialize, Debug, PartialEq, TlsSerialize, TlsSize, Clone, Default)] +pub struct ExtensionStruct { + extension_type: ExtensionType, + extension_data: TlsVecU32, +} + +#[derive(TlsDeserialize, Debug, PartialEq, TlsSize, TlsSerialize)] +pub struct ExtensionTypeVec { + data: TlsVecU8, +} + +#[derive(TlsDeserialize, Debug, PartialEq, TlsSize, TlsSerialize)] +pub struct ArrayWrap { + data: [u8; 8], +} + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Debug, PartialEq)] +pub struct TupleStruct1(ExtensionStruct); + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Debug, PartialEq)] +pub struct TupleStruct(ExtensionStruct, u8); + +#[test] +fn tuple_struct() { + let ext = ExtensionStruct { + extension_type: ExtensionType::KeyId, + extension_data: TlsVecU32::from_slice(&[1, 2, 3, 4, 5]), + }; + let t1 = TupleStruct1(ext.clone()); + let serialized_t1 = t1.tls_serialize_detached().unwrap(); + let deserialized_t1 = TupleStruct1::tls_deserialize(&mut serialized_t1.as_slice()).unwrap(); + assert_eq!(t1, deserialized_t1); + assert_eq!( + serialized_t1, + deserialized_t1.tls_serialize_detached().unwrap() + ); + + let t2 = TupleStruct(ext, 5); + let serialized_t2 = t2.tls_serialize_detached().unwrap(); + let deserialized_t2 = TupleStruct::tls_deserialize(&mut serialized_t2.as_slice()).unwrap(); + assert_eq!(t2, deserialized_t2); + assert_eq!( + serialized_t2, + deserialized_t2.tls_serialize_detached().unwrap() + ); +} + +#[test] +fn simple_enum() { + let mut b = &[0u8, 5] as &[u8]; + let deserialized = ExtensionType::tls_deserialize(&mut b).unwrap(); + assert_eq!(ExtensionType::RatchetTree, deserialized); + + let mut b = &[0u8, 5, 1, 244, 0, 1] as &[u8]; + let variants = [ + ExtensionType::RatchetTree, + ExtensionType::SomethingElse, + ExtensionType::Capabilities, + ]; + for variant in variants.iter() { + let deserialized = ExtensionType::tls_deserialize(&mut b).unwrap(); + assert_eq!(variant, &deserialized); + } +} + +#[test] +fn deserialize_tls_vec() { + let long_vector = vec![ExtensionStruct::default(); 3000]; + let serialized_long_vec = TlsSliceU16(&long_vector).tls_serialize_detached().unwrap(); + println!("ser len: {:?}", serialized_long_vec.len()); + println!("ser len: {:?}", &serialized_long_vec[0..2]); + let deserialized_long_vec: Vec = + TlsVecU16::tls_deserialize(&mut serialized_long_vec.as_slice()) + .unwrap() + .into(); + assert_eq!(long_vector.len(), deserialized_long_vec.len()); + assert_eq!(long_vector, deserialized_long_vec); +} + +#[test] +fn byte_arrays() { + let x = [0u8, 1, 2, 3]; + let serialized = x.tls_serialize_detached().unwrap(); + assert_eq!(x.to_vec(), serialized); + + let y = <[u8; 4]>::tls_deserialize(&mut serialized.as_slice()).unwrap(); + assert_eq!(y, x); + + let x = [0u8, 1, 2, 3, 7, 6, 5, 4]; + let w = ArrayWrap { data: x }; + let serialized = x.tls_serialize_detached().unwrap(); + assert_eq!(x.to_vec(), serialized); + + let y = ArrayWrap::tls_deserialize(&mut serialized.as_slice()).unwrap(); + assert_eq!(y, w); +} + +#[test] +fn simple_struct() { + let mut b = &[0u8, 3, 0, 0, 0, 5, 1, 2, 3, 4, 5] as &[u8]; + let extension = ExtensionStruct { + extension_type: ExtensionType::KeyId, + extension_data: TlsVecU32::from_slice(&[1, 2, 3, 4, 5]), + }; + let deserialized = ExtensionStruct::tls_deserialize(&mut b).unwrap(); + assert_eq!(extension, deserialized); + + let mut b = &[8u8, 0, 1, 0, 2, 0, 3, 1, 244] as &[u8]; + let extension = ExtensionTypeVec { + data: TlsVecU8::from_slice(&[ + ExtensionType::Capabilities, + ExtensionType::Lifetime, + ExtensionType::KeyId, + ExtensionType::SomethingElse, + ]), + }; + let deserialized = ExtensionTypeVec::tls_deserialize(&mut b).unwrap(); + assert_eq!(extension, deserialized); +} + +#[derive(TlsDeserialize, Clone, TlsSize, PartialEq)] +struct DeserializeOnlyStruct(u16); + +// KAT from MLS + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +#[repr(u8)] +enum ProtocolVersion { + Reserved = 0, + Mls10 = 1, +} + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct CipherSuite(u16); + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct HPKEPublicKey(TlsVecU16); + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct CredentialType(u16); + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct SignatureScheme(u16); + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct BasicCredential { + identity: TlsVecU16, + signature_scheme: SignatureScheme, + signature_key: TlsVecU16, +} + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct Credential { + credential_type: CredentialType, + credential: BasicCredential, +} + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct Extension { + extension_type: ExtensionType, + extension_data: TlsVecU32, +} + +#[derive(TlsSerialize, TlsDeserialize, TlsSize, Clone, PartialEq)] +struct KeyPackage { + version: ProtocolVersion, + cipher_suite: CipherSuite, + hpke_init_key: HPKEPublicKey, + credential: Credential, + extensions: TlsVecU32, + signature: TlsVecU16, +} + +#[test] +fn kat_mls_key_package() { + let key_package_bytes = &[ + 0x01u8, 0x00, 0x01, 0x00, 0x20, 0xF2, 0xBC, 0xD8, 0x95, 0x19, 0xDD, 0x1D, 0x06, 0x9F, 0x8B, + 0x9E, 0xB2, 0xEC, 0xBA, 0xA9, 0xF1, 0x67, 0xAA, 0xCC, 0x52, 0xE7, 0x4D, 0x8D, 0xFE, 0xCC, + 0xAA, 0xA3, 0xF9, 0xCF, 0x92, 0xAA, 0x35, 0x00, 0x01, 0x00, 0x0D, 0x4F, 0x70, 0x65, 0x6E, + 0x4D, 0x4C, 0x53, 0x20, 0x72, 0x6F, 0x63, 0x6B, 0x73, 0x08, 0x07, 0x00, 0x20, 0xD8, 0x0F, + 0x6A, 0x71, 0xFD, 0x5F, 0xB5, 0xEF, 0x27, 0x13, 0xE0, 0xA1, 0xD4, 0xC9, 0x28, 0x5D, 0xD2, + 0x4A, 0x5A, 0x5B, 0x21, 0xCC, 0xF5, 0x13, 0x4F, 0xDF, 0xE8, 0x25, 0xB2, 0xA6, 0x17, 0x18, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0x02, 0x01, 0xC8, 0x02, 0x00, + 0x01, 0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x60, 0xA5, 0xEF, 0xCE, 0x00, 0x00, 0x00, 0x00, 0x61, 0x14, 0xBB, 0xDE, + 0x00, 0x40, 0x73, 0xE2, 0xC6, 0x9A, 0x23, 0x49, 0x24, 0x04, 0x8A, 0xC3, 0x19, 0x20, 0x72, + 0x1A, 0x14, 0xBB, 0x92, 0x52, 0x33, 0x29, 0xB0, 0xDD, 0x36, 0x08, 0x35, 0xA1, 0x6F, 0xCA, + 0xA7, 0x64, 0xB2, 0x2A, 0xC3, 0x5C, 0x47, 0x6D, 0x0C, 0x6A, 0x8E, 0x28, 0x86, 0x96, 0x94, + 0xB6, 0xC4, 0xE0, 0x2C, 0xBE, 0x00, 0xB6, 0x2D, 0x50, 0xA7, 0x39, 0xF9, 0x30, 0xA7, 0x3F, + 0x09, 0xFC, 0xA1, 0xF4, 0x53, 0x03, + ]; + let key_package = KeyPackage::tls_deserialize(&mut (key_package_bytes as &[u8])) + .expect("Error deserializing key package."); + let serialized_key_package = key_package + .tls_serialize_detached() + .expect("Error serializing key package."); + assert_eq!( + key_package_bytes as &[u8], + serialized_key_package.as_slice() + ); +} diff --git a/tls_codec/derive/tests/encode.rs b/tls_codec/derive/tests/encode.rs new file mode 100644 index 000000000..3cbf29311 --- /dev/null +++ b/tls_codec/derive/tests/encode.rs @@ -0,0 +1,105 @@ +use tls_codec::{SecretTlsVecU16, Serialize, Size, TlsSliceU16, TlsVecU16, TlsVecU32}; +use tls_codec_derive::{TlsSerialize, TlsSize}; + +#[derive(TlsSerialize, TlsSize, Debug)] +#[repr(u16)] +pub enum ExtensionType { + Reserved = 0, + Capabilities = 1, + Lifetime = 2, + KeyId = 3, + ParentHash = 4, + RatchetTree = 5, + SomethingElse = 500, +} + +#[derive(TlsSerialize, TlsSize, Debug)] +pub struct ExtensionStruct { + extension_type: ExtensionType, + extension_data: TlsVecU32, + additional_data: Option>, +} + +#[derive(TlsSerialize, TlsSize, Debug)] +pub struct TupleStruct1(ExtensionStruct); + +#[derive(TlsSerialize, TlsSize, Debug)] +pub struct TupleStruct(ExtensionStruct, u8); + +#[derive(TlsSerialize, TlsSize, Debug)] +pub struct StructWithLifetime<'a> { + value: &'a TlsVecU16, +} + +#[derive(TlsSerialize, TlsSize, Debug, Clone)] +struct SomeValue { + val: TlsVecU16, +} + +#[derive(TlsSerialize, TlsSize)] +pub struct StructWithDoubleLifetime<'a, 'b> { + value: &'a TlsSliceU16<'a, &'b SomeValue>, +} + +#[test] +fn lifetime_struct() { + let value: TlsVecU16 = vec![7u8; 33].into(); + let s = StructWithLifetime { value: &value }; + let serialized_s = s.tls_serialize_detached().unwrap(); + assert_eq!(serialized_s, value.tls_serialize_detached().unwrap()); + + let some_default_value = SomeValue { val: value }; + let values = vec![some_default_value; 33]; + let ref_values: Vec<&SomeValue> = values.iter().map(|v| v).collect(); + let ref_values_slice = TlsSliceU16(&ref_values); + let s = StructWithDoubleLifetime { + value: &ref_values_slice, + }; + let serialized_s = s.tls_serialize_detached().unwrap(); + assert_eq!( + serialized_s, + ref_values_slice.tls_serialize_detached().unwrap() + ); +} + +#[test] +fn simple_enum() { + let serialized = ExtensionType::KeyId.tls_serialize_detached().unwrap(); + assert_eq!(vec![0, 3], serialized); + let serialized = ExtensionType::SomethingElse + .tls_serialize_detached() + .unwrap(); + assert_eq!(vec![1, 244], serialized); +} + +#[test] +fn simple_struct() { + let extension = ExtensionStruct { + extension_type: ExtensionType::KeyId, + extension_data: TlsVecU32::from_slice(&[1, 2, 3, 4, 5]), + additional_data: None, + }; + let serialized = extension.tls_serialize_detached().unwrap(); + assert_eq!(vec![0, 3, 0, 0, 0, 5, 1, 2, 3, 4, 5, 0], serialized); +} + +#[test] +fn byte_arrays() { + let x = [0u8, 1, 2, 3]; + let serialized = x.tls_serialize_detached().unwrap(); + assert_eq!(vec![0, 1, 2, 3], serialized); +} + +#[test] +fn lifetimes() { + let x = vec![1, 2, 3, 4].into(); + let s = StructWithLifetime { value: &x }; + let serialized = s.tls_serialize_detached().unwrap(); + assert_eq!(vec![0, 4, 1, 2, 3, 4], serialized); + + pub fn do_some_serializing(val: &StructWithLifetime) -> Vec { + val.tls_serialize_detached().unwrap() + } + let serialized = do_some_serializing(&s); + assert_eq!(vec![0, 4, 1, 2, 3, 4], serialized); +} diff --git a/tls_codec/src/arrays.rs b/tls_codec/src/arrays.rs new file mode 100644 index 000000000..ffb876470 --- /dev/null +++ b/tls_codec/src/arrays.rs @@ -0,0 +1,45 @@ +//! Implement the TLS codec for some byte arrays. + +use super::{Deserialize, Error, Serialize, Size}; +use std::io::{Read, Write}; + +macro_rules! impl_array { + ($($len:literal),*) => { + $( + impl Serialize for [u8; $len] { + fn tls_serialize(&self, writer: &mut W) -> Result { + let written = writer.write(self)?; + if written == $len { + Ok(written) + } else { + Err(Error::InvalidWriteLength(format!("Expected to write {} bytes but only {} were written.", $len, written))) + } + } + } + + impl Deserialize for [u8; $len] { + fn tls_deserialize(bytes: &mut R) -> Result { + let mut out = [0u8; $len]; + bytes.read_exact(&mut out)?; + Ok(out) + } + } + + impl Size for [u8; $len] { + #[inline] + fn tls_serialized_len(&self) -> usize { + $len + } + } + )* + }; +} + +impl_array!( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, + 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128 +); diff --git a/tls_codec/src/lib.rs b/tls_codec/src/lib.rs new file mode 100644 index 000000000..9ea4b47d1 --- /dev/null +++ b/tls_codec/src/lib.rs @@ -0,0 +1,148 @@ +//! # TLS Codec +//! +//! This crate implements the TLS codec as defined in [RFC 8446](https://tools.ietf.org/html/rfc8446) +//! as well as some extensions required by MLS. +//! +//! With the feature `derive` `TlsSerialize`, `TlsDeserialize`, and `TlsSize` +//! can be derived. +//! Note that `TlsSerialize` and `TlsDeserialize` both require `TlsSize`. +//! +//! This crate provides the following data structures that implement TLS +//! serialization/deserialization +//! * `u8`, `u16`, `u32`, `u64` +//! * `TlsVecU8`, `TlsVecU16`, `TlsVecU32` +//! * `SecretTlsVecU8`, `SecretTlsVecU16`, `SecretTlsVecU32` +//! The same as the `TlsVec*` versions but it implements zeroize, requiring +//! the elements to implement zeroize as well. +//! * `TlsSliceU8`, `TlsSliceU16`, `TlsSliceU32` are lightweight wrapper for slices +//! that allow to serialize them without having to create a `TlsVec*`. +//! * `TlsByteSliceU8`, `TlsByteSliceU16`, `TlsByteSliceU32`, and +//! `TlsByteVecU8`, `TlsByteVecU16`, `TlsByteVecU32` +//! are provided with optimized implementations for byte vectors. +//! * `[u8; l]`, for `l ∈ [1..128]` +//! * Serialize for `Option` where `T: Serialize` +//! * Deserialize for `Option` where `T: Deserialize` +//! * Serialize for `(T, U)` and `(T, U, V)` where `T, U, V` implement Serialize` +//! * Deserialize for `(T, U)` and `(T, U, V)` where `T, U, V` implement Deserialize` +//! +//! ## Usage +//! +//! ``` +//! use tls_codec::{TlsVecU8, Serialize, Deserialize}; +//! let mut b = &[1u8, 4, 77, 88, 1, 99] as &[u8]; +//! +//! let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); +//! assert_eq!(1, a); +//! println!("b: {:?}", b); +//! let v = TlsVecU8::::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); +//! assert_eq!(&[77, 88, 1, 99], v.as_slice()); +//! ``` + +use std::{ + fmt::Display, + io::{Read, Write}, +}; + +mod arrays; +mod primitives; +mod tls_vec; +pub use tls_vec::{ + SecretTlsVecU16, SecretTlsVecU32, SecretTlsVecU8, TlsByteSliceU16, TlsByteSliceU32, + TlsByteSliceU8, TlsByteVecU16, TlsByteVecU32, TlsByteVecU8, TlsSliceU16, TlsSliceU32, + TlsSliceU8, TlsVecU16, TlsVecU32, TlsVecU8, +}; + +#[cfg(feature = "derive")] +pub use tls_codec_derive::{TlsDeserialize, TlsSerialize, TlsSize}; + +/// Errors that are thrown by this crate. +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + /// An error occurred during encoding. + EncodingError(String), + + /// The length of a vector is invalid. + InvalidVectorLength, + + /// Error writing everything out. + InvalidWriteLength(String), + + /// Invalid input when trying to decode a primitive integer. + InvalidInput, + + /// An error occurred during decoding. + DecodingError(String), + + /// Reached the end of a byte stream. + EndOfStream, +} + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{:?}", self)) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + match e.kind() { + std::io::ErrorKind::UnexpectedEof => Self::EndOfStream, + _ => Self::DecodingError(format!("io error: {:?}", e)), + } + } +} + +/// The `Size` trait needs to be implemented by any struct that should be +/// efficiently serialized. +/// This allows to collect the length of a serialized structure before allocating +/// memory. +pub trait Size { + fn tls_serialized_len(&self) -> usize; +} + +/// The `Serialize` trait provides functions to serialize a struct or enum. +/// +/// The trait provides two functions: +/// * `tls_serialize` that takes a buffer to write the serialization to +/// * `tls_serialize_detached` that returns a byte vector +pub trait Serialize: Size { + /// Serialize `self` and write it to the `writer`. + /// The function returns the number of bytes written to `writer`. + fn tls_serialize(&self, writer: &mut W) -> Result; + + /// Serialize `self` and return it as a byte vector. + fn tls_serialize_detached(&self) -> Result, Error> { + let mut buffer = Vec::with_capacity(self.tls_serialized_len()); + let written = self.tls_serialize(&mut buffer)?; + debug_assert_eq!( + written, + buffer.len(), + "Expected that {} bytes were written but the output holds {} bytes", + written, + buffer.len() + ); + if written != buffer.len() { + Err(Error::EncodingError(format!( + "Expected that {} bytes were written but the output holds {} bytes", + written, + buffer.len() + ))) + } else { + Ok(buffer) + } + } +} + +/// The `Deserialize` trait defines functions to deserialize a byte slice to a +/// struct or enum. +pub trait Deserialize: Size { + /// This function deserializes the `bytes` from the provided a [`std::io::Read`] + /// and returns the populated struct. + /// + /// In order to get the amount of bytes read, use [`TlsSize::serialized_len`]. + fn tls_deserialize(bytes: &mut R) -> Result + where + Self: Sized; +} diff --git a/tls_codec/src/primitives.rs b/tls_codec/src/primitives.rs new file mode 100644 index 000000000..42dddd0f0 --- /dev/null +++ b/tls_codec/src/primitives.rs @@ -0,0 +1,162 @@ +//! Codec implementations for unsigned integer primitives. + +use super::{Deserialize, Error, Serialize, Size}; + +use std::io::{Read, Write}; + +impl Size for Option { + #[inline] + fn tls_serialized_len(&self) -> usize { + 1 + match self { + Some(v) => v.tls_serialized_len(), + None => 0, + } + } +} + +impl Serialize for Option { + fn tls_serialize(&self, writer: &mut W) -> Result { + match self { + Some(e) => { + let written = writer.write(&[1])?; + debug_assert_eq!(written, 1); + e.tls_serialize(writer).map(|l| l + 1) + } + None => { + writer.write_all(&[0])?; + Ok(1) + } + } + } +} + +impl Deserialize for Option { + fn tls_deserialize(bytes: &mut R) -> Result { + let mut some_or_none = [0u8; 1]; + bytes.read_exact(&mut some_or_none)?; + match some_or_none[0] { + 0 => { + Ok(None) + }, + 1 => { + let element = T::tls_deserialize(bytes)?; + Ok(Some(element)) + }, + _ => Err(Error::DecodingError(format!("Trying to decode Option with {} for option. It must be 0 for None and 1 for Some.", some_or_none[0]))) + } + } +} + +macro_rules! impl_unsigned { + ($t:ty, $bytes:literal) => { + impl Deserialize for $t { + fn tls_deserialize(bytes: &mut R) -> Result { + let mut x = (0 as $t).to_be_bytes(); + bytes.read_exact(&mut x)?; + Ok(<$t>::from_be_bytes(x)) + } + } + + impl Serialize for $t { + fn tls_serialize(&self, writer: &mut W) -> Result { + let written = writer.write(&self.to_be_bytes())?; + Ok(written) + } + } + + impl Size for $t { + #[inline] + fn tls_serialized_len(&self) -> usize { + $bytes + } + } + }; +} + +impl_unsigned!(u8, 1); +impl_unsigned!(u16, 2); +impl_unsigned!(u32, 4); +impl_unsigned!(u64, 8); + +impl From for Error { + fn from(_: std::array::TryFromSliceError) -> Self { + Self::InvalidInput + } +} + +// Implement (de)serialization for tuple. +impl Deserialize for (T, U) +where + T: Deserialize, + U: Deserialize, +{ + #[inline(always)] + fn tls_deserialize(bytes: &mut R) -> Result { + Ok((T::tls_deserialize(bytes)?, U::tls_deserialize(bytes)?)) + } +} + +impl Serialize for (T, U) +where + T: Serialize, + U: Serialize, +{ + #[inline(always)] + fn tls_serialize(&self, writer: &mut W) -> Result { + let written = self.0.tls_serialize(writer)?; + self.1.tls_serialize(writer).map(|l| l + written) + } +} + +impl Size for (T, U) +where + T: Size, + U: Size, +{ + #[inline(always)] + fn tls_serialized_len(&self) -> usize { + self.0.tls_serialized_len() + self.1.tls_serialized_len() + } +} + +impl Deserialize for (T, U, V) +where + T: Deserialize, + U: Deserialize, + V: Deserialize, +{ + #[inline(always)] + fn tls_deserialize(bytes: &mut R) -> Result { + Ok(( + T::tls_deserialize(bytes)?, + U::tls_deserialize(bytes)?, + V::tls_deserialize(bytes)?, + )) + } +} + +impl Serialize for (T, U, V) +where + T: Serialize, + U: Serialize, + V: Serialize, +{ + #[inline(always)] + fn tls_serialize(&self, writer: &mut W) -> Result { + let mut written = self.0.tls_serialize(writer)?; + written += self.1.tls_serialize(writer)?; + self.2.tls_serialize(writer).map(|l| l + written) + } +} + +impl Size for (T, U, V) +where + T: Size, + U: Size, + V: Size, +{ + #[inline(always)] + fn tls_serialized_len(&self) -> usize { + self.0.tls_serialized_len() + self.1.tls_serialized_len() + self.2.tls_serialized_len() + } +} diff --git a/tls_codec/src/tls_vec.rs b/tls_codec/src/tls_vec.rs new file mode 100644 index 000000000..18a0a947f --- /dev/null +++ b/tls_codec/src/tls_vec.rs @@ -0,0 +1,958 @@ +//! A vector with a length field for TLS serialisation. +//! Use this for any vector that is serialised. + +// TODO: #2 share code between the different implementations. There's too much +// duplicate code in here. + +use std::{ + io::{Read, Write}, + ops::Drop, +}; + +#[cfg(feature = "serde_serialize")] +use serde::ser::SerializeStruct; +use zeroize::Zeroize; + +use crate::{Deserialize, Error, Serialize, Size}; + +macro_rules! impl_size { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + /// The serialized len + #[inline(always)] + fn tls_serialized_length(&$self) -> usize { + $self.as_slice() + .iter() + .fold($len_len, |acc, e| acc + e.tls_serialized_len()) + } + } +} + +macro_rules! impl_byte_size { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + /// The serialized len + #[inline(always)] + fn tls_serialized_byte_length(&$self) -> usize { + $self.as_slice().len() + $len_len + } + } +} + +macro_rules! impl_byte_deserialize { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + #[inline(always)] + fn deserialize_bytes(bytes: &mut R) -> Result { + let len = <$size>::tls_deserialize(bytes)? as usize; + // When fuzzing we limit the maximum size to allocate. + // XXX: We should think about a configurable limit for the allocation + // here. + if cfg!(fuzzing) && len > u16::MAX as usize { + return Err(Error::DecodingError(format!( + "Trying to allocate {} bytes. Only {} allowed.", + len, + u16::MAX + ))); + } + let mut result = Self { + vec: vec![0u8; len], + }; + let read = bytes.read(result.vec.as_mut_slice())?; + if read != len { + if !cfg!(fuzzing) { + // When fuzzing we don't want to assert here because it would + // crash all the time. Throwing an error is sufficient. + debug_assert_eq!( + read, len, + "Expected to read {} bytes but {} were read.", + len, read + ); + } + Err(Error::DecodingError(format!( + "{} bytes were read but {} were expected", + read, len + ))) + } else { + Ok(result) + } + } + }; +} + +macro_rules! impl_deserialize { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + #[inline(always)] + fn deserialize(bytes: &mut R) -> Result { + let mut result = Self { vec: Vec::new() }; + let len = <$size>::tls_deserialize(bytes)?; + let mut read = len.tls_serialized_len(); + let len_len = read; + while (read - len_len) < len as usize { + let element = T::tls_deserialize(bytes)?; + read += element.tls_serialized_len(); + result.push(element); + } + Ok(result) + } + }; +} + +macro_rules! impl_serialize { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + #[inline(always)] + fn serialize(&$self, writer: &mut W) -> Result { + // Get the byte length of the content, make sure it's not too + // large and write it out. + let tls_serialized_len = $self.tls_serialized_len(); + let byte_length = tls_serialized_len - $len_len; + + let max_len = <$size>::MAX as usize; + debug_assert!( + byte_length <= max_len, + "Vector length can't be encoded in the vector length a {} >= {}", + byte_length, + max_len + ); + if byte_length > max_len { + return Err(Error::InvalidVectorLength); + } + + let mut written = (byte_length as $size).tls_serialize(writer)?; + + // Now serialize the elements + for e in $self.as_slice().iter() { + written += e.tls_serialize(writer)?; + } + + debug_assert_eq!( + written, tls_serialized_len, + "{} bytes should have been serialized but {} were written", + tls_serialized_len, written + ); + if written != tls_serialized_len { + return Err(Error::EncodingError(format!( + "{} bytes should have been serialized but {} were written", + tls_serialized_len, written + ))); + } + Ok(written) + } + }; +} + +macro_rules! impl_byte_serialize { + ($self:ident, $size:ty, $name:ident, $len_len:literal) => { + #[inline(always)] + fn serialize_bytes(&$self, writer: &mut W) -> Result { + // Get the byte length of the content, make sure it's not too + // large and write it out. + let tls_serialized_len = $self.tls_serialized_len(); + let byte_length = tls_serialized_len - $len_len; + + let max_len = <$size>::MAX as usize; + debug_assert!( + byte_length <= max_len, + "Vector length can't be encoded in the vector length a {} >= {}", + byte_length, + max_len + ); + if byte_length > max_len { + return Err(Error::InvalidVectorLength); + } + + let mut written = (byte_length as $size).tls_serialize(writer)?; + + // Now serialize the elements + written += writer.write($self.as_slice())?; + + debug_assert_eq!( + written, tls_serialized_len, + "{} bytes should have been serialized but {} were written", + tls_serialized_len, written + ); + if written != tls_serialized_len { + return Err(Error::EncodingError(format!( + "{} bytes should have been serialized but {} were written", + tls_serialized_len, written + ))); + } + Ok(written) + } + }; +} + +macro_rules! impl_tls_vec_codec_generic { + ($size:ty, $name:ident, $len_len: literal, $($bounds:ident),*) => { + impl Serialize + for $name + { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize(writer) + } + } + + impl Size + for $name + { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_length() + } + } + + impl Serialize + for &$name + { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize(writer) + } + } + + impl Size + for &$name + { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_length() + } + } + + impl Deserialize + for $name + { + fn tls_deserialize(bytes: &mut R) -> Result { + Self::deserialize(bytes) + } + } + } +} + +macro_rules! impl_tls_vec_codec_bytes { + ($size:ty, $name:ident, $len_len: literal) => { + impl Serialize for $name { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize_bytes(writer) + } + } + + impl Size for $name { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_byte_length() + } + } + + impl Serialize for &$name { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize_bytes(writer) + } + } + + impl Size for &$name { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_byte_length() + } + } + + impl Deserialize for $name { + fn tls_deserialize(bytes: &mut R) -> Result { + Self::deserialize_bytes(bytes) + } + } + }; +} + +macro_rules! impl_vec_members { + ($element_type:ident, $len_len:literal) => { + /// Create a new `TlsVec` from a Rust Vec. + #[inline] + pub fn new(vec: Vec<$element_type>) -> Self { + Self { vec } + } + + /// Create a new `TlsVec` from a slice. + #[inline] + pub fn from_slice(slice: &[$element_type]) -> Self + where + $element_type: Clone, + { + Self { + vec: slice.to_vec(), + } + } + + /// Get the length of the vector. + #[inline] + pub fn len(&self) -> usize { + self.vec.len() + } + + /// Get a slice to the raw vector. + #[inline] + pub fn as_slice(&self) -> &[$element_type] { + &self.vec + } + + /// Check if the vector is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + + /// Get the underlying vector and consume this. + #[inline] + pub fn into_vec(mut self) -> Vec<$element_type> { + std::mem::take(&mut self.vec) + } + + /// Add an element to this. + #[inline] + pub fn push(&mut self, value: $element_type) { + self.vec.push(value); + } + + /// Remove the last element. + #[inline] + pub fn pop(&mut self) -> Option<$element_type> { + self.vec.pop() + } + + /// Remove the element at `index`. + #[inline] + pub fn remove(&mut self, index: usize) -> $element_type { + self.vec.remove(index) + } + + /// Returns a reference to an element or subslice depending on the type of index. + /// XXX: implement SliceIndex instead + #[inline] + pub fn get(&self, index: usize) -> Option<&$element_type> { + self.vec.get(index) + } + + /// Returns an iterator over the slice. + #[inline] + pub fn iter(&self) -> std::slice::Iter<'_, $element_type> { + self.vec.iter() + } + + /// Retains only the elements specified by the predicate. + #[inline] + pub fn retain(&mut self, f: F) + where + F: FnMut(&$element_type) -> bool, + { + self.vec.retain(f) + } + + /// Get the number of bytes used for the length encoding. + #[inline(always)] + pub fn len_len() -> usize { + $len_len + } + }; +} + +macro_rules! impl_tls_vec_generic { + ($size:ty, $name:ident, $len_len: literal, $($bounds:ident),*) => { + #[derive(Eq, Debug)] + pub struct $name { + vec: Vec, + } + + impl Clone for $name { + fn clone(&self) -> Self { + Self::new(self.vec.clone()) + } + } + + impl $name { + impl_vec_members!(T, $len_len); + } + + impl std::hash::Hash for $name { + #[inline] + fn hash(&self, state: &mut H) { + self.vec.hash(state) + } + } + + impl std::ops::Index for $name { + type Output = T; + + #[inline] + fn index(&self, i: usize) -> &T { + self.vec.index(i) + } + } + + impl std::cmp::PartialEq for $name { + fn eq(&self, other: &Self) -> bool { + self.vec.eq(&other.vec) + } + } + + impl std::ops::IndexMut for $name { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut Self::Output { + self.vec.index_mut(i) + } + } + + impl std::borrow::Borrow<[T]> for $name { + #[inline] + fn borrow(&self) -> &[T] { + &self.vec + } + } + + impl std::iter::FromIterator for $name { + #[inline] + fn from_iter(iter: I) -> Self + where + I: IntoIterator, { + let vec = Vec::::from_iter(iter); + Self{vec} + } + } + + impl From> + for $name + { + #[inline] + fn from(v: Vec) -> Self { + Self::new(v) + } + } + + impl From<&[T]> + for $name + { + #[inline] + fn from(v: &[T]) -> Self { + Self::from_slice(v) + } + } + + impl From<$name> + for Vec + { + #[inline] + fn from(mut v: $name) -> Self { + std::mem::take(&mut v.vec) + } + } + + impl Default + for $name + { + #[inline] + fn default() -> Self { + Self { vec: Vec::new() } + } + } + + #[cfg(feature = "serde_serialize")] + impl serde::Serialize for $name + where + T: $($bounds + )* serde::Serialize, + { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct(stringify!($name), 1)?; + state.serialize_field("vec", &self.vec)?; + state.end() + } + } + + #[cfg(feature = "serde_serialize")] + impl<'de, T> serde::de::Deserialize<'de> for $name + where + T: Serialize + + Deserialize + + Size + + $($bounds + )* + serde::de::Deserialize<'de>, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + enum Field { + Vec, + } + + impl<'de> serde::de::Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("`vec`") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "vec" => Ok(Field::Vec), + _ => Err(serde::de::Error::unknown_field(value, &["vec"])), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct TlsVecVisitor { + data: std::marker::PhantomData, + } + + impl<'de, T> serde::de::Visitor<'de> for TlsVecVisitor + where + T: Serialize + + Deserialize + + Size + + $($bounds + )* + serde::de::Deserialize<'de>, + { + type Value = $name; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_fmt(format_args!("struct {}", stringify!($name))) + } + fn visit_seq(self, mut seq: V) -> Result<$name, V::Error> + where + V: serde::de::SeqAccess<'de>, + { + let vec = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; + Ok($name::::new(vec)) + } + fn visit_map(self, mut map: V) -> Result<$name, V::Error> + where + V: serde::de::MapAccess<'de>, + { + let mut vec = None; + while let Some(key) = map.next_key()? { + match key { + Field::Vec => { + if vec.is_some() { + return Err(serde::de::Error::duplicate_field("vec")); + } + vec = Some(map.next_value()?); + } + } + } + let vec = vec.ok_or_else(|| serde::de::Error::missing_field("vec"))?; + Ok($name::::new(vec)) + } + } + deserializer.deserialize_struct( + stringify!($name), + &["vec"], + TlsVecVisitor { + data: std::marker::PhantomData, + }, + ) + } + } + }; +} + +macro_rules! impl_tls_vec { + ($name:ident, $len_len:literal) => { + #[derive(Eq, Clone, Debug)] + pub struct $name { + vec: Vec, + } + + impl $name { + impl_vec_members!(u8, 1); + } + + impl std::hash::Hash for $name { + #[inline] + fn hash(&self, state: &mut H) { + self.vec.hash(state) + } + } + + impl std::ops::Index for $name { + type Output = u8; + + #[inline] + fn index(&self, i: usize) -> &u8 { + self.vec.index(i) + } + } + + impl std::cmp::PartialEq for $name { + fn eq(&self, other: &Self) -> bool { + self.vec.eq(&other.vec) + } + } + + impl std::ops::IndexMut for $name { + #[inline] + fn index_mut(&mut self, i: usize) -> &mut Self::Output { + self.vec.index_mut(i) + } + } + + impl std::borrow::Borrow<[u8]> for $name { + #[inline] + fn borrow(&self) -> &[u8] { + &self.vec + } + } + + impl std::iter::FromIterator for $name { + #[inline] + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let vec = Vec::::from_iter(iter); + Self { vec } + } + } + + impl From> for $name { + #[inline] + fn from(v: Vec) -> Self { + Self::new(v) + } + } + + impl From<&[u8]> for $name { + #[inline] + fn from(v: &[u8]) -> Self { + Self::from_slice(v) + } + } + + impl From<$name> for Vec { + #[inline] + fn from(mut v: $name) -> Self { + std::mem::take(&mut v.vec) + } + } + + impl Default for $name { + #[inline] + fn default() -> Self { + Self { vec: Vec::new() } + } + } + + #[cfg(feature = "serde_serialize")] + impl serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct(stringify!($name), 1)?; + state.serialize_field("vec", &self.vec)?; + state.end() + } + } + + #[cfg(feature = "serde_serialize")] + impl<'de> serde::de::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + enum Field { + Vec, + } + + impl<'de> serde::de::Deserialize<'de> for Field { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + struct FieldVisitor; + + impl<'de> serde::de::Visitor<'de> for FieldVisitor { + type Value = Field; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("`vec`") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "vec" => Ok(Field::Vec), + _ => Err(serde::de::Error::unknown_field(value, &["vec"])), + } + } + } + + deserializer.deserialize_identifier(FieldVisitor) + } + } + + struct TlsVecVisitor { + data: std::marker::PhantomData, + } + + impl<'de> serde::de::Visitor<'de> for TlsVecVisitor { + type Value = $name; + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_fmt(format_args!("struct {}", stringify!($name))) + } + fn visit_seq(self, mut seq: V) -> Result<$name, V::Error> + where + V: serde::de::SeqAccess<'de>, + { + let vec = seq + .next_element()? + .ok_or_else(|| serde::de::Error::invalid_length(0, &self))?; + Ok($name::new(vec)) + } + fn visit_map(self, mut map: V) -> Result<$name, V::Error> + where + V: serde::de::MapAccess<'de>, + { + let mut vec = None; + while let Some(key) = map.next_key()? { + match key { + Field::Vec => { + if vec.is_some() { + return Err(serde::de::Error::duplicate_field("vec")); + } + vec = Some(map.next_value()?); + } + } + } + let vec = vec.ok_or_else(|| serde::de::Error::missing_field("vec"))?; + Ok($name::new(vec)) + } + } + deserializer.deserialize_struct( + stringify!($name), + &["vec"], + TlsVecVisitor { + data: std::marker::PhantomData, + }, + ) + } + } + }; +} + +macro_rules! impl_secret_tls_vec { + ($size:ty, $name:ident, $len_len: literal) => { + impl_tls_vec_generic!( + $size, + $name, + $len_len, + Serialize, + Deserialize, + Size, + Zeroize + ); + impl_tls_vec_codec_generic!( + $size, + $name, + $len_len, + Serialize, + Deserialize, + Size, + Zeroize + ); + + impl $name { + // This implements serialize and size for all versions + impl_serialize!(self, $size, $name, $len_len); + impl_size!(self, $size, $name, $len_len); + impl_deserialize!(self, $size, $name, $len_len); + } + + impl Zeroize for $name { + fn zeroize(&mut self) { + self.vec.zeroize() + } + } + + impl Drop for $name { + fn drop(&mut self) { + self.zeroize() + } + } + }; +} + +macro_rules! impl_public_tls_vec { + ($size:ty, $name:ident, $len_len: literal) => { + impl_tls_vec_generic!($size, $name, $len_len, Serialize, Deserialize, Size); + + impl_tls_vec_codec_generic!($size, $name, $len_len, Serialize, Deserialize, Size); + + impl $name { + // This implements serialize and size for all versions + impl_serialize!(self, $size, $name, $len_len); + impl_size!(self, $size, $name, $len_len); + impl_deserialize!(self, $size, $name, $len_len); + } + }; +} + +macro_rules! impl_tls_byte_vec { + ($size:ty, $name:ident, $len_len: literal) => { + impl_tls_vec!($name, $len_len); + + impl $name { + // This implements serialize and size for all versions + impl_byte_serialize!(self, $size, $name, $len_len); + impl_byte_size!(self, $size, $name, $len_len); + impl_byte_deserialize!(self, $size, $name, $len_len); + } + + impl_tls_vec_codec_bytes!($size, $name, $len_len); + }; +} + +// TODO: #1 provide specialized TlsByteVec* versions holding u8 that are more efficient. + +impl_public_tls_vec!(u8, TlsVecU8, 1); +impl_public_tls_vec!(u16, TlsVecU16, 2); +impl_public_tls_vec!(u32, TlsVecU32, 4); + +impl_tls_byte_vec!(u8, TlsByteVecU8, 1); +impl_tls_byte_vec!(u16, TlsByteVecU16, 2); +impl_tls_byte_vec!(u32, TlsByteVecU32, 4); + +// Secrets should be put into these Secret tls vectors as they implement zeroize. +impl_secret_tls_vec!(u8, SecretTlsVecU8, 1); +impl_secret_tls_vec!(u16, SecretTlsVecU16, 2); +impl_secret_tls_vec!(u32, SecretTlsVecU32, 4); + +// We also implement shallow serialization for slices + +macro_rules! impl_tls_byte_slice { + ($size:ty, $name:ident, $len_len:literal) => { + pub struct $name<'a>(pub &'a [u8]); + + impl<'a> $name<'a> { + #[inline(always)] + fn as_slice(&self) -> &[u8] { + &self.0 + } + } + + impl<'a> $name<'a> { + impl_byte_serialize!(self, $size, $name, $len_len); + impl_byte_size!(self, $size, $name, $len_len); + } + + impl<'a> Serialize for &$name<'a> { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize_bytes(writer) + } + } + + impl<'a> Serialize for $name<'a> { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize_bytes(writer) + } + } + + impl<'a> Size for &$name<'a> { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_byte_length() + } + } + + impl<'a> Size for $name<'a> { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_byte_length() + } + } + }; +} + +impl_tls_byte_slice!(u8, TlsByteSliceU8, 1); +impl_tls_byte_slice!(u16, TlsByteSliceU16, 2); +impl_tls_byte_slice!(u32, TlsByteSliceU32, 4); + +macro_rules! impl_tls_slice { + ($size:ty, $name:ident, $len_len: literal) => { + pub struct $name<'a, T: Size + Serialize>(pub &'a [T]); + + impl<'a, T: Size + Serialize> $name<'a, T> { + #[inline(always)] + fn as_slice(&self) -> &[T] { + &self.0 + } + } + + impl<'a, T: Size + Serialize> $name<'a, T> { + impl_serialize!(self, $size, $name, $len_len); + impl_size!(self, $size, $name, $len_len); + } + + impl<'a, T: Size + Serialize> Serialize for &$name<'a, T> { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize(writer) + } + } + + impl<'a, T: Size + Serialize> Serialize for $name<'a, T> { + fn tls_serialize(&self, writer: &mut W) -> Result { + self.serialize(writer) + } + } + + impl<'a, T: Size + Serialize> Size for &$name<'a, T> { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_length() + } + } + + impl<'a, T: Size + Serialize> Size for $name<'a, T> { + #[inline] + fn tls_serialized_len(&self) -> usize { + self.tls_serialized_length() + } + } + }; +} + +impl_tls_slice!(u8, TlsSliceU8, 1); +impl_tls_slice!(u16, TlsSliceU16, 2); +impl_tls_slice!(u32, TlsSliceU32, 4); + +impl From for Error { + fn from(_e: std::num::TryFromIntError) -> Self { + Self::InvalidVectorLength + } +} + +impl From for Error { + fn from(_e: std::convert::Infallible) -> Self { + Self::InvalidVectorLength + } +} diff --git a/tls_codec/tests/decode.rs b/tls_codec/tests/decode.rs new file mode 100644 index 000000000..4a0fe2d5b --- /dev/null +++ b/tls_codec/tests/decode.rs @@ -0,0 +1,78 @@ +use tls_codec::{ + Deserialize, Serialize, Size, TlsByteSliceU16, TlsByteVecU16, TlsByteVecU8, TlsSliceU16, + TlsVecU16, TlsVecU8, +}; + +#[test] +fn deserialize_primitives() { + let mut b = &[77u8, 88, 1, 99] as &[u8]; + + let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(1, a.tls_serialized_len()); + assert_eq!(77, a); + let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(1, a.tls_serialized_len()); + assert_eq!(88, a); + let a = u16::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(2, a.tls_serialized_len()); + assert_eq!(355, a); + + // It's empty now. + assert!(u8::tls_deserialize(&mut b).is_err()) +} + +#[test] +fn deserialize_tls_vec() { + let mut b = &[1u8, 4, 77, 88, 1, 99] as &[u8]; + + let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(1, a); + assert_eq!(1, a.tls_serialized_len()); + println!("b: {:?}", b); + let v = TlsVecU8::::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(5, v.tls_serialized_len()); + assert_eq!(&[77, 88, 1, 99], v.as_slice()); + + // It's empty now. + assert!(u8::tls_deserialize(&mut b).is_err()); + + let long_vector = vec![77u8; 65535]; + let serialized_long_vec = TlsSliceU16(&long_vector).tls_serialize_detached().unwrap(); + let deserialized_long_vec = + TlsVecU16::::tls_deserialize(&mut serialized_long_vec.as_slice()).unwrap(); + assert_eq!( + deserialized_long_vec.tls_serialized_len(), + long_vector.len() + 2 + ); + assert_eq!(long_vector.len(), deserialized_long_vec.len()); + assert_eq!(long_vector.as_slice(), deserialized_long_vec.as_slice()); +} + +#[test] +fn deserialize_tls_byte_vec() { + let mut b = &[1u8, 4, 77, 88, 1, 99] as &[u8]; + + let a = u8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(1, a); + assert_eq!(1, a.tls_serialized_len()); + println!("b: {:?}", b); + let v = TlsByteVecU8::tls_deserialize(&mut b).expect("Unable to tls_deserialize"); + assert_eq!(5, v.tls_serialized_len()); + assert_eq!(&[77, 88, 1, 99], v.as_slice()); + + // It's empty now. + assert!(u8::tls_deserialize(&mut b).is_err()); + + let long_vector = vec![77u8; 65535]; + let serialized_long_vec = TlsByteSliceU16(&long_vector) + .tls_serialize_detached() + .unwrap(); + let deserialized_long_vec = + TlsByteVecU16::tls_deserialize(&mut serialized_long_vec.as_slice()).unwrap(); + assert_eq!( + deserialized_long_vec.tls_serialized_len(), + long_vector.len() + 2 + ); + assert_eq!(long_vector.len(), deserialized_long_vec.len()); + assert_eq!(long_vector.as_slice(), deserialized_long_vec.as_slice()); +} diff --git a/tls_codec/tests/encode.rs b/tls_codec/tests/encode.rs new file mode 100644 index 000000000..df0da8753 --- /dev/null +++ b/tls_codec/tests/encode.rs @@ -0,0 +1,23 @@ +use tls_codec::{Serialize, TlsVecU16}; + +#[test] +fn serialize_primitives() { + let mut v = Vec::new(); + 77u8.tls_serialize(&mut v).expect("Error encoding u8"); + 88u8.tls_serialize(&mut v).expect("Error encoding u8"); + 355u16.tls_serialize(&mut v).expect("Error encoding u16"); + let b = [77u8, 88, 1, 99]; + assert_eq!(&b[..], &v[..]); +} + +#[test] +fn serialize_tls_vec() { + let mut v = Vec::new(); + 1u8.tls_serialize(&mut v).expect("Error encoding u8"); + TlsVecU16::::from_slice(&[77, 88, 1, 99]) + .tls_serialize(&mut v) + .expect("Error encoding u8"); + + let b = [1u8, 0, 4, 77, 88, 1, 99]; + assert_eq!(&b[..], &v[..]); +}