Compare commits
5 commits
main
...
new-parser
Author | SHA1 | Date | |
---|---|---|---|
3be9d0fc5e | |||
81d8d7bfcc | |||
9abc7519d4 | |||
7cc2212786 | |||
d5b3f64dfe |
15 changed files with 1227 additions and 715 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
/Cargo.lock
|
||||||
|
|
460
Cargo.lock
generated
460
Cargo.lock
generated
|
@ -1,460 +0,0 @@
|
||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 3
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.10.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chacha20"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cipher",
|
|
||||||
"cpufeatures",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cipher"
|
|
||||||
version = "0.4.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
|
||||||
dependencies = [
|
|
||||||
"crypto-common",
|
|
||||||
"inout",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cpufeatures"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crypto-common"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "curve25519-dalek"
|
|
||||||
version = "4.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"curve25519-dalek-derive",
|
|
||||||
"fiat-crypto",
|
|
||||||
"platforms",
|
|
||||||
"rustc_version",
|
|
||||||
"subtle",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "curve25519-dalek-derive"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.10.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"crypto-common",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dryoc"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "65f5013c48133363c5a8db6bc74511b8b254680929c7757d9d833dea18c12f13"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"chacha20",
|
|
||||||
"curve25519-dalek",
|
|
||||||
"generic-array",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"rand_core",
|
|
||||||
"salsa20",
|
|
||||||
"sha2",
|
|
||||||
"subtle",
|
|
||||||
"winapi",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "err-derive"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"rustversion",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"synstructure",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fiat-crypto"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "53a56f0780318174bad1c127063fd0c5fdfb35398e3cd79ffaab931a6c79df80"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.14.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.2.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"wasi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "inout"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[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.150"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "memchr"
|
|
||||||
version = "2.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minimal-lexical"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nettext"
|
|
||||||
version = "0.4.1"
|
|
||||||
dependencies = [
|
|
||||||
"base64",
|
|
||||||
"dryoc",
|
|
||||||
"err-derive",
|
|
||||||
"hex",
|
|
||||||
"nom",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "7.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"minimal-lexical",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "platforms"
|
|
||||||
version = "3.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.6.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
|
||||||
dependencies = [
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "salsa20"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
|
|
||||||
dependencies = [
|
|
||||||
"cipher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.20"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde"
|
|
||||||
version = "1.0.192"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
|
||||||
dependencies = [
|
|
||||||
"serde_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive"
|
|
||||||
version = "1.0.192"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha2"
|
|
||||||
version = "0.10.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "subtle"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.39"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "synstructure"
|
|
||||||
version = "0.12.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"unicode-xid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|
||||||
|
|
||||||
[[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-x86_64-pc-windows-gnu"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
|
||||||
dependencies = [
|
|
||||||
"zeroize_derive",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zeroize_derive"
|
|
||||||
version = "1.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.39",
|
|
||||||
]
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "nettext"
|
name = "nettext"
|
||||||
description = "A text-based data format for cryptographic network protocols"
|
description = "A text-based data format for cryptographic network protocols"
|
||||||
authors = ["Alex Auvolat <alex@adnab.me>"]
|
authors = ["Alex Auvolat <alex@adnab.me>"]
|
||||||
version = "0.4.1"
|
version = "0.4.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
26
README.md
26
README.md
|
@ -82,30 +82,6 @@ Terms can be interpreted in a number of different ways, depending on the context
|
||||||
|
|
||||||
Terms further have mappings as different data types:
|
Terms further have mappings as different data types:
|
||||||
|
|
||||||
|
- BYTES: if the term maps as a STRING, decode it using base64
|
||||||
- INT: if the term maps as a STRING, decode it as an integer written in decimal notation
|
- INT: if the term maps as a STRING, decode it as an integer written in decimal notation
|
||||||
- BYTES: if the term maps as a STRING, decode it using base64. Since a STRING cannot be empty, the string `-` is used to represent an empty byte string.
|
|
||||||
- Cryptographic data types (see below)
|
|
||||||
|
|
||||||
## Cryptographic data types
|
|
||||||
|
|
||||||
Cryptographic values such as keys, hashes, signatures, etc. are encoded
|
|
||||||
as STRING with a prefix indicating the algorithm used, followed by ":",
|
|
||||||
followed by the base64-encoded value.
|
|
||||||
|
|
||||||
Prefixes are as follows:
|
|
||||||
|
|
||||||
- `pk.box:` public key for NaCl's box API
|
|
||||||
- `sk.box:` secret key for NaCl's box API
|
|
||||||
- `sk.sbox:` secret key for NaCl's secretbox API
|
|
||||||
- `h.sha256:` sha256 hash
|
|
||||||
- `h.sha512:` sha512 hash
|
|
||||||
- `h.sha3:` sha3 hash
|
|
||||||
- `h.b2:` blake2b hash
|
|
||||||
- `h.b3:` blake3 hash
|
|
||||||
- `sig.ed25519:` ed25519 signature
|
|
||||||
- `pk.ed25519:` ed25519 public signing key
|
|
||||||
- `sk.ed25519:` ed25519 secret signing key
|
|
||||||
|
|
||||||
More can be added.
|
|
||||||
|
|
||||||
- HASH, PUBKEY, SECKEY, SIGNATURE, ENCKEY, DECKEY, SYMKEY: a bunch of things that interpret BYTES as specific cryptographic items
|
- HASH, PUBKEY, SECKEY, SIGNATURE, ENCKEY, DECKEY, SYMKEY: a bunch of things that interpret BYTES as specific cryptographic items
|
||||||
|
|
474
src/buf/decode.rs
Normal file
474
src/buf/decode.rs
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
use crate::{
|
||||||
|
is_string_char, is_whitespace, DICT_ASSIGN, DICT_CLOSE, DICT_DELIM, DICT_OPEN, LIST_CLOSE,
|
||||||
|
LIST_DELIM, LIST_OPEN,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub fn decode(input: &[u8]) -> Result<(Buf<'_>, ITerm), ParseError> {
|
||||||
|
let mut buf = Buf {
|
||||||
|
bytes: input.into(),
|
||||||
|
seqs: Vec::with_capacity(16),
|
||||||
|
dicts: Vec::with_capacity(16),
|
||||||
|
terms: Vec::with_capacity(16),
|
||||||
|
};
|
||||||
|
let all_buf = IRaw {
|
||||||
|
start: 0,
|
||||||
|
end: input.len() as Pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
let term = buf.decode(all_buf)?;
|
||||||
|
|
||||||
|
Ok((buf, term))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================
|
||||||
|
|
||||||
|
enum StackItem {
|
||||||
|
Term(ITerm),
|
||||||
|
KeyValue(IRaw, ITerm),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stack = Vec<StackItem>;
|
||||||
|
|
||||||
|
impl StackItem {
|
||||||
|
fn term(self) -> ITerm {
|
||||||
|
match self {
|
||||||
|
StackItem::Term(term) => term,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn kv(self) -> (IRaw, ITerm) {
|
||||||
|
match self {
|
||||||
|
StackItem::KeyValue(key, term) => (key, term),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Buf<'a> {
|
||||||
|
pub(crate) fn decode(&mut self, raw: IRaw) -> Result<ITerm, ParseError> {
|
||||||
|
let mut stack = Vec::with_capacity(16);
|
||||||
|
|
||||||
|
let (term, rest) = self.decode_seq(raw, &mut stack)?;
|
||||||
|
assert!(stack.is_empty());
|
||||||
|
|
||||||
|
let rest = self.take_whitespace(rest);
|
||||||
|
if rest.start < raw.end {
|
||||||
|
return Err(ParseError::UnexpectedInput(rest.start as usize));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(term)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_seq(&mut self, raw: IRaw, stack: &mut Stack) -> Result<(ITerm, IRaw), ParseError> {
|
||||||
|
let start = self.take_whitespace(raw);
|
||||||
|
let stack_start = stack.len();
|
||||||
|
|
||||||
|
let mut cur_end = start;
|
||||||
|
let mut next_start = start;
|
||||||
|
loop {
|
||||||
|
match self.decode_nonseq_term(next_start, stack) {
|
||||||
|
Err(_) => break,
|
||||||
|
Ok((term, rest)) => {
|
||||||
|
stack.push(StackItem::Term(term));
|
||||||
|
cur_end = rest;
|
||||||
|
next_start = self.take_whitespace(rest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stack.len() == stack_start {
|
||||||
|
Err(self.error_at(next_start))
|
||||||
|
} else if stack.len() == stack_start + 1 {
|
||||||
|
Ok((stack.pop().unwrap().term(), next_start))
|
||||||
|
} else {
|
||||||
|
let seq_raw = IRaw {
|
||||||
|
start: start.start,
|
||||||
|
end: cur_end.start,
|
||||||
|
};
|
||||||
|
let seq_start = self.seqs.len();
|
||||||
|
self.seqs
|
||||||
|
.extend(stack.drain(stack_start..).map(StackItem::term));
|
||||||
|
let seq = TTerm::RawSeq(
|
||||||
|
seq_raw,
|
||||||
|
ISeq {
|
||||||
|
seq_start: seq_start as Pos,
|
||||||
|
seq_end: self.seqs.len() as Pos,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok((self.push_term(seq), next_start))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_nonseq_term(
|
||||||
|
&mut self,
|
||||||
|
raw: IRaw,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<(ITerm, IRaw), ParseError> {
|
||||||
|
if let Ok((term, rest)) = self.decode_string(raw) {
|
||||||
|
Ok((term, rest))
|
||||||
|
} else if let Ok((term, rest)) = self.decode_list(raw, stack) {
|
||||||
|
Ok((term, rest))
|
||||||
|
} else if let Ok((term, rest)) = self.decode_dict(raw, stack) {
|
||||||
|
Ok((term, rest))
|
||||||
|
} else {
|
||||||
|
Err(self.error_at(raw))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_list(&mut self, raw: IRaw, stack: &mut Stack) -> Result<(ITerm, IRaw), ParseError> {
|
||||||
|
let stack_start = stack.len();
|
||||||
|
|
||||||
|
let mut cur = self.take_whitespace(self.take_char(raw, LIST_OPEN)?);
|
||||||
|
|
||||||
|
while let Ok((term, rest)) = self.decode_seq(cur, stack) {
|
||||||
|
stack.push(StackItem::Term(term));
|
||||||
|
cur = self.take_whitespace(rest);
|
||||||
|
if let Ok(rest) = self.take_char(rest, LIST_DELIM) {
|
||||||
|
cur = self.take_whitespace(rest);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(rest) = self.take_char(cur, LIST_CLOSE) {
|
||||||
|
let seq_raw = IRaw {
|
||||||
|
start: raw.start,
|
||||||
|
end: rest.start,
|
||||||
|
};
|
||||||
|
let seq_start = self.seqs.len();
|
||||||
|
self.seqs
|
||||||
|
.extend(stack.drain(stack_start..).map(StackItem::term));
|
||||||
|
let seq = TTerm::RawList(
|
||||||
|
seq_raw,
|
||||||
|
ISeq {
|
||||||
|
seq_start: seq_start as Pos,
|
||||||
|
seq_end: self.seqs.len() as Pos,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok((self.push_term(seq), rest))
|
||||||
|
} else {
|
||||||
|
stack.truncate(stack_start);
|
||||||
|
Err(self.error_at(cur))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_dict(&mut self, raw: IRaw, stack: &mut Stack) -> Result<(ITerm, IRaw), ParseError> {
|
||||||
|
let stack_start = stack.len();
|
||||||
|
|
||||||
|
match self.decode_dict_inner(raw, stack) {
|
||||||
|
Ok(rest) => {
|
||||||
|
let dict_raw = IRaw {
|
||||||
|
start: raw.start,
|
||||||
|
end: rest.start,
|
||||||
|
};
|
||||||
|
let dict_start = self.dicts.len();
|
||||||
|
self.dicts
|
||||||
|
.extend(stack.drain(stack_start..).map(StackItem::kv));
|
||||||
|
self.dicts[dict_start..]
|
||||||
|
.sort_by_key(|(k, _)| (&self.bytes[k.start as usize..k.end as usize], k.start));
|
||||||
|
|
||||||
|
for ((k1, _), (k2, _)) in self.dicts[dict_start..]
|
||||||
|
.iter()
|
||||||
|
.zip(self.dicts[dict_start + 1..].iter())
|
||||||
|
{
|
||||||
|
if self.get_bytes(*k1) == self.get_bytes(*k2) {
|
||||||
|
return Err(ParseError::DuplicateKey(String::from_utf8(self.get_bytes(*k1).to_vec()).unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dict = TTerm::RawDict(
|
||||||
|
dict_raw,
|
||||||
|
IDict {
|
||||||
|
dict_start: dict_start as Pos,
|
||||||
|
dict_end: self.dicts.len() as Pos,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok((self.push_term(dict), rest))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
stack.truncate(stack_start);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_dict_inner(&mut self, raw: IRaw, stack: &mut Stack) -> Result<IRaw, ParseError> {
|
||||||
|
let mut cur = self.take_whitespace(self.take_char(raw, DICT_OPEN)?);
|
||||||
|
|
||||||
|
while let Ok((key, rest)) = self.take_string(cur) {
|
||||||
|
cur = self.take_whitespace(rest);
|
||||||
|
|
||||||
|
cur = self.take_char(cur, DICT_ASSIGN)?;
|
||||||
|
|
||||||
|
let (value, rest) = self.decode_seq(cur, stack)?;
|
||||||
|
cur = self.take_whitespace(rest);
|
||||||
|
stack.push(StackItem::KeyValue(key, value));
|
||||||
|
|
||||||
|
if let Ok(rest) = self.take_char(cur, DICT_DELIM) {
|
||||||
|
cur = self.take_whitespace(rest);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rest = self.take_char(cur, DICT_CLOSE)?;
|
||||||
|
Ok(rest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_string(&mut self, raw: IRaw) -> Result<(ITerm, IRaw), ParseError> {
|
||||||
|
let (string_raw, rest) = self.take_string(raw)?;
|
||||||
|
|
||||||
|
let term = if (string_raw.end - string_raw.start) as usize <= STR_INLINE_MAX {
|
||||||
|
let b = self.get_bytes(string_raw);
|
||||||
|
let mut bytes = [0u8; STR_INLINE_MAX];
|
||||||
|
bytes[..b.len()].copy_from_slice(b);
|
||||||
|
TTerm::StrInline(b.len() as u8, bytes)
|
||||||
|
} else {
|
||||||
|
TTerm::Str(string_raw)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((self.push_term(term), rest))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_string(&mut self, raw: IRaw) -> Result<(IRaw, IRaw), ParseError> {
|
||||||
|
let mut rest = raw;
|
||||||
|
while rest.start < rest.end {
|
||||||
|
if is_string_char(self.bytes[rest.start as usize]) {
|
||||||
|
rest.start += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rest.start > raw.start {
|
||||||
|
let string_raw = IRaw {
|
||||||
|
start: raw.start,
|
||||||
|
end: rest.start,
|
||||||
|
};
|
||||||
|
Ok((string_raw, rest))
|
||||||
|
} else {
|
||||||
|
Err(self.error_at(rest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn take_char(&self, raw: IRaw, c: u8) -> Result<IRaw, ParseError> {
|
||||||
|
if raw.start >= raw.end {
|
||||||
|
Err(ParseError::IncompleteInput)
|
||||||
|
} else if self.bytes[raw.start as usize] != c {
|
||||||
|
Err(ParseError::UnexpectedInput(raw.start as usize))
|
||||||
|
} else {
|
||||||
|
Ok(IRaw {
|
||||||
|
start: raw.start + 1,
|
||||||
|
end: raw.end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn take_whitespace(&self, mut raw: IRaw) -> IRaw {
|
||||||
|
while raw.start < raw.end {
|
||||||
|
if is_whitespace(self.bytes[raw.start as usize]) {
|
||||||
|
raw.start += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get_bytes(&self, raw: IRaw) -> &[u8] {
|
||||||
|
&self.bytes[raw.start as usize..raw.end as usize]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn error_at(&self, raw: IRaw) -> ParseError {
|
||||||
|
if raw.start < raw.end {
|
||||||
|
ParseError::UnexpectedInput(raw.start as usize)
|
||||||
|
} else {
|
||||||
|
ParseError::IncompleteInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn debug(&self, i: ITerm) {
|
||||||
|
use crate::debug as debug_str;
|
||||||
|
|
||||||
|
let term = &self.terms[i.0 as usize];
|
||||||
|
match term {
|
||||||
|
TTerm::StrInline(len, bytes) => {
|
||||||
|
eprintln!(
|
||||||
|
"{} -> {:?} = `{}`",
|
||||||
|
i.0,
|
||||||
|
term,
|
||||||
|
debug_str(&bytes[..*len as usize])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TTerm::Str(r) => {
|
||||||
|
eprintln!(
|
||||||
|
"{} -> {:?} = `{}`",
|
||||||
|
i.0,
|
||||||
|
term,
|
||||||
|
debug_str(&self.bytes[r.start as usize..r.end as usize])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
TTerm::RawSeq(r, s) => {
|
||||||
|
eprintln!(
|
||||||
|
"{} -> {:?} = `{}` ((",
|
||||||
|
i.0,
|
||||||
|
term,
|
||||||
|
debug_str(&self.bytes[r.start as usize..r.end as usize])
|
||||||
|
);
|
||||||
|
for j in self.seqs[s.seq_start as usize..s.seq_end as usize].iter() {
|
||||||
|
self.debug(*j);
|
||||||
|
}
|
||||||
|
eprintln!("))");
|
||||||
|
}
|
||||||
|
TTerm::Seq(s) => {
|
||||||
|
eprintln!("{} -> {:?} ((", i.0, term);
|
||||||
|
for j in self.seqs[s.seq_start as usize..s.seq_end as usize].iter() {
|
||||||
|
self.debug(*j);
|
||||||
|
}
|
||||||
|
eprintln!("))");
|
||||||
|
}
|
||||||
|
TTerm::RawList(r, l) => {
|
||||||
|
eprintln!(
|
||||||
|
"{} -> {:?} = `{}` [[",
|
||||||
|
i.0,
|
||||||
|
term,
|
||||||
|
debug_str(&self.bytes[r.start as usize..r.end as usize])
|
||||||
|
);
|
||||||
|
for j in self.seqs[l.seq_start as usize..l.seq_end as usize].iter() {
|
||||||
|
self.debug(*j);
|
||||||
|
}
|
||||||
|
eprintln!("]]");
|
||||||
|
}
|
||||||
|
TTerm::List(l) => {
|
||||||
|
eprintln!("{} -> {:?} [[", i.0, term);
|
||||||
|
for j in self.seqs[l.seq_start as usize..l.seq_end as usize].iter() {
|
||||||
|
self.debug(*j);
|
||||||
|
}
|
||||||
|
eprintln!("]]");
|
||||||
|
}
|
||||||
|
TTerm::RawDict(r, d) => {
|
||||||
|
eprintln!(
|
||||||
|
"{} -> {:?} = `{}` {{{{",
|
||||||
|
i.0,
|
||||||
|
term,
|
||||||
|
debug_str(&self.bytes[r.start as usize..r.end as usize])
|
||||||
|
);
|
||||||
|
for (k, v) in self.dicts[d.dict_start as usize..d.dict_end as usize].iter() {
|
||||||
|
eprint!(
|
||||||
|
"[`{}`] = ",
|
||||||
|
debug_str(&self.bytes[k.start as usize..k.end as usize])
|
||||||
|
);
|
||||||
|
self.debug(*v);
|
||||||
|
}
|
||||||
|
eprintln!("}}}}");
|
||||||
|
}
|
||||||
|
TTerm::Dict(d) => {
|
||||||
|
eprintln!("{} -> {:?} {{{{", i.0, term);
|
||||||
|
for (k, v) in self.dicts[d.dict_start as usize..d.dict_end as usize].iter() {
|
||||||
|
eprint!(
|
||||||
|
"[`{}`] = ",
|
||||||
|
debug_str(&self.bytes[k.start as usize..k.end as usize])
|
||||||
|
);
|
||||||
|
self.debug(*v);
|
||||||
|
}
|
||||||
|
eprintln!("}}}}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_and_seq() {
|
||||||
|
let string = "[ [ h1; h2; h3 ]; hello world [ acc bii; persephonisumiolaodododo dlpa] ]";
|
||||||
|
eprintln!("{}", string);
|
||||||
|
let (buf, term) = decode(string.as_bytes()).unwrap();
|
||||||
|
buf.debug(term);
|
||||||
|
|
||||||
|
let [a, b] = buf.list_of(term).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buf.raw(a).unwrap(), b"[ h1; h2; h3 ]");
|
||||||
|
let l = buf.list(a).unwrap();
|
||||||
|
assert_eq!(l.len(), 3);
|
||||||
|
assert_eq!(buf.str(l[0]).unwrap(), "h1");
|
||||||
|
assert_eq!(buf.str(l[1]).unwrap(), "h2");
|
||||||
|
assert_eq!(buf.str(l[2]).unwrap(), "h3");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buf.raw(b).unwrap(),
|
||||||
|
b"hello world [ acc bii; persephonisumiolaodododo dlpa]"
|
||||||
|
);
|
||||||
|
let [h, w, l] = buf.seq_of(b).unwrap();
|
||||||
|
assert_eq!(buf.str(h).unwrap(), "hello");
|
||||||
|
assert_eq!(buf.str(w).unwrap(), "world");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buf.raw(l).unwrap(),
|
||||||
|
b"[ acc bii; persephonisumiolaodododo dlpa]"
|
||||||
|
);
|
||||||
|
let [l1, l2] = buf.list_of(l).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buf.raw(l1).unwrap(), b"acc bii");
|
||||||
|
let s = buf.seq(&l1).unwrap();
|
||||||
|
assert_eq!(s.len(), 2);
|
||||||
|
assert_eq!(buf.str(s[0]).unwrap(), "acc");
|
||||||
|
assert_eq!(buf.str(s[1]).unwrap(), "bii");
|
||||||
|
|
||||||
|
assert_eq!(buf.raw(l2).unwrap(), b"persephonisumiolaodododo dlpa");
|
||||||
|
let [s2a, s2b] = buf.seq_of(l2).unwrap();
|
||||||
|
assert_eq!(buf.str(s2a).unwrap(), "persephonisumiolaodododo");
|
||||||
|
assert_eq!(buf.str(s2b).unwrap(), "dlpa");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dict() {
|
||||||
|
let string =
|
||||||
|
"[ { a = plop; b = hello world }; ploplop { e=15; d=12 ;c = {key=val;key2=azertyuiopazertyuiopazertyuiop}} ]";
|
||||||
|
eprintln!("{}", string);
|
||||||
|
let (buf, term) = decode(string.as_bytes()).unwrap();
|
||||||
|
buf.debug(term);
|
||||||
|
|
||||||
|
let [a, b] = buf.list_of(term).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(buf.raw(a).unwrap(), b"{ a = plop; b = hello world }");
|
||||||
|
let [aa, ab] = buf.dict_of(a, ["a", "b"], false).unwrap();
|
||||||
|
assert_eq!(buf.raw(aa).unwrap(), b"plop");
|
||||||
|
assert_eq!(buf.raw(ab).unwrap(), b"hello world");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buf.raw(b).unwrap(),
|
||||||
|
b"ploplop { e=15; d=12 ;c = {key=val;key2=azertyuiopazertyuiopazertyuiop}}"
|
||||||
|
);
|
||||||
|
let [ba, bb] = buf.seq_of(b).unwrap();
|
||||||
|
assert_eq!(buf.str(ba).unwrap(), "ploplop");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
buf.str(buf.dict_get(bb, "e").unwrap().unwrap()).unwrap(),
|
||||||
|
"15"
|
||||||
|
);
|
||||||
|
let mut iter = buf.dict_iter(bb).unwrap();
|
||||||
|
let (k1, v1) = iter.next().unwrap();
|
||||||
|
assert_eq!(k1, "c");
|
||||||
|
assert_eq!(
|
||||||
|
buf.raw(v1).unwrap(),
|
||||||
|
b"{key=val;key2=azertyuiopazertyuiopazertyuiop}"
|
||||||
|
);
|
||||||
|
let (k2, v2) = iter.next().unwrap();
|
||||||
|
assert_eq!(k2, "d");
|
||||||
|
assert_eq!(buf.str(v2).unwrap(), "12");
|
||||||
|
let (k3, v3) = iter.next().unwrap();
|
||||||
|
assert_eq!(k3, "e");
|
||||||
|
assert_eq!(buf.str(v3).unwrap(), "15");
|
||||||
|
assert!(iter.next().is_none());
|
||||||
|
}
|
||||||
|
}
|
39
src/buf/error.rs
Normal file
39
src/buf/error.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use err_derive::Error;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ParseError {
|
||||||
|
#[error(display = "Incomplete input")]
|
||||||
|
IncompleteInput,
|
||||||
|
#[error(display = "UnexpectedInput at position {}", _0)]
|
||||||
|
UnexpectedInput(usize),
|
||||||
|
#[error(display = "Duplicate dictionnary key: {}", _0)]
|
||||||
|
DuplicateKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum TermError {
|
||||||
|
#[error(display = "Invalid term index ({:?})", _0)]
|
||||||
|
InvalidIndex(ITerm),
|
||||||
|
#[error(display = "Wrong type, expected {}, got {}", _0, _1)]
|
||||||
|
WrongType(&'static str, &'static str),
|
||||||
|
#[error(display = "Wrong length, expected {}, got {}", _0, _1)]
|
||||||
|
WrongLength(usize, usize),
|
||||||
|
#[error(display = "Wrong dictionnary keys")]
|
||||||
|
WrongKeys,
|
||||||
|
#[error(display = "Term does not have a raw representation")]
|
||||||
|
NoRawRepresentation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ValueError {
|
||||||
|
#[error(display = "Invalid term index ({:?})", _0)]
|
||||||
|
InvalidIndex(ITerm),
|
||||||
|
#[error(display = "Duplicate dictionary key")]
|
||||||
|
DuplicateKey,
|
||||||
|
#[error(display = "Bad string (contains invalid characters)")]
|
||||||
|
BadString,
|
||||||
|
#[error(display = "Nested sequence")]
|
||||||
|
SeqInSeq,
|
||||||
|
}
|
391
src/buf/mod.rs
Normal file
391
src/buf/mod.rs
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
mod decode;
|
||||||
|
mod error;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::is_string_char;
|
||||||
|
|
||||||
|
pub use error::*;
|
||||||
|
pub use decode::*;
|
||||||
|
|
||||||
|
pub(crate) const STR_INLINE_MAX: usize = 18;
|
||||||
|
|
||||||
|
pub(crate) type Pos = u32;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ITerm(Pos);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct IRaw {
|
||||||
|
start: Pos,
|
||||||
|
end: Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct ISeq {
|
||||||
|
seq_start: Pos,
|
||||||
|
seq_end: Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) struct IDict {
|
||||||
|
dict_start: Pos,
|
||||||
|
dict_end: Pos,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum TTerm {
|
||||||
|
Str(IRaw),
|
||||||
|
StrInline(u8, [u8; STR_INLINE_MAX]),
|
||||||
|
RawSeq(IRaw, ISeq),
|
||||||
|
RawList(IRaw, ISeq),
|
||||||
|
RawDict(IRaw, IDict),
|
||||||
|
Seq(ISeq),
|
||||||
|
List(ISeq),
|
||||||
|
Dict(IDict),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Buf<'a> {
|
||||||
|
bytes: Cow<'a, [u8]>,
|
||||||
|
seqs: Vec<ITerm>,
|
||||||
|
dicts: Vec<(IRaw, ITerm)>,
|
||||||
|
terms: Vec<TTerm>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Buf<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
bytes: Default::default(),
|
||||||
|
seqs: Vec::with_capacity(16),
|
||||||
|
dicts: Vec::with_capacity(16),
|
||||||
|
terms: Vec::with_capacity(16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================ READING FUNCTIONS ==================
|
||||||
|
|
||||||
|
pub fn raw(&self, term: ITerm) -> Result<&[u8], TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::StrInline(len, bytes) => Ok(&bytes[..*len as usize]),
|
||||||
|
TTerm::Str(r) | TTerm::RawSeq(r, _) | TTerm::RawList(r, _) | TTerm::RawDict(r, _) => {
|
||||||
|
Ok(self.get_bytes(*r))
|
||||||
|
}
|
||||||
|
_ => Err(TermError::NoRawRepresentation),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn str(&self, term: ITerm) -> Result<&str, TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::StrInline(len, bytes) => {
|
||||||
|
let bytes = &bytes[..*len as usize];
|
||||||
|
let s = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
TTerm::Str(r) => {
|
||||||
|
let bytes = self.get_bytes(*r);
|
||||||
|
let s = unsafe { std::str::from_utf8_unchecked(bytes) };
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("string", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seq<'x>(&'x self, term: &'x ITerm) -> Result<&'x [ITerm], TermError> {
|
||||||
|
match self.get_term(*term)? {
|
||||||
|
TTerm::RawSeq(_, s) | TTerm::Seq(s) => {
|
||||||
|
Ok(&self.seqs[s.seq_start as usize..s.seq_end as usize])
|
||||||
|
}
|
||||||
|
_ => Ok(std::slice::from_ref(term)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seq_of<const N: usize>(&self, term: ITerm) -> Result<[ITerm; N], TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawSeq(_, s) | TTerm::Seq(s) => {
|
||||||
|
let seq_len = (s.seq_end - s.seq_start) as usize;
|
||||||
|
if seq_len == N {
|
||||||
|
let seq = &self.seqs[s.seq_start as usize..s.seq_end as usize];
|
||||||
|
Ok(seq.try_into().unwrap())
|
||||||
|
} else {
|
||||||
|
Err(TermError::WrongLength(N, seq_len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("seq", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(&self, term: ITerm) -> Result<&[ITerm], TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawList(_, s) | TTerm::List(s) => {
|
||||||
|
Ok(&self.seqs[s.seq_start as usize..s.seq_end as usize])
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("list", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_of<const N: usize>(&self, term: ITerm) -> Result<[ITerm; N], TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawList(_, s) | TTerm::List(s) => {
|
||||||
|
let list_len = (s.seq_end - s.seq_start) as usize;
|
||||||
|
if list_len == N {
|
||||||
|
let seq = &self.seqs[s.seq_start as usize..s.seq_end as usize];
|
||||||
|
Ok(seq.try_into().unwrap())
|
||||||
|
} else {
|
||||||
|
Err(TermError::WrongLength(N, list_len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("list", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dict_get(&self, term: ITerm, key: &str) -> Result<Option<ITerm>, TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawDict(_, d) | TTerm::Dict(d) => {
|
||||||
|
let dict = &self.dicts[d.dict_start as usize..d.dict_end as usize];
|
||||||
|
let pos_opt = dict
|
||||||
|
.binary_search_by(|(k, _)| self.get_bytes(*k).cmp(key.as_bytes()))
|
||||||
|
.ok();
|
||||||
|
Ok(pos_opt.map(|pos| dict[pos].1))
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("dict", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dict_of<const N: usize>(
|
||||||
|
&self,
|
||||||
|
term: ITerm,
|
||||||
|
keys: [&str; N],
|
||||||
|
allow_other: bool,
|
||||||
|
) -> Result<[ITerm; N], TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawDict(_, d) | TTerm::Dict(d) => {
|
||||||
|
let dict = &self.dicts[d.dict_start as usize..d.dict_end as usize];
|
||||||
|
if dict.len() < N || (dict.len() > N && !allow_other) {
|
||||||
|
return Err(TermError::WrongKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ret = [ITerm(0); N];
|
||||||
|
for i in 0..N {
|
||||||
|
let pos = dict
|
||||||
|
.binary_search_by(|(k, _)| self.get_bytes(*k).cmp(keys[i].as_bytes()))
|
||||||
|
.map_err(|_| TermError::WrongKeys)?;
|
||||||
|
ret[i] = dict[pos].1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("dict", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dict_iter(
|
||||||
|
&self,
|
||||||
|
term: ITerm,
|
||||||
|
) -> Result<impl Iterator<Item = (&str, ITerm)> + '_, TermError> {
|
||||||
|
match self.get_term(term)? {
|
||||||
|
TTerm::RawDict(_, d) | TTerm::Dict(d) => {
|
||||||
|
let dict = &self.dicts[d.dict_start as usize..d.dict_end as usize];
|
||||||
|
let iter = dict.iter().map(|(k, v)| {
|
||||||
|
(
|
||||||
|
unsafe { std::str::from_utf8_unchecked(self.get_bytes(*k)) },
|
||||||
|
*v,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Ok(iter)
|
||||||
|
}
|
||||||
|
t => Err(TermError::WrongType("dict", t.typename())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= WRITING FUNCTIONS ================
|
||||||
|
|
||||||
|
pub fn push_str(&mut self, s: &str) -> Result<ITerm, ValueError> {
|
||||||
|
let b = s.as_bytes();
|
||||||
|
|
||||||
|
if !b.iter().copied().all(is_string_char) {
|
||||||
|
return Err(ValueError::BadString);
|
||||||
|
}
|
||||||
|
|
||||||
|
let term = if b.len() <= STR_INLINE_MAX {
|
||||||
|
let mut bytes = [0u8; STR_INLINE_MAX];
|
||||||
|
bytes[..b.len()].copy_from_slice(b);
|
||||||
|
TTerm::StrInline(b.len() as u8, bytes)
|
||||||
|
} else {
|
||||||
|
TTerm::Str(self.push_bytes(b))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.push_term(term))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_seq(&mut self, iterator: impl Iterator<Item = ITerm>) -> Result<ITerm, ValueError> {
|
||||||
|
let seq_start = self.seqs.len();
|
||||||
|
|
||||||
|
for term in iterator {
|
||||||
|
match self.terms.get(term.0 as usize) {
|
||||||
|
None => {
|
||||||
|
self.seqs.truncate(seq_start);
|
||||||
|
return Err(ValueError::InvalidIndex(term));
|
||||||
|
}
|
||||||
|
Some(TTerm::RawSeq(_, _)) => {
|
||||||
|
self.seqs.truncate(seq_start);
|
||||||
|
return Err(ValueError::SeqInSeq);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.seqs.push(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let seq = ISeq {
|
||||||
|
seq_start: seq_start as Pos,
|
||||||
|
seq_end: self.seqs.len() as Pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.push_term(TTerm::Seq(seq)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_list(
|
||||||
|
&mut self,
|
||||||
|
iterator: impl Iterator<Item = ITerm>,
|
||||||
|
) -> Result<ITerm, ValueError> {
|
||||||
|
let list_start = self.seqs.len();
|
||||||
|
|
||||||
|
for term in iterator {
|
||||||
|
match self.terms.get(term.0 as usize) {
|
||||||
|
None => {
|
||||||
|
self.seqs.truncate(list_start);
|
||||||
|
return Err(ValueError::InvalidIndex(term));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.seqs.push(term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = ISeq {
|
||||||
|
seq_start: list_start as Pos,
|
||||||
|
seq_end: self.seqs.len() as Pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.push_term(TTerm::List(list)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_dict<'k>(
|
||||||
|
&mut self,
|
||||||
|
iterator: impl Iterator<Item = (&'k str, ITerm)>,
|
||||||
|
) -> Result<ITerm, ValueError> {
|
||||||
|
let bytes_start = self.bytes.len();
|
||||||
|
let dict_start = self.dicts.len();
|
||||||
|
|
||||||
|
for (key, term) in iterator {
|
||||||
|
if !key.as_bytes().iter().copied().all(is_string_char) {
|
||||||
|
return Err(ValueError::BadString);
|
||||||
|
}
|
||||||
|
let key = self.push_bytes(key.as_bytes());
|
||||||
|
|
||||||
|
match self.terms.get(term.0 as usize) {
|
||||||
|
None => {
|
||||||
|
self.bytes.to_mut().truncate(bytes_start);
|
||||||
|
self.dicts.truncate(dict_start);
|
||||||
|
return Err(ValueError::InvalidIndex(term));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.dicts.push((key, term));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dicts[dict_start..]
|
||||||
|
.sort_by_key(|(k, _)| (&self.bytes[k.start as usize..k.end as usize], k.start));
|
||||||
|
|
||||||
|
for ((k1, _), (k2, _)) in self.dicts[dict_start..]
|
||||||
|
.iter()
|
||||||
|
.zip(self.dicts[dict_start + 1..].iter())
|
||||||
|
{
|
||||||
|
if self.get_bytes(*k1) == self.get_bytes(*k2) {
|
||||||
|
self.bytes.to_mut().truncate(bytes_start);
|
||||||
|
self.dicts.truncate(dict_start);
|
||||||
|
return Err(ValueError::DuplicateKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dict = IDict {
|
||||||
|
dict_start: dict_start as Pos,
|
||||||
|
dict_end: self.dicts.len() as Pos,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.push_term(TTerm::Dict(dict)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_raw(&mut self, raw: &[u8]) -> Result<ITerm, ParseError> {
|
||||||
|
let bytes_len = self.bytes.len();
|
||||||
|
let seqs_len = self.seqs.len();
|
||||||
|
let dicts_len = self.dicts.len();
|
||||||
|
let terms_len = self.terms.len();
|
||||||
|
|
||||||
|
let raw = self.push_bytes(raw);
|
||||||
|
let result = self.decode(raw);
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
// reset to initial state
|
||||||
|
self.bytes.to_mut().truncate(bytes_len);
|
||||||
|
self.seqs.truncate(seqs_len);
|
||||||
|
self.dicts.truncate(dicts_len);
|
||||||
|
self.terms.truncate(terms_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(ParseError::UnexpectedInput(pos)) = result {
|
||||||
|
Err(ParseError::UnexpectedInput(pos - bytes_len))
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Internal ====
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_term(&self, term: ITerm) -> Result<&TTerm, TermError> {
|
||||||
|
self.terms
|
||||||
|
.get(term.0 as usize)
|
||||||
|
.ok_or(TermError::InvalidIndex(term))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push_term(&mut self, term: TTerm) -> ITerm {
|
||||||
|
let ret = ITerm(self.terms.len() as Pos);
|
||||||
|
self.terms.push(term);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push_bytes(&mut self, raw: &[u8]) -> IRaw {
|
||||||
|
let bytes_start = self.bytes.len();
|
||||||
|
self.bytes.to_mut().extend(raw);
|
||||||
|
IRaw {
|
||||||
|
start: bytes_start as Pos,
|
||||||
|
end: self.bytes.len() as Pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TTerm {
|
||||||
|
fn typename(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
TTerm::Str(_) | TTerm::StrInline(_, _) => "string",
|
||||||
|
TTerm::RawSeq(_, _) | TTerm::Seq(_) => "seq",
|
||||||
|
TTerm::RawList(_, _) | TTerm::List(_) => "list",
|
||||||
|
TTerm::RawDict(_, _) | TTerm::Dict(_) => "dict",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
pub use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sizeof() {
|
||||||
|
assert_eq!(std::mem::size_of::<TTerm>(), 20);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,158 +1,48 @@
|
||||||
//! Helpers to use cryptographic data types in nettext
|
//! Helpers to use cryptographic data types in nettext
|
||||||
|
|
||||||
pub use dryoc;
|
pub use dryoc::*;
|
||||||
|
|
||||||
use dryoc::types::Bytes;
|
use dryoc::types::{Bytes, StackByteArray};
|
||||||
use dryoc::*;
|
|
||||||
|
|
||||||
use crate::dec;
|
|
||||||
use crate::enc;
|
use crate::enc;
|
||||||
|
|
||||||
const BM_HASH: &str = "h.b2";
|
pub type SigningKeyPair = sign::SigningKeyPair<sign::PublicKey, sign::SecretKey>;
|
||||||
|
|
||||||
const BM_SIGNATURE: &str = "sig.ed25519";
|
impl<const N: usize> enc::Encode for StackByteArray<N> {
|
||||||
const BM_SIGN_KEYPAIR: &str = "sk.ed25519";
|
|
||||||
const BM_SIGN_PUBKEY: &str = "pk.ed25519";
|
|
||||||
|
|
||||||
// ---- types ----
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
|
||||||
pub struct Hash(pub generichash::Hash);
|
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
|
||||||
pub struct Signature(pub sign::Signature);
|
|
||||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
|
||||||
pub struct SigningPublicKey(pub sign::PublicKey);
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
|
||||||
pub struct SigningKeyPair(pub sign::SigningKeyPair<sign::PublicKey, sign::SecretKey>);
|
|
||||||
|
|
||||||
impl SigningKeyPair {
|
|
||||||
/// Return the public key of this keypair
|
|
||||||
pub fn public_key(&self) -> SigningPublicKey {
|
|
||||||
SigningPublicKey(self.0.public_key.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- encoding ----
|
|
||||||
|
|
||||||
impl enc::Encode for Hash {
|
|
||||||
fn term(&self) -> enc::Result<'_> {
|
fn term(&self) -> enc::Result<'_> {
|
||||||
enc::marked_bytes(BM_HASH, self.0.as_slice())
|
Ok(enc::bytes(self.as_slice()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl enc::Encode for Signature {
|
impl enc::Encode for sign::SigningKeyPair<sign::PublicKey, sign::SecretKey> {
|
||||||
fn term(&self) -> enc::Result<'_> {
|
fn term(&self) -> enc::Result<'_> {
|
||||||
enc::marked_bytes(BM_SIGNATURE, self.0.as_slice())
|
Ok(enc::bytes(self.secret_key.as_slice()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl enc::Encode for SigningPublicKey {
|
// ---- helpers ----
|
||||||
fn term(&self) -> enc::Result<'_> {
|
|
||||||
enc::marked_bytes(BM_SIGN_PUBKEY, self.0.as_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl enc::Encode for SigningKeyPair {
|
|
||||||
fn term(&self) -> enc::Result<'_> {
|
|
||||||
enc::marked_bytes(BM_SIGN_KEYPAIR, self.0.secret_key.as_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- calculating hashes, signatures, etc ----
|
|
||||||
|
|
||||||
/// Compute the hash of a payload with default dryoc parameters and optionnal key
|
/// Compute the hash of a payload with default dryoc parameters and optionnal key
|
||||||
pub fn compute_hash(bytes: &[u8], key: Option<&[u8; 32]>) -> Hash {
|
pub fn compute_hash(bytes: &[u8], key: Option<&[u8; 32]>) -> generichash::Hash {
|
||||||
Hash(generichash::GenericHash::hash_with_defaults(bytes, key).unwrap())
|
generichash::GenericHash::hash_with_defaults(bytes, key).unwrap()
|
||||||
}
|
|
||||||
|
|
||||||
/// Generate a new signing keypair
|
|
||||||
pub fn gen_signing_keypair() -> SigningKeyPair {
|
|
||||||
SigningKeyPair(sign::SigningKeyPair::gen_with_defaults())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the ed25519 signature of a message using a secret key
|
/// Compute the ed25519 signature of a message using a secret key
|
||||||
pub fn compute_signature(message: &[u8], keypair: &SigningKeyPair) -> Signature {
|
pub fn compute_signature(message: &[u8], secret_key: &sign::SecretKey) -> sign::Signature {
|
||||||
Signature(
|
SigningKeyPair::from_secret_key(secret_key.clone())
|
||||||
keypair
|
|
||||||
.0
|
|
||||||
.sign_with_defaults(message)
|
.sign_with_defaults(message)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_parts()
|
.into_parts()
|
||||||
.0,
|
.0
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the ed25519 signature of a message using a public key
|
/// Verify the ed25519 signature of a message using a public key
|
||||||
pub fn verify_signature(
|
pub fn verify_signature(
|
||||||
signature: &Signature,
|
signature: sign::Signature,
|
||||||
message: &[u8],
|
message: Vec<u8>,
|
||||||
public_key: &SigningPublicKey,
|
public_key: &sign::PublicKey,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
sign::SignedMessage::from_parts(signature.0.clone(), message.to_vec())
|
sign::SignedMessage::from_parts(signature.clone(), message)
|
||||||
.verify(&public_key.0)
|
.verify(public_key)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- decode helpers ----
|
|
||||||
|
|
||||||
pub trait CryptoDec {
|
|
||||||
/// Try to interpret this string as a Blake2b512 digest
|
|
||||||
/// (32-bytes base64 encoded, prefixed by `h.b2:`)
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use nettext::dec::decode;
|
|
||||||
/// use nettext::crypto::{compute_hash, CryptoDec};
|
|
||||||
///
|
|
||||||
/// let term = decode(b"{
|
|
||||||
/// message = hello;
|
|
||||||
/// hash = h.b2:Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8;
|
|
||||||
/// }").unwrap();
|
|
||||||
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
|
||||||
/// let expected_hash = compute_hash(msg.raw(), None);
|
|
||||||
/// assert_eq!(hash.hash().unwrap(), expected_hash);
|
|
||||||
/// ```
|
|
||||||
fn hash(&self) -> Result<Hash, dec::TypeError>;
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 signature
|
|
||||||
/// (64 bytes base64 encoded, prefixed by `sig.ed25519:`)
|
|
||||||
fn signature(&self) -> Result<Signature, dec::TypeError>;
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 keypair
|
|
||||||
/// (64 bytes base64 encoded, prefixed by `sk.ed25519:`)
|
|
||||||
fn keypair(&self) -> Result<SigningKeyPair, dec::TypeError>;
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 public key
|
|
||||||
/// (32 bytes base64 encoded, prefixed by `pk.ed25519:`)
|
|
||||||
fn public_key(&self) -> Result<SigningPublicKey, dec::TypeError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> CryptoDec for dec::Term<'a, 'b> {
|
|
||||||
fn hash(&self) -> Result<Hash, dec::TypeError> {
|
|
||||||
Ok(Hash(generichash::Hash::from(
|
|
||||||
self.marked_bytes_exact(BM_HASH)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
|
||||||
fn signature(&self) -> Result<Signature, dec::TypeError> {
|
|
||||||
Ok(Signature(sign::Signature::from(
|
|
||||||
self.marked_bytes_exact(BM_SIGNATURE)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keypair(&self) -> Result<SigningKeyPair, dec::TypeError> {
|
|
||||||
let secret_key = sign::SecretKey::from(self.marked_bytes_exact(BM_SIGN_KEYPAIR)?);
|
|
||||||
Ok(SigningKeyPair(sign::SigningKeyPair::from_secret_key(
|
|
||||||
secret_key,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key(&self) -> Result<SigningPublicKey, dec::TypeError> {
|
|
||||||
Ok(SigningPublicKey(sign::PublicKey::from(
|
|
||||||
self.marked_bytes_exact(BM_SIGN_PUBKEY)?,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,9 +8,6 @@ pub enum TypeError {
|
||||||
/// The term could not be decoded in the given type
|
/// The term could not be decoded in the given type
|
||||||
#[error(display = "Not a {}", _0)]
|
#[error(display = "Not a {}", _0)]
|
||||||
WrongType(&'static str),
|
WrongType(&'static str),
|
||||||
/// The term did not have the correct marker
|
|
||||||
#[error(display = "Byte marker was not {}", _0)]
|
|
||||||
WrongMarker(&'static str),
|
|
||||||
|
|
||||||
/// The term is not an array of the requested length
|
/// The term is not an array of the requested length
|
||||||
#[error(display = "Expected {} items, got {}", _0, _1)]
|
#[error(display = "Expected {} items, got {}", _0, _1)]
|
||||||
|
|
|
@ -5,6 +5,9 @@ mod error;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "dryoc"))]
|
||||||
|
use crate::crypto;
|
||||||
|
|
||||||
use crate::debug;
|
use crate::debug;
|
||||||
|
|
||||||
pub use decode::*;
|
pub use decode::*;
|
||||||
|
@ -417,7 +420,7 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
};
|
};
|
||||||
match self.0.mkref() {
|
match self.0.mkref() {
|
||||||
AnyTerm::Str(encoded) => {
|
AnyTerm::Str(encoded) => {
|
||||||
if encoded == b"-" {
|
if encoded == b"." {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
} else {
|
} else {
|
||||||
decode(encoded)
|
decode(encoded)
|
||||||
|
@ -439,40 +442,68 @@ impl<'a, 'b> Term<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to interpret this string as base64-encoded bytes,
|
/// Try to interpret this string as base64-encoded bytes,
|
||||||
/// with a marker prefix and an exact byte length.
|
/// with an exact length.
|
||||||
/// This is typically used for cryptographic data types such as hashes,
|
|
||||||
/// keys, signatures, ...
|
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use nettext::dec::decode;
|
/// use nettext::dec::decode;
|
||||||
///
|
///
|
||||||
/// let term = decode(b"test:aGVsbG8sIHdvcmxkIQ").unwrap();
|
/// let term = decode(b"aGVsbG8sIHdvcmxkIQ").unwrap();
|
||||||
/// assert_eq!(&term.marked_bytes_exact::<13>("test").unwrap(), b"hello, world!");
|
/// assert_eq!(&term.bytes_exact::<13>().unwrap(), b"hello, world!");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn marked_bytes_exact<const N: usize>(
|
pub fn bytes_exact<const N: usize>(&self) -> Result<[u8; N], TypeError> {
|
||||||
&self,
|
let bytes = self.bytes()?;
|
||||||
marker: &'static str,
|
|
||||||
) -> Result<[u8; N], TypeError> {
|
|
||||||
let mkr = marker.as_bytes();
|
|
||||||
match &self.0 {
|
|
||||||
AnyTerm::Str(s)
|
|
||||||
if s.len() >= mkr.len() + 2 && &s[..mkr.len()] == mkr && s[mkr.len()] == b':' =>
|
|
||||||
{
|
|
||||||
let bytes = match &s[mkr.len() + 1..] {
|
|
||||||
b"-" => vec![],
|
|
||||||
bytes => base64::decode_config(bytes, base64::URL_SAFE_NO_PAD)
|
|
||||||
.map_err(|_| TypeError::WrongType("BYTES"))?,
|
|
||||||
};
|
|
||||||
let bytes_len = bytes.len();
|
let bytes_len = bytes.len();
|
||||||
bytes
|
bytes
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
.map_err(|_| TypeError::WrongLength(bytes_len, N))
|
||||||
}
|
}
|
||||||
AnyTerm::Str(_) => Err(TypeError::WrongMarker(marker)),
|
|
||||||
_ => Err(TypeError::WrongType("BYTES")),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- CRYPTO HELPERS ----
|
||||||
|
|
||||||
|
#[cfg(feature = "dryoc")]
|
||||||
|
impl<'a, 'b> Term<'a, 'b> {
|
||||||
|
/// Try to interpret this string as a Blake2b512 digest (32-bytes base64 encoded)
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use nettext::dec::decode;
|
||||||
|
/// use nettext::crypto::generichash::GenericHash;
|
||||||
|
///
|
||||||
|
/// let term = decode(b"{
|
||||||
|
/// message = hello;
|
||||||
|
/// hash = Mk3PAn3UowqTLEQfNlol6GsXPe-kuOWJSCU0cbgbcs8;
|
||||||
|
/// }").unwrap();
|
||||||
|
/// let [msg, hash] = term.dict_of(["message", "hash"], false).unwrap();
|
||||||
|
/// let expected_hash = GenericHash::hash_with_defaults(msg.raw(), None::<&Vec<u8>>).unwrap();
|
||||||
|
/// assert_eq!(hash.hash().unwrap(), expected_hash);
|
||||||
|
/// ```
|
||||||
|
pub fn hash(&self) -> Result<crypto::generichash::Hash, TypeError> {
|
||||||
|
Ok(crypto::generichash::Hash::from(self.bytes_exact()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 keypair (64 bytes base64 encoded)
|
||||||
|
pub fn keypair(&self) -> Result<crypto::SigningKeyPair, TypeError> {
|
||||||
|
let secret_key = crypto::sign::SecretKey::from(self.bytes_exact()?);
|
||||||
|
Ok(crypto::SigningKeyPair::from_secret_key(secret_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 public key (32 bytes base64 encoded)
|
||||||
|
pub fn public_key(&self) -> Result<crypto::sign::PublicKey, TypeError> {
|
||||||
|
Ok(crypto::sign::PublicKey::from(self.bytes_exact()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 secret key (32 bytes base64 encoded)
|
||||||
|
pub fn secret_key(&self) -> Result<crypto::sign::SecretKey, TypeError> {
|
||||||
|
Ok(crypto::sign::SecretKey::from(self.bytes_exact()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to interpret this string as an ed25519 signature (64 bytes base64 encoded)
|
||||||
|
pub fn signature(&self) -> Result<crypto::sign::Signature, TypeError> {
|
||||||
|
Ok(crypto::sign::Signature::from(self.bytes_exact()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::dec::{self, decode};
|
use crate::dec::{self, decode};
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use crate::{is_string_char, is_whitespace};
|
use crate::{is_string_char, is_whitespace, switch64, BytesEncoding};
|
||||||
|
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
|
@ -100,36 +100,41 @@ pub fn raw(bytes: &[u8]) -> Result<'_> {
|
||||||
/// Term corresponding to a byte slice,
|
/// Term corresponding to a byte slice,
|
||||||
/// encoding using base64 url-safe encoding without padding.
|
/// encoding using base64 url-safe encoding without padding.
|
||||||
/// Since empty strings are not possible in nettext,
|
/// Since empty strings are not possible in nettext,
|
||||||
/// an empty byte string is encoded as the special string `-`.
|
/// an empty byte string is encoded as an empty list (`[]`).
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use nettext::enc::*;
|
/// use nettext::enc::*;
|
||||||
///
|
///
|
||||||
/// assert_eq!(bytes(b"").encode(), b"-");
|
|
||||||
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
/// assert_eq!(bytes(b"hello, world!").encode(), b"aGVsbG8sIHdvcmxkIQ");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
pub fn bytes(bytes: &[u8]) -> Term<'static> {
|
||||||
if bytes.is_empty() {
|
bytes_format(bytes, BytesEncoding::Base64 { split: false })
|
||||||
Term(T::Str(b"-"))
|
|
||||||
} else {
|
|
||||||
Term(T::OwnedStr(
|
|
||||||
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
/// Same as `bytes()`, but splits the byte slice in 48-byte chunks
|
||||||
/// and encodes each chunk separately, putting them in a sequence of terms.
|
/// and encodes each chunk separately, putting them in a sequence of terms.
|
||||||
/// Usefull for long byte slices to have cleaner representations.
|
/// Usefull for long byte slices to have cleaner representations,
|
||||||
|
/// mainly usefull for dictionnary keys.
|
||||||
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
||||||
if bytes.is_empty() {
|
bytes_format(bytes, BytesEncoding::Base64 { split: true })
|
||||||
Term(T::Str(b"-"))
|
}
|
||||||
} else {
|
|
||||||
|
pub fn bytes_format(bytes: &[u8], encoding: BytesEncoding) -> Term<'static> {
|
||||||
|
match encoding {
|
||||||
|
BytesEncoding::Base64 { .. } | BytesEncoding::Hex { .. } if bytes.is_empty() => {
|
||||||
|
Term(T::List(vec![]))
|
||||||
|
}
|
||||||
|
BytesEncoding::Base64 { split: false } => Term(T::OwnedStr(
|
||||||
|
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD).into_bytes(),
|
||||||
|
)),
|
||||||
|
BytesEncoding::Base64 { split: true } => {
|
||||||
let chunks = bytes
|
let chunks = bytes
|
||||||
.chunks(48)
|
.chunks(48)
|
||||||
.map(|b| T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes()))
|
.map(|b| {
|
||||||
|
T::OwnedStr(base64::encode_config(b, base64::URL_SAFE_NO_PAD).into_bytes())
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if chunks.len() > 1 {
|
if chunks.len() > 1 {
|
||||||
Term(T::Seq(chunks))
|
Term(T::Seq(chunks))
|
||||||
|
@ -137,39 +142,21 @@ pub fn bytes_split(bytes: &[u8]) -> Term<'static> {
|
||||||
Term(chunks.into_iter().next().unwrap())
|
Term(chunks.into_iter().next().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
BytesEncoding::Hex { split: false } => Term(T::OwnedStr(hex::encode(bytes).into_bytes())),
|
||||||
|
BytesEncoding::Hex { split: true } => {
|
||||||
/// Term corresponding to a byte slice,
|
let chunks = bytes
|
||||||
/// encoding using base64 url-safe encoding without padding,
|
.chunks(32)
|
||||||
/// with a prefix used to identify its content type.
|
.map(|b| T::OwnedStr(hex::encode(b).into_bytes()))
|
||||||
/// The marker prefix is typically used in crypto settings to identify
|
.collect::<Vec<_>>();
|
||||||
/// a cryptographic protocol or algorithm; it may not contain the `:` character.
|
if chunks.len() > 1 {
|
||||||
///
|
Term(T::Seq(chunks))
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use nettext::enc::*;
|
|
||||||
///
|
|
||||||
/// assert_eq!(marked_bytes("mytype", b"").unwrap().encode(), b"mytype:-");
|
|
||||||
/// assert_eq!(marked_bytes("mytype", b"hello, world!").unwrap().encode(), b"mytype:aGVsbG8sIHdvcmxkIQ");
|
|
||||||
/// ```
|
|
||||||
pub fn marked_bytes(marker: &str, bytes: &[u8]) -> Result<'static> {
|
|
||||||
for c in marker.as_bytes().iter() {
|
|
||||||
if !is_string_char(*c) || *c == b':' {
|
|
||||||
return Err(Error::InvalidCharacter(*c));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bytes.is_empty() {
|
|
||||||
Ok(Term(T::OwnedStr(format!("{}:-", marker).into_bytes())))
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Term(T::OwnedStr(
|
Term(chunks.into_iter().next().unwrap())
|
||||||
format!(
|
}
|
||||||
"{}:{}",
|
}
|
||||||
marker,
|
BytesEncoding::Switch64 { allow_whitespace } => {
|
||||||
base64::encode_config(bytes, base64::URL_SAFE_NO_PAD)
|
Term(T::OwnedStr(switch64::encode(bytes, allow_whitespace)))
|
||||||
)
|
}
|
||||||
.into_bytes(),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
src/lib.rs
54
src/lib.rs
|
@ -1,12 +1,13 @@
|
||||||
//! A text-based data format for cryptographic network protocols.
|
//! A text-based data format for cryptographic network protocols.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
|
//! #[cfg(feature = "dryoc")] {
|
||||||
//! use nettext::enc::*;
|
//! use nettext::enc::*;
|
||||||
//! use nettext::dec::*;
|
//! use nettext::dec::*;
|
||||||
//! use nettext::crypto::*;
|
//! use nettext::crypto::*;
|
||||||
//!
|
//!
|
||||||
//! let final_payload = {
|
//! let final_payload = {
|
||||||
//! let keypair = gen_signing_keypair();
|
//! let keypair = SigningKeyPair::gen_with_defaults();
|
||||||
//!
|
//!
|
||||||
//! // Encode a fist object that represents a payload that will be hashed and signed
|
//! // Encode a fist object that represents a payload that will be hashed and signed
|
||||||
//! let signed_payload = seq([
|
//! let signed_payload = seq([
|
||||||
|
@ -18,12 +19,12 @@
|
||||||
//! ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
|
//! ("c", raw(b"{ a = 12; b = 42 }").unwrap()),
|
||||||
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
//! ("d", bytes_split(&((0..128u8).collect::<Vec<_>>()))),
|
||||||
//! ]).unwrap(),
|
//! ]).unwrap(),
|
||||||
//! keypair.public_key().term().unwrap(),
|
//! keypair.public_key.term().unwrap(),
|
||||||
//! ]).unwrap().encode();
|
//! ]).unwrap().encode();
|
||||||
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
//! eprintln!("{}", std::str::from_utf8(&signed_payload).unwrap());
|
||||||
//!
|
//!
|
||||||
//! let hash = compute_hash(&signed_payload, None);
|
//! let hash = compute_hash(&signed_payload, None);
|
||||||
//! let sign = compute_signature(&signed_payload[..], &keypair);
|
//! let sign = compute_signature(&signed_payload[..], &keypair.secret_key);
|
||||||
//!
|
//!
|
||||||
//! // Encode a second object that represents the signed and hashed payload
|
//! // Encode a second object that represents the signed and hashed payload
|
||||||
//! dict([
|
//! dict([
|
||||||
|
@ -46,10 +47,11 @@
|
||||||
//!
|
//!
|
||||||
//! let [verb, arg1, arg2, pubkey] = object2.seq_of().unwrap();
|
//! let [verb, arg1, arg2, pubkey] = object2.seq_of().unwrap();
|
||||||
//! let pubkey = pubkey.public_key().unwrap();
|
//! let pubkey = pubkey.public_key().unwrap();
|
||||||
//! assert!(verify_signature(&signature, payload.raw(), &pubkey));
|
//! assert!(verify_signature(signature, payload.raw().to_vec(), &pubkey));
|
||||||
//!
|
//!
|
||||||
//! assert_eq!(verb.string().unwrap(), "CALL");
|
//! assert_eq!(verb.string().unwrap(), "CALL");
|
||||||
//! assert_eq!(arg1.string().unwrap(), "myfunction");
|
//! assert_eq!(arg1.string().unwrap(), "myfunction");
|
||||||
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The value of `signed_payload` would be as follows:
|
//! The value of `signed_payload` would be as follows:
|
||||||
|
@ -62,13 +64,13 @@
|
||||||
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
||||||
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
||||||
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
||||||
//! } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94
|
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! And the value of `final_payload` would be as follows:
|
//! And the value of `final_payload` would be as follows:
|
||||||
//! ```raw
|
//! ```raw
|
||||||
//! {
|
//! {
|
||||||
//! hash = h.b2:B1AnRocS90DmqxynGyvvBNuh-brucNO7-5hrsGplJr0;
|
//! hash = fTTk8Hm0HLGwaskCIqFBzRVMrVTeXGetmNBK2X3pNyY;
|
||||||
//! payload = CALL myfunction {
|
//! payload = CALL myfunction {
|
||||||
//! a = hello;
|
//! a = hello;
|
||||||
//! b = world;
|
//! b = world;
|
||||||
|
@ -76,16 +78,18 @@
|
||||||
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
//! d = AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v
|
||||||
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
//! MDEyMzQ1Njc4OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f
|
||||||
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
//! YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8;
|
||||||
//! } pk.ed25519:inYgWFyL_BzZTsXNKp71r2aVct_3Izi_bkerbzOiz94;
|
//! } ZCkE-mTMlK3355u_0UzabRbSNcNO3CWAur7dAhglYtI;
|
||||||
//! signature = sig.ed25519:LvLC1gHxNxUH44HHQRO-zWtLM4WyXhiYLFr94qTdI311Wa-kmgZsaWqSWe3jcjkS4PnsWSNt5apgbhR68cWWCg;
|
//! signature = XPMrlhAIMfZb6a5Fh5F_ZaEf61olJ1hK4I2kh7vEPT1n20S-943X5cH35bb0Bfwkvy_ENfOTbb3ep1zn2lSIBg;
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Note that the value of `text1` is embedded as-is inside `text2`. This is what allows us
|
//! Note that the value of `text1` is embedded as-is inside `text2`. This is what allows us
|
||||||
//! to check the hash and the signature: the raw representation of the term hasn't changed.
|
//! to check the hash and the signature: the raw representation of the term hasn't changed.
|
||||||
|
|
||||||
|
pub mod buf;
|
||||||
pub mod dec;
|
pub mod dec;
|
||||||
pub mod enc;
|
pub mod enc;
|
||||||
|
pub mod switch64;
|
||||||
|
|
||||||
#[cfg(feature = "dryoc")]
|
#[cfg(feature = "dryoc")]
|
||||||
pub mod crypto;
|
pub mod crypto;
|
||||||
|
@ -93,6 +97,35 @@ pub mod crypto;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
pub mod serde;
|
pub mod serde;
|
||||||
|
|
||||||
|
/// Possible encodings for byte strings in NetText
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum BytesEncoding {
|
||||||
|
/// Base64 encoding (default)
|
||||||
|
Base64 { split: bool },
|
||||||
|
/// Hexadecimal encoding
|
||||||
|
Hex { split: bool },
|
||||||
|
/// Switch64 encoding, a mix of plain text and base64
|
||||||
|
Switch64 { allow_whitespace: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BytesEncoding {
|
||||||
|
fn default() -> Self {
|
||||||
|
BytesEncoding::Base64 { split: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BytesEncoding {
|
||||||
|
pub fn without_whitespace(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
BytesEncoding::Base64 { .. } => BytesEncoding::Base64 { split: false },
|
||||||
|
BytesEncoding::Hex { .. } => BytesEncoding::Hex { split: false },
|
||||||
|
BytesEncoding::Switch64 { .. } => BytesEncoding::Switch64 {
|
||||||
|
allow_whitespace: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- syntactic elements of the data format ----
|
// ---- syntactic elements of the data format ----
|
||||||
|
|
||||||
pub(crate) const DICT_OPEN: u8 = b'{';
|
pub(crate) const DICT_OPEN: u8 = b'{';
|
||||||
|
@ -102,9 +135,12 @@ pub(crate) const DICT_DELIM: u8 = b';';
|
||||||
pub(crate) const LIST_OPEN: u8 = b'[';
|
pub(crate) const LIST_OPEN: u8 = b'[';
|
||||||
pub(crate) const LIST_CLOSE: u8 = b']';
|
pub(crate) const LIST_CLOSE: u8 = b']';
|
||||||
pub(crate) const LIST_DELIM: u8 = b';';
|
pub(crate) const LIST_DELIM: u8 = b';';
|
||||||
const BASE_EXTRA_CHARS: &[u8] = b".,:?!@$^<>|&#'_-+*/%";
|
const BASE_EXTRA_CHARS: &[u8] = br#".,:?!@$^<>|&#"'_-+*/%"#;
|
||||||
const STR_EXTRA_CHARS: &[u8] = b"\\";
|
const STR_EXTRA_CHARS: &[u8] = b"\\";
|
||||||
|
|
||||||
|
pub(crate) const SWITCH64_SEPARATOR: u8 = b'\\';
|
||||||
|
pub(crate) const SWITCH64_EXTRA_CHARS: &[u8] = BASE_EXTRA_CHARS;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn is_string_char(c: u8) -> bool {
|
pub(crate) fn is_string_char(c: u8) -> bool {
|
||||||
c.is_ascii_alphanumeric() || BASE_EXTRA_CHARS.contains(&c) || STR_EXTRA_CHARS.contains(&c)
|
c.is_ascii_alphanumeric() || BASE_EXTRA_CHARS.contains(&c) || STR_EXTRA_CHARS.contains(&c)
|
||||||
|
|
|
@ -4,6 +4,7 @@ mod de;
|
||||||
mod error;
|
mod error;
|
||||||
mod ser;
|
mod ser;
|
||||||
|
|
||||||
|
pub use crate::BytesEncoding;
|
||||||
pub use de::{from_bytes, from_term, Deserializer};
|
pub use de::{from_bytes, from_term, Deserializer};
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use ser::{to_bytes, to_term, Serializer};
|
pub use ser::{to_bytes, to_term, Serializer};
|
||||||
|
@ -31,6 +32,17 @@ mod tests {
|
||||||
eprintln!("Serialized (concise): {}", ser_concise);
|
eprintln!("Serialized (concise): {}", ser_concise);
|
||||||
assert_eq!(ser_concise, expected_concise);
|
assert_eq!(ser_concise, expected_concise);
|
||||||
assert_eq!(from_bytes::<T>(ser_concise.as_bytes()).unwrap(), input);
|
assert_eq!(from_bytes::<T>(ser_concise.as_bytes()).unwrap(), input);
|
||||||
|
|
||||||
|
// let ser_str_hex = input
|
||||||
|
// .serialize(&mut Serializer {
|
||||||
|
// string_format: BytesEncoding::Switch64 {
|
||||||
|
// allow_whitespace: true,
|
||||||
|
// },
|
||||||
|
// bytes_format: BytesEncoding::Hex { split: true },
|
||||||
|
// })
|
||||||
|
// .unwrap()
|
||||||
|
// .encode();
|
||||||
|
// panic!("{}", debug(&ser_str_hex));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,11 +2,15 @@ use serde::{ser, Serialize};
|
||||||
|
|
||||||
use crate::enc::*;
|
use crate::enc::*;
|
||||||
use crate::serde::error::{Error, Result};
|
use crate::serde::error::{Error, Result};
|
||||||
|
use crate::BytesEncoding;
|
||||||
use serde::ser::Error as SerError;
|
use serde::ser::Error as SerError;
|
||||||
|
|
||||||
/// Serde serializer for nettext
|
/// Serde serializer for nettext
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
pub struct Serializer;
|
pub struct Serializer {
|
||||||
|
pub string_format: BytesEncoding,
|
||||||
|
pub bytes_format: BytesEncoding,
|
||||||
|
}
|
||||||
|
|
||||||
/// Serialize value to nettext encoder term
|
/// Serialize value to nettext encoder term
|
||||||
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
pub fn to_term<T>(value: &T) -> Result<Term<'static>>
|
||||||
|
@ -90,11 +94,11 @@ impl<'a> ser::Serializer for &'a mut Serializer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
fn serialize_str(self, v: &str) -> Result<Self::Ok> {
|
||||||
Ok(bytes(v.as_bytes()))
|
Ok(bytes_format(v.as_bytes(), self.string_format))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok> {
|
||||||
Ok(bytes(v))
|
Ok(bytes_format(v, self.bytes_format))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_none(self) -> Result<Self::Ok> {
|
fn serialize_none(self) -> Result<Self::Ok> {
|
||||||
|
@ -309,7 +313,10 @@ impl ser::SerializeMap for MapSerializer {
|
||||||
where
|
where
|
||||||
T: ?Sized + Serialize,
|
T: ?Sized + Serialize,
|
||||||
{
|
{
|
||||||
let mut ser = Serializer;
|
let mut ser = Serializer {
|
||||||
|
string_format: self.ser.string_format.without_whitespace(),
|
||||||
|
bytes_format: self.ser.bytes_format.without_whitespace(),
|
||||||
|
};
|
||||||
self.next = Some(key.serialize(&mut ser)?.encode());
|
self.next = Some(key.serialize(&mut ser)?.encode());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
131
src/switch64.rs
Normal file
131
src/switch64.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
//! The Switch64 encoding for text strings
|
||||||
|
//!
|
||||||
|
//! Allowed characters are encoded as-is.
|
||||||
|
//! Others are encoded using base64.
|
||||||
|
//! Plain parts and base64-encoded parts are separated by a backslasah `\`
|
||||||
|
|
||||||
|
use crate::{SWITCH64_EXTRA_CHARS, SWITCH64_SEPARATOR};
|
||||||
|
|
||||||
|
pub fn encode(bytes: &[u8], allow_whitespace: bool) -> Vec<u8> {
|
||||||
|
let mut output = Vec::with_capacity(bytes.len());
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos < bytes.len() {
|
||||||
|
// Determine how many bytes to copy as-is
|
||||||
|
let cnt = bytes[pos..]
|
||||||
|
.iter()
|
||||||
|
.take_while(|c| is_valid_plaintext_char(**c, allow_whitespace))
|
||||||
|
.count();
|
||||||
|
|
||||||
|
// Copy those bytes as-is
|
||||||
|
output.extend_from_slice(&bytes[pos..pos + cnt]);
|
||||||
|
pos += cnt;
|
||||||
|
|
||||||
|
// If some bytes remain, switch to base64 encoding
|
||||||
|
if pos < bytes.len() {
|
||||||
|
output.push(SWITCH64_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count how many bytes to write as base64
|
||||||
|
// We stop at the first position where we find three consecutive
|
||||||
|
// characters to encode as-is
|
||||||
|
let mut b64end = bytes.len();
|
||||||
|
for i in pos..bytes.len() {
|
||||||
|
if i + 3 > bytes.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if bytes[i..i + 3]
|
||||||
|
.iter()
|
||||||
|
.all(|c| is_valid_plaintext_char(*c, allow_whitespace))
|
||||||
|
{
|
||||||
|
b64end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.extend_from_slice(
|
||||||
|
base64::encode_config(&bytes[pos..b64end], base64::URL_SAFE_NO_PAD).as_bytes(),
|
||||||
|
);
|
||||||
|
pos = b64end;
|
||||||
|
|
||||||
|
if pos < bytes.len() {
|
||||||
|
output.push(SWITCH64_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(bytes: &[u8]) -> Result<Vec<u8>, base64::DecodeError> {
|
||||||
|
let mut output = Vec::with_capacity(bytes.len());
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos < bytes.len() {
|
||||||
|
let cnt = bytes[pos..]
|
||||||
|
.iter()
|
||||||
|
.take_while(|c| **c != SWITCH64_SEPARATOR)
|
||||||
|
.count();
|
||||||
|
output.extend_from_slice(&bytes[pos..pos + cnt]);
|
||||||
|
pos += cnt + 1;
|
||||||
|
|
||||||
|
if pos >= bytes.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cnt = bytes[pos..]
|
||||||
|
.iter()
|
||||||
|
.take_while(|c| **c != SWITCH64_SEPARATOR)
|
||||||
|
.count();
|
||||||
|
output.extend_from_slice(&base64::decode_config(
|
||||||
|
&bytes[pos..pos + cnt],
|
||||||
|
base64::URL_SAFE_NO_PAD,
|
||||||
|
)?);
|
||||||
|
pos += cnt + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_valid_plaintext_char(c: u8, allow_whitespace: bool) -> bool {
|
||||||
|
c.is_ascii_alphanumeric()
|
||||||
|
|| (allow_whitespace && c.is_ascii_whitespace())
|
||||||
|
|| SWITCH64_EXTRA_CHARS.contains(&c)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::debug;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode() {
|
||||||
|
assert_eq!(debug(&encode(&b"hello world"[..], true)), "hello world");
|
||||||
|
assert_eq!(debug(&encode(&b"hello, world!"[..], true)), "hello, world!");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode() {
|
||||||
|
assert_eq!(debug(&decode(&b"hello world"[..]).unwrap()), "hello world");
|
||||||
|
assert_eq!(
|
||||||
|
debug(&decode(&b"hello\\LA\\ world\\IQ"[..]).unwrap()),
|
||||||
|
"hello, world!"
|
||||||
|
);
|
||||||
|
assert_eq!(debug(&decode(&b"\\LDssQCQ7OA"[..]).unwrap()), ",;,@$;8");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encdec() {
|
||||||
|
for s in [
|
||||||
|
br#"assert_eq!(debug(&decode(&b"hello\\LA\\ world\\IQ"[..]).unwrap()), "hello, world!");"#.to_vec(),
|
||||||
|
br#"- a list, which may contain any number of any kind of terms (can be mixed)"#.to_vec(),
|
||||||
|
base64::decode("dVcG5EzJqGP/2ZGkVu4ewzfAug1W96tb2KiBOVyPUXfw8uD34DEepW/PPqRzi0HL").unwrap(),
|
||||||
|
br#",;,@$;8"#.to_vec()
|
||||||
|
] {
|
||||||
|
assert_eq!(decode(&encode(&s, true)).unwrap(), s);
|
||||||
|
assert_eq!(decode(&encode(&s, false)).unwrap(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue