MAJOR switch to pocket

This commit is contained in:
Mike Dilger 2024-05-02 09:57:56 +12:00
parent 3c9c442bb5
commit 02de9e2038
43 changed files with 898 additions and 5803 deletions

401
Cargo.lock generated
View File

@ -19,24 +19,24 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.69"
version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
"cc",
@ -61,9 +61,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
@ -92,15 +92,9 @@ dependencies = [
[[package]]
name = "bitflags"
version = "1.3.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
dependencies = [
"serde",
]
@ -114,12 +108,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bytemuck"
version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f"
[[package]]
name = "byteorder"
version = "1.5.0"
@ -128,18 +116,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
[[package]]
name = "cfg-if"
@ -151,7 +136,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
name = "chorus"
version = "1.3.0"
dependencies = [
"chorus-lib",
"base64 0.22.1",
"dashmap",
"env_logger",
"futures",
@ -160,8 +145,12 @@ dependencies = [
"lazy_static",
"log",
"parking_lot",
"pocket-db",
"pocket-types",
"rustls-pemfile",
"secp256k1",
"serde",
"speedy",
"tempfile",
"textnonce",
"tokio",
@ -171,25 +160,10 @@ dependencies = [
]
[[package]]
name = "chorus-lib"
version = "1.3.0"
dependencies = [
"base64 0.22.0",
"futures",
"heed",
"hyper",
"hyper-tungstenite",
"log",
"mmap-append",
"secp256k1",
"serde",
"speedy",
"tempfile",
"tokio",
"tokio-rustls",
"toml",
"url",
]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cpufeatures"
@ -240,9 +214,22 @@ dependencies = [
[[package]]
name = "data-encoding"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn 1.0.109",
]
[[package]]
name = "digest"
@ -294,9 +281,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.0.1"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "fnv"
@ -369,7 +356,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -425,9 +412,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.12"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
@ -442,17 +429,16 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hashbrown"
version = "0.14.3"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heed"
version = "0.20.0-alpha.8"
source = "git+https://github.com/meilisearch/heed?rev=64fd6fec293c0dee94855b8267557ce03e7ce5d8#64fd6fec293c0dee94855b8267557ce03e7ce5d8"
version = "0.20.0-alpha.9"
source = "git+https://github.com/meilisearch/heed?rev=036ac23f73a021894974b9adc815bc95b3e0482a#036ac23f73a021894974b9adc815bc95b3e0482a"
dependencies = [
"bitflags 2.4.2",
"bytemuck",
"bitflags",
"byteorder",
"heed-traits",
"heed-types",
@ -467,16 +453,15 @@ dependencies = [
[[package]]
name = "heed-traits"
version = "0.20.0-alpha.8"
source = "git+https://github.com/meilisearch/heed?rev=64fd6fec293c0dee94855b8267557ce03e7ce5d8#64fd6fec293c0dee94855b8267557ce03e7ce5d8"
version = "0.20.0-alpha.9"
source = "git+https://github.com/meilisearch/heed?rev=036ac23f73a021894974b9adc815bc95b3e0482a#036ac23f73a021894974b9adc815bc95b3e0482a"
[[package]]
name = "heed-types"
version = "0.20.0-alpha.8"
source = "git+https://github.com/meilisearch/heed?rev=64fd6fec293c0dee94855b8267557ce03e7ce5d8#64fd6fec293c0dee94855b8267557ce03e7ce5d8"
version = "0.20.0-alpha.9"
source = "git+https://github.com/meilisearch/heed?rev=036ac23f73a021894974b9adc815bc95b3e0482a#036ac23f73a021894974b9adc815bc95b3e0482a"
dependencies = [
"bincode",
"bytemuck",
"byteorder",
"heed-traits",
"serde",
@ -485,9 +470,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.3.6"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hex-conservative"
@ -497,9 +482,9 @@ checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2"
[[package]]
name = "http"
version = "0.2.11"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
@ -583,9 +568,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.2.3"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
@ -604,9 +589,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
@ -616,9 +601,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "linux-raw-sys"
@ -629,7 +614,7 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lmdb-master-sys"
version = "0.1.0"
source = "git+https://github.com/meilisearch/heed?rev=64fd6fec293c0dee94855b8267557ce03e7ce5d8#64fd6fec293c0dee94855b8267557ce03e7ce5d8"
source = "git+https://github.com/meilisearch/heed?rev=036ac23f73a021894974b9adc815bc95b3e0482a#036ac23f73a021894974b9adc815bc95b3e0482a"
dependencies = [
"cc",
"doxygen-rs",
@ -638,9 +623,9 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.4.11"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
@ -648,15 +633,15 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memmap2"
@ -669,9 +654,9 @@ dependencies = [
[[package]]
name = "memoffset"
version = "0.9.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
@ -687,9 +672,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
@ -733,9 +718,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "page_size"
version = "0.5.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
@ -743,9 +728,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.12.1"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
dependencies = [
"lock_api",
"parking_lot_core",
@ -753,15 +738,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.9"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
"windows-targets 0.52.5",
]
[[package]]
@ -800,7 +785,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -814,9 +799,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.13"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "pin-utils"
@ -824,6 +809,25 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pocket-db"
version = "0.1.0"
source = "git+https://github.com/mikedilger/pocket?branch=master#257d096fb3ed2347f30b6c0b93346dff6f8ee395"
dependencies = [
"heed",
"mmap-append",
"pocket-types",
]
[[package]]
name = "pocket-types"
version = "0.1.0"
source = "git+https://github.com/mikedilger/pocket?branch=master#257d096fb3ed2347f30b6c0b93346dff6f8ee395"
dependencies = [
"derive_more",
"secp256k1",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -832,18 +836,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@ -907,7 +911,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.12",
"getrandom 0.2.15",
]
[[package]]
@ -921,18 +925,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.4.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 1.3.2",
"bitflags",
]
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
@ -942,9 +946,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
@ -953,9 +957,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "ring"
@ -965,7 +969,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.12",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
@ -979,12 +983,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.31"
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"bitflags 2.4.2",
"semver",
]
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
@ -993,9 +1006,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.10"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
@ -1065,30 +1078,36 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.197"
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
version = "1.0.200"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
name = "serde_json"
version = "1.0.114"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"itoa",
"ryu",
@ -1117,9 +1136,9 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
@ -1141,18 +1160,18 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.13.1"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.5"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@ -1173,7 +1192,7 @@ checksum = "658f2ca5276b92c3dfd65fa88316b4e032ace68f88d7570b43967784c0bac5ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -1184,9 +1203,20 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "syn"
version = "2.0.50"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [
"proc-macro2",
"quote",
@ -1204,9 +1234,9 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.10.0"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
@ -1235,22 +1265,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.57"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.57"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -1270,9 +1300,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.36.0"
version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
dependencies = [
"backtrace",
"bytes",
@ -1295,7 +1325,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.60",
]
[[package]]
@ -1322,9 +1352,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.10"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
@ -1343,9 +1373,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.6"
version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
"indexmap",
"serde",
@ -1424,9 +1454,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
dependencies = [
"tinyvec",
]
@ -1499,11 +1529,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@ -1527,7 +1557,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
"windows-targets 0.52.5",
]
[[package]]
@ -1547,17 +1577,18 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
"windows_aarch64_gnullvm 0.52.5",
"windows_aarch64_msvc 0.52.5",
"windows_i686_gnu 0.52.5",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.5",
"windows_x86_64_gnu 0.52.5",
"windows_x86_64_gnullvm 0.52.5",
"windows_x86_64_msvc 0.52.5",
]
[[package]]
@ -1568,9 +1599,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
@ -1580,9 +1611,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
@ -1592,9 +1623,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
@ -1604,9 +1641,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
@ -1616,9 +1653,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
@ -1628,9 +1665,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
@ -1640,15 +1677,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.2"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]

View File

@ -1,6 +1,33 @@
[workspace]
members = [
"chorus-lib",
"chorus-bin",
]
resolver = "2"
[package]
name = "chorus"
version = "1.3.0"
description = "A personal relay for nostr"
authors = ["Mike Dilger <mike@mikedilger.com>"]
license = "MIT"
repository = "https://github.com/mikedilger/chorus"
edition = "2021"
[dependencies]
base64 = "0.22"
dashmap = "5.5"
env_logger = "0.10"
futures = "0.3"
hyper = { version = "0.14", features = [ "http1", "server", "runtime", "stream" ] }
hyper-tungstenite = "0.11"
lazy_static = "1.4"
log = "0.4"
pocket-types = { git = "https://github.com/mikedilger/pocket", branch = "master" }
pocket-db = { git = "https://github.com/mikedilger/pocket", branch = "master" }
parking_lot = "0.12"
rustls-pemfile = "1.0"
secp256k1 = { version = "0.28", features = [ "hashes", "global-context", "rand-std" ] }
serde = { version = "1.0", features = ["derive"] }
speedy = "0.8"
textnonce = "1"
tokio = { version = "1", features = [ "full" ] }
tokio-rustls = "0.24"
toml = "0.8"
url = "2.5"
[dev-dependencies]
tempfile = "3"

View File

@ -1,29 +0,0 @@
[package]
name = "chorus"
version = "1.3.0"
description = "A personal relay for nostr"
authors = ["Mike Dilger <mike@mikedilger.com>"]
license = "MIT"
repository = "https://github.com/mikedilger/chorus"
edition = "2021"
[dependencies]
chorus-lib = { path = "../chorus-lib" }
dashmap = "5.5"
env_logger = "0.10"
futures = "0.3"
hyper = { version = "0.14", features = [ "http1", "server", "runtime", "stream" ] }
hyper-tungstenite = "0.11"
lazy_static = "1.4"
log = "0.4"
parking_lot = "0.12"
rustls-pemfile = "1.0"
serde = { version = "1.0", features = ["derive"] }
textnonce = "1"
tokio = { version = "1", features = [ "full" ] }
tokio-rustls = "0.24"
toml = "0.8"
url = "2.5"
[dev-dependencies]
tempfile = "3"

View File

@ -1,38 +0,0 @@
use chorus_lib::config::{Config, FriendlyConfig};
use chorus_lib::error::Error;
use chorus_lib::store::Store;
use std::env;
use std::fs::OpenOptions;
use std::io::Read;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_compress <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let config: Config = friendly_config.into_config()?;
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
Store::rebuild(&config)?;
Ok(())
}

View File

@ -1,54 +0,0 @@
use chorus_lib::config::{Config, FriendlyConfig};
use chorus_lib::error::Error;
use chorus_lib::store::Store;
use chorus_lib::types::{Event, Filter};
use std::env;
use std::fs::OpenOptions;
use std::io::Read;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_dump <chorus_config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let mut config: Config = friendly_config.into_config()?;
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
// Force allow of scraping (this program is a scraper)
config.allow_scraping = true;
// Setup store
let store = Store::new(&config)?;
let mut buffer: [u8; 128] = [0; 128];
let (_incount, _outcount, filter) = Filter::from_json(b"{}", &mut buffer)?;
let screen = |_: &Event<'_>| -> bool { true };
let mut events = store.find_events(filter, screen, &config)?;
for event in events.drain(..) {
let bytes = event.as_json()?;
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
println!("{s}");
}
Ok(())
}

View File

@ -1,50 +0,0 @@
use chorus_lib::config::{Config, FriendlyConfig};
use chorus_lib::error::Error;
use chorus_lib::store::Store;
use std::env;
use std::fs::OpenOptions;
use std::io::Read;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_moderate <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let mut config: Config = friendly_config.into_config()?;
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
// Force allow of scraping (this program is a scraper)
config.allow_scraping = true;
// Setup store
let store = Store::new(&config)?;
for (id, approved) in store.dump_event_approvals()? {
println!("ID {} = {}", id, approved);
}
for (pubkey, approved) in store.dump_pubkey_approvals()? {
println!("PUBKEY {} = {}", pubkey, approved);
}
Ok(())
}

View File

@ -1,27 +0,0 @@
[package]
name = "chorus-lib"
version = "1.3.0"
description = "A personal relay for nostr"
authors = ["Mike Dilger <mike@mikedilger.com>"]
license = "MIT"
repository = "https://github.com/mikedilger/chorus"
edition = "2021"
[dependencies]
base64 = "0.22"
futures = "0.3"
heed = { git = "https://github.com/meilisearch/heed", rev = "64fd6fec293c0dee94855b8267557ce03e7ce5d8" }
hyper = { version = "0.14", features = [ "http1", "server", "runtime", "stream" ] }
hyper-tungstenite = "0.11"
log = "0.4"
mmap-append = "0.2"
secp256k1 = { version = "0.28", features = [ "hashes", "global-context", "rand-std" ] }
serde = { version = "1.0", features = ["derive"] }
speedy = "0.8"
tokio = { version = "1", features = [ "full" ] }
tokio-rustls = "0.24"
toml = "0.8"
url = "2.5"
[dev-dependencies]
tempfile = "3"

View File

@ -1,8 +0,0 @@
include!("macros.rs");
pub mod config;
pub mod error;
pub mod ip;
pub mod reply;
pub mod store;
pub mod types;

View File

@ -1,75 +0,0 @@
static HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
#[allow(clippy::zero_prefixed_literal)]
static HEX_INVERSE: [u8; 128] = {
const __: u8 = 255;
[
// 1 2 3 4 5 6 7 8 9 A B C D E F
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 0
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 1
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2
00, 01, 02, 03, 04, 05, 06, 07, 08, 09, __, __, __, __, __, __, // 3
__, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 4
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 5
__, 10, 11, 12, 13, 14, 15, __, __, __, __, __, __, __, __, __, // 6
__, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7
]
};
macro_rules! write_hex {
($input:expr, $output:expr, $bytelen:expr) => {{
assert_eq!($input.len(), $bytelen);
if $output.len() != $bytelen * 2 {
Err(crate::error::ChorusError::BufferTooSmall.into())
} else {
for (i, byte) in $input.iter().enumerate() {
$output[i * 2] = crate::HEX_CHARS[((byte & 0xF0) >> 4) as usize];
$output[i * 2 + 1] = crate::HEX_CHARS[(byte & 0x0F) as usize];
}
Ok(())
}
}};
}
macro_rules! read_hex {
($input:expr, $output:expr, $bytelen:expr) => {{
assert_eq!($output.len(), $bytelen);
if $input.len() != $bytelen * 2 {
Err(Into::<crate::error::Error>::into(crate::error::ChorusError::EndOfInput))
} else {
let mut i = 0;
loop {
let high = crate::HEX_INVERSE[$input[i * 2] as usize];
if high == 255 {
break Err(crate::error::ChorusError::BadHexInput.into());
}
let low = crate::HEX_INVERSE[$input[i * 2 + 1] as usize];
if low == 255 {
break Err(crate::error::ChorusError::BadHexInput.into());
}
$output[i] = high * 16 + low;
i += 1;
if i == $bytelen {
break Ok(());
}
}
}
}};
}
macro_rules! parse_u16 {
($input:expr, $start:expr) => {
u16::from_ne_bytes($input[$start .. $start+2].try_into().unwrap())
}
}
macro_rules! parse_u32 {
($input:expr, $start:expr) => {
u32::from_ne_bytes($input[$start .. $start+4].try_into().unwrap())
}
}
macro_rules! parse_u64 {
($input:expr, $start:expr) => {
u64::from_ne_bytes($input[$start .. $start+8].try_into().unwrap())
}
}

View File

@ -1,245 +0,0 @@
use crate::error::{ChorusError, Error};
use crate::types::Event;
use mmap_append::MmapAppend;
use std::fs::{File, OpenOptions};
use std::mem;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
/// This is the size of the initial event map, and also how large it grows by
/// when we need to grow it. This should be a multiple of the page size (4096)
// While debugging, we like to use a small value to increase the frequency of
// resizing, to help detect if there are problems in the algorithm.
#[cfg(debug_assertions)]
const EVENT_MAP_CHUNK: usize = 2048;
#[cfg(not(debug_assertions))]
const EVENT_MAP_CHUNK: usize = 4096 * 1024; // grow by 4 megabytes at a time
/// An EventStore is a fast storage facility for events.
#[derive(Debug)]
pub struct EventStore {
// the Mmap doesn't need us to keep the file, but we keep it for resizing.
event_map_file: File,
event_map_file_len: AtomicUsize,
// This is a linear sequence of events in an append-only memory mapped file which
// internally remembers the 'end' pointer and internally prevents multiple writers.
event_map: MmapAppend,
// Whether or not event offsets are aligned on 8-byte boundaries. If they are,
// event retrieval is more CPU efficient, and a tiny bit more space is used.
// Early code did not align the data. If you specify the wrong value here that
// doesnt match the data, the iterator will fail badly.
aligned: bool,
}
impl EventStore {
/// Create a new `EventStore`. The `event_map_file` is the eventually large file
/// that holds all the events.
pub fn new<P: AsRef<Path>>(event_map_file: P, aligned: bool) -> Result<EventStore, Error> {
// Open the event map file, possibly creating if it isn't there
let event_map_file = OpenOptions::new()
.read(true)
.write(true)
.truncate(false)
.create(true)
.open(event_map_file)?;
// Get it's size
let metadata = event_map_file.metadata()?;
let mut len = metadata.len() as usize;
// Determine if we just created it
// (not long enough for the required end offset)
let new = len < mem::size_of::<usize>();
// If brand new:
if new {
// grow to initial size
len = EVENT_MAP_CHUNK;
event_map_file.set_len(EVENT_MAP_CHUNK as u64)?;
}
// Memory map it
let event_map = unsafe { MmapAppend::new(&event_map_file, new)? };
log::info!(
"Event Store: new={:?} end={} len={}",
new,
event_map.get_end(),
len
);
Ok(EventStore {
event_map_file,
event_map_file_len: AtomicUsize::new(len),
event_map,
aligned,
})
}
/// Get whether events are aligned in the map
#[inline]
pub fn is_aligned(&self) -> bool {
self.aligned
}
/// Get the number of bytes used in the event map
#[inline]
pub fn read_event_map_end(&self) -> usize {
self.event_map.get_end()
}
/// Get an event by its offset in the map
pub fn get_event_by_offset(&self, offset: usize) -> Result<Event, Error> {
if offset >= self.read_event_map_end() {
return Err(ChorusError::EndOfInput.into());
}
let event = Event::delineate(&self.event_map[offset..])?;
Ok(event)
}
// This stores an event
// It does NOT validate the event.
// It does NOT check first if the event is already stored, so it could store a duplicate
// It does NOT record the event into any indexes
// But it does grow the file if needed and returns the offset where it was stored
pub fn store_event(&self, event: &Event) -> Result<usize, Error> {
if self.aligned {
let mut end = self.event_map.get_end();
if end % 8 != 0 {
let padding = 8 - (end % 8);
end += padding;
assert_eq!(end % 8, 0);
self.event_map.append(padding, |_| Ok(padding))?;
}
}
let event_size = event.length();
loop {
let result = self.event_map.append(event_size, |dst| event.macopy(dst));
match result {
Ok(offset) => return Ok(offset),
Err(e) => {
if e.kind() == std::io::ErrorKind::Other {
if e.to_string() == "Out of space" {
// Determine the new size
let new_file_len = {
let file_len = self.event_map_file_len.load(Ordering::Relaxed);
file_len + EVENT_MAP_CHUNK
};
// Grow the file
self.event_map_file.set_len(new_file_len as u64)?;
// Resize the memory map
self.event_map.resize(new_file_len)?;
// Save this new length
self.event_map_file_len
.store(new_file_len, Ordering::Relaxed);
// Try again
continue;
} else {
return Err(e.into());
}
} else {
return Err(e.into());
}
}
}
}
}
pub fn iter(&self) -> EventStoreIter<'_> {
EventStoreIter {
store: self,
offset: mmap_append::HEADER_SIZE,
}
}
}
pub struct EventStoreIter<'a> {
store: &'a EventStore,
offset: usize,
}
impl<'a> Iterator for EventStoreIter<'a> {
type Item = Result<Event<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.store.get_event_by_offset(self.offset) {
Err(e) => {
if matches!(e.inner, ChorusError::EndOfInput) {
None
} else {
Some(Err(e))
}
}
Ok(event) => {
self.offset += event.length();
if self.store.aligned && self.offset % 8 != 0 {
self.offset += 8 - (self.offset % 8);
assert_eq!(self.offset % 8, 0);
}
Some(Ok(event))
}
}
}
}
#[cfg(test)]
mod tests {
use crate::store::EventStore;
use crate::types::Event;
#[test]
fn test_event_store() {
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().join("mmap");
let store = EventStore::new(&path, false).unwrap();
println!("Event map has {} used bytes", store.read_event_map_end());
let e1str = br#"{"id":"000000005ccb8402fe9af2ecc72ca1dbbf2dbeb9a0c6353b7f8198a65106f04a","pubkey":"7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805","created_at":1677930312,"kind":1,"tags":[["p","fe1d10131ca6103715d261f1615a8cd31f6b68a3d1a3272aab8e9e83f787126e"],["e","f76a6f60df029c388e98d4e97c67424cb71e34455302580d3aa48d0be96dfaef","wss://nos.lol","root"],["e","f529676213b123f55117ae9b93ca7dd6c85c872b7c260da7dbf60d71875879f6","wss://brb.io","reply"],["nonce","12912720851603613541","25"]],"content":"https://www.nostr.guru/p/fe1d10131ca6103715d261f1615a8cd31f6b68a3d1a3272aab8e9e83f787126e\nHere I can see only the today contact list.\n\nI tried to query relay.damus.io and nostr.wine but I can get only this last contact list, perhaps they have a time limit; you could ask some relay owner for a backup.\nIf can be useful this is the REQ filter to use:\n{\"kinds\": [3], \"authors\": [\"fe1d10131ca6103715d261f1615a8cd31f6b68a3d1a3272aab8e9e83f787126e\"], \"since\": 1672569529}","sig":"7c72d15cf9b4244cb8c49564d3735d0ac620dca69253956975def12054bc50217307f062f3e9ea879de8bae756c844a9315f03aabbb5f5fd94f9fc9beb76f457"}"#;
let mut buffer1: Vec<u8> = Vec::with_capacity(4096);
buffer1.resize(4096, 0);
let (_insize, event1) = Event::from_json(&e1str[..], &mut buffer1).unwrap();
let offset1 = store.store_event(&event1).unwrap();
let e2str = br#"{"id":"00000000a6fa8ee15b17fcc5bb49f09f85c15cddf5349986db09fddc0a123f7d","pubkey":"7cc328a08ddb2afdf9f9be77beff4c83489ff979721827d628a542f32a247c0e","created_at":1678835260,"kind":1,"tags":[["p","0000000033f569c7069cdec575ca000591a31831ebb68de20ed9fb783e3fc287"],["e","52338357568d06379ad2412a2a2033f23f224754d74148bbfc659b872c9477c4","wss://relay.damus.io/","root"],["nonce","9223372036941030112","32"]],"content":"oh shit, it got released?! no wayyy","sig":"ec9bcdcaa843a2a275857999556136d2b3cc47dac52c899d313dd70291b9661590d4c10b13680bbfa685d67d319798cdfa3ca58f61af5f8acffd0e25bca5ab95"}"#;
let mut buffer2: Vec<u8> = Vec::with_capacity(4096);
buffer2.resize(4096, 0);
let (_insize, event2) = Event::from_json(&e2str[..], &mut buffer2).unwrap();
let offset2 = store.store_event(&event2).unwrap();
let e3str = br#"{"id":"00000000ad0efde5b63e9b24b12a586dc98df372e1fd6f96ac6ad24ea2ed1350","pubkey":"c5fb6ecc876e0458e3eca9918e370cbcd376901c58460512fe537a46e58c38bb","created_at":1681739201,"kind":7,"tags":[["e","193bd20beb8fc13f4218ea106928c3be81ee3b2ad2b1bdbdd2c55efd859a195a","wss://eden.nostr.land/"],["p","3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24"],["client","gossip"],["nonce","2305843009213833122","22"]],"content":"+","sig":"f344111c221d2fea5f006865b98b0767b40ed1cc2907d8a325a8dea4b98414d008296ff4f4bd4666d52ec86ffd4739e807c6655ce43de98b473326e30957fcb2"}"#;
let mut buffer3: Vec<u8> = Vec::with_capacity(4096);
buffer3.resize(4096, 0);
let (_insize, event3) = Event::from_json(&e3str[..], &mut buffer3).unwrap();
let offset3 = store.store_event(&event3).unwrap();
println!("Event map has {} used bytes", store.read_event_map_end());
if let Ok(event) = store.get_event_by_offset(offset1) {
assert_eq!(event, event1);
} else {
panic!("EVENT 1 IS WRONG");
}
if let Ok(event) = store.get_event_by_offset(offset2) {
assert_eq!(event, event2);
} else {
panic!("EVENT 2 IS WRONG");
}
if let Ok(event) = store.get_event_by_offset(offset3) {
assert_eq!(event, event3);
} else {
panic!("EVENT 3 IS WRONG");
}
}
}

View File

@ -1,766 +0,0 @@
use crate::error::Error;
use crate::ip::{HashedIp, IpData};
use crate::types::{Event, Id, Kind, Pubkey, Time};
use heed::types::{OwnedType, UnalignedSlice, Unit, U8};
use heed::{Database, Env, EnvFlags, EnvOpenOptions, RoIter, RoRange, RoTxn, RwTxn};
use speedy::{Readable, Writable};
use std::fs;
use std::ops::Bound;
mod retired;
const FALSE: &[u8] = &[0];
const TRUE: &[u8] = &[1];
#[derive(Debug)]
pub struct Lmdb {
env: Env,
general: Database<UnalignedSlice<u8>, UnalignedSlice<u8>>,
i_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
ci_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
tc_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
ac_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
akc_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
atc_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
ktc_index: Database<UnalignedSlice<u8>, OwnedType<usize>>,
// this is for events deleted by other events
deleted_ids: Database<UnalignedSlice<u8>, Unit>,
approved_events: Database<UnalignedSlice<u8>, U8>,
approved_pubkeys: Database<UnalignedSlice<u8>, U8>,
ip_data: Database<UnalignedSlice<u8>, UnalignedSlice<u8>>,
}
impl Lmdb {
pub fn new(directory: &str) -> Result<Lmdb, Error> {
let mut builder = EnvOpenOptions::new();
unsafe {
builder.flags(EnvFlags::NO_TLS);
}
builder.max_dbs(32);
builder.map_size(1048576 * 1024 * 24); // 24 GB
fs::create_dir_all(directory)?;
let env = match builder.open(directory) {
Ok(env) => env,
Err(e) => {
log::error!("Unable to open LMDB at {}", directory);
return Err(e.into());
}
};
// Open/Create maps
let mut txn = env.write_txn()?;
let general = env
.database_options()
.types::<UnalignedSlice<u8>, UnalignedSlice<u8>>()
.create(&mut txn)?;
let i_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("ids")
.create(&mut txn)?;
let ci_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("ci")
.create(&mut txn)?;
let tc_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("tci")
.create(&mut txn)?;
let ac_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("aci")
.create(&mut txn)?;
let akc_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("akci")
.create(&mut txn)?;
let atc_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("atci")
.create(&mut txn)?;
let ktc_index = env
.database_options()
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("ktci")
.create(&mut txn)?;
let deleted_ids = env
.database_options()
.types::<UnalignedSlice<u8>, Unit>()
.name("deleted-ids")
.create(&mut txn)?;
let approved_events = env
.database_options()
.types::<UnalignedSlice<u8>, U8>()
.name("approved-events")
.create(&mut txn)?;
let approved_pubkeys = env
.database_options()
.types::<UnalignedSlice<u8>, U8>()
.name("approved-pubkeys")
.create(&mut txn)?;
let ip_data = env
.database_options()
.types::<UnalignedSlice<u8>, UnalignedSlice<u8>>()
.name("ip_data")
.create(&mut txn)?;
txn.commit()?;
let lmdb = Lmdb {
env,
general,
i_index,
ci_index,
tc_index,
ac_index,
akc_index,
atc_index,
ktc_index,
deleted_ids,
approved_events,
approved_pubkeys,
ip_data,
};
Ok(lmdb)
}
/// Sync the data to disk. This happens periodically, but sometimes it's useful to force
/// it.
pub fn sync(&self) -> Result<(), Error> {
self.env.force_sync()?;
Ok(())
}
/// Get a read transaction
pub fn read_txn(&self) -> Result<RoTxn, Error> {
Ok(self.env.read_txn()?)
}
/// Get a write transaction
pub fn write_txn(&self) -> Result<RwTxn, Error> {
Ok(self.env.write_txn()?)
}
pub fn log_stats(&self) -> Result<(), Error> {
let txn = self.read_txn()?;
if !self.get_if_events_are_aligned()? {
log::warn!("Events are not aligned on 8-byte offsets. You should run `chorus_compress` to rebuild your data (but it will keep working regardless)");
}
if let Ok(count) = self.i_index.len(&txn) {
log::info!("Index: id ({} entries, {} bytes)", count, count * (32 + 8));
}
if let Ok(count) = self.ci_index.len(&txn) {
log::info!(
"Index: created_at+id ({} entries, {} bytes)",
count,
count * (40 + 8)
);
}
if let Ok(count) = self.tc_index.len(&txn) {
log::info!(
"Index: tag+created_at+id ({} entries, {} bytes)",
count,
count * (223 + 8)
);
}
if let Ok(count) = self.ac_index.len(&txn) {
log::info!(
"Index: author+created_at+id ({} entries, {} bytes)",
count,
count * (72 + 8)
);
}
if let Ok(count) = self.akc_index.len(&txn) {
log::info!(
"Index: author+kind+created_at+id ({} entries, {} bytes)",
count,
count * (74 + 8)
);
}
if let Ok(count) = self.atc_index.len(&txn) {
log::info!(
"Index: author+tags+created_at+id ({} entries, {} bytes)",
count,
count * (255 + 8)
);
}
if let Ok(count) = self.ktc_index.len(&txn) {
log::info!(
"Index: kind+tags+created_at+id ({} entries, {} bytes)",
count,
count * (225 + 8)
);
}
if let Ok(count) = self.deleted_ids.len(&txn) {
log::info!("{} deleted events", count);
}
if let Ok(count) = self.ip_data.len(&txn) {
log::info!("{count} IP addresses reputationally tracked");
}
Ok(())
}
pub fn get_migration_level(&self, txn: &RoTxn<'_>) -> Result<u32, Error> {
let zero_bytes = 0_u32.to_be_bytes();
let migration_level_bytes = self
.general
.get(txn, b"migration_level")?
.unwrap_or(zero_bytes.as_slice());
Ok(u32::from_be_bytes(
migration_level_bytes[..4].try_into().unwrap(),
))
}
pub fn set_migration_level(&self, txn: &mut RwTxn<'_>, level: u32) -> Result<(), Error> {
self.general
.put(txn, b"migration_level", level.to_be_bytes().as_slice())?;
Ok(())
}
pub fn get_if_events_are_aligned(&self) -> Result<bool, Error> {
let txn = self.read_txn()?;
match self.general.get(&txn, b"events_are_aligned")? {
None => Ok(false),
Some(bytes) => match bytes[0] {
0 => Ok(false),
_ => Ok(true),
},
}
}
pub fn set_if_events_are_aligned(
&self,
txn: &mut RwTxn<'_>,
events_are_aligned: bool,
) -> Result<(), Error> {
let slice = match events_are_aligned {
false => FALSE,
true => TRUE,
};
self.general.put(txn, b"events_are_aligned", slice)?;
Ok(())
}
// Index the event
pub fn index(&self, txn: &mut RwTxn<'_>, event: &Event, offset: usize) -> Result<(), Error> {
// Index by id
self.i_index.put(txn, event.id().0.as_slice(), &offset)?;
// Index by created_at and id
self.ci_index.put(
txn,
&Self::key_ci_index(event.created_at(), event.id()),
&offset,
)?;
// Index by author and kind (with created_at and id)
self.akc_index.put(
txn,
&Self::key_akc_index(event.pubkey(), event.kind(), event.created_at(), event.id()),
&offset,
)?;
self.ac_index.put(
txn,
&Self::key_ac_index(event.pubkey(), event.created_at(), event.id()),
&offset,
)?;
for mut tsi in event.tags()?.iter() {
if let Some(tagname) = tsi.next() {
// FIXME make sure it is a letter too
if tagname.len() == 1 {
if let Some(tagvalue) = tsi.next() {
// Index by tag (with created_at and id)
self.tc_index.put(
txn,
&Self::key_tc_index(
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
&offset,
)?;
// Index by author and tag (with created_at and id)
self.atc_index.put(
txn,
&Self::key_atc_index(
event.pubkey(),
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
&offset,
)?;
// Index by kind and tag (with created_at and id)
self.ktc_index.put(
txn,
&Self::key_ktc_index(
event.kind(),
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
&offset,
)?;
}
}
}
}
Ok(())
}
// Remove the event from all indexes (except the 'id' index)
pub fn deindex(&self, txn: &mut RwTxn<'_>, event: &Event) -> Result<(), Error> {
for mut tsi in event.tags()?.iter() {
if let Some(tagname) = tsi.next() {
// FIXME make sure it is a letter too
if tagname.len() == 1 {
if let Some(tagvalue) = tsi.next() {
// Index by author and tag (with created_at and id)
self.atc_index.delete(
txn,
&Self::key_atc_index(
event.pubkey(),
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
)?;
// Index by kind and tag (with created_at and id)
self.ktc_index.delete(
txn,
&Self::key_ktc_index(
event.kind(),
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
)?;
// Index by tag (with created_at and id)
self.tc_index.delete(
txn,
&Self::key_tc_index(
tagname[0],
tagvalue,
event.created_at(),
event.id(),
),
)?;
}
}
}
}
self.ac_index.delete(
txn,
&Self::key_ac_index(event.pubkey(), event.created_at(), event.id()),
)?;
self.ci_index
.delete(txn, &Self::key_ci_index(event.created_at(), event.id()))?;
self.akc_index.delete(
txn,
&Self::key_akc_index(event.pubkey(), event.kind(), event.created_at(), event.id()),
)?;
// We leave it in the id map. If someone wants to load the replaced event by id
// they can still do it.
// self.i_index.delete(&mut txn, event.id().0.as_slice())?;
Ok(())
}
pub fn deindex_id(&self, txn: &mut RwTxn<'_>, id: Id) -> Result<(), Error> {
self.i_index.delete(txn, id.0.as_slice())?;
Ok(())
}
pub fn get_offset_by_id(&self, txn: &RoTxn<'_>, id: Id) -> Result<Option<usize>, Error> {
Ok(self.i_index.get(txn, id.0.as_slice())?)
}
pub fn is_deleted(&self, txn: &RoTxn<'_>, id: Id) -> Result<bool, Error> {
Ok(self.deleted_ids.get(txn, id.as_slice())?.is_some())
}
pub fn mark_deleted(&self, txn: &mut RwTxn<'_>, id: Id) -> Result<(), Error> {
self.deleted_ids.put(txn, id.as_slice(), &())?;
Ok(())
}
pub fn dump_deleted(&self) -> Result<Vec<Id>, Error> {
let mut output: Vec<Id> = Vec::new();
let txn = self.read_txn()?;
for i in self.deleted_ids.iter(&txn)? {
let (key, _val) = i?;
let id = Id(key[0..32].try_into().unwrap());
output.push(id);
}
Ok(output)
}
pub fn get_ip_data(&self, ip: HashedIp) -> Result<IpData, Error> {
let key = &ip.0;
let txn = self.read_txn()?;
let bytes = match self.ip_data.get(&txn, key)? {
Some(b) => b,
None => return Ok(Default::default()),
};
Ok(IpData::read_from_buffer(bytes)?)
}
pub fn update_ip_data(&self, ip: HashedIp, data: &IpData) -> Result<(), Error> {
let key = &ip.0;
let mut txn = self.write_txn()?;
let bytes = data.write_to_vec()?;
self.ip_data.put(&mut txn, key, &bytes)?;
txn.commit()?;
Ok(())
}
pub fn dump_ip_data(&self) -> Result<Vec<(HashedIp, IpData)>, Error> {
let mut output: Vec<(HashedIp, IpData)> = Vec::new();
let txn = self.read_txn()?;
for i in self.ip_data.iter(&txn)? {
let (key, val) = i?;
let hashedip = HashedIp::from_bytes(key);
let data = IpData::read_from_buffer(val)?;
output.push((hashedip, data));
}
Ok(output)
}
pub fn mark_event_approval(&self, id: Id, approval: bool) -> Result<(), Error> {
let mut txn = self.write_txn()?;
self.approved_events
.put(&mut txn, id.0.as_slice(), &(approval as u8))?;
txn.commit()?;
Ok(())
}
pub fn clear_event_approval(&self, id: Id) -> Result<(), Error> {
let mut txn = self.write_txn()?;
self.approved_events.delete(&mut txn, id.0.as_slice())?;
txn.commit()?;
Ok(())
}
pub fn get_event_approval(&self, id: Id) -> Result<Option<bool>, Error> {
let txn = self.read_txn()?;
Ok(self
.approved_events
.get(&txn, id.0.as_slice())?
.map(|u| u != 0))
}
pub fn dump_event_approvals(&self) -> Result<Vec<(Id, bool)>, Error> {
let mut output: Vec<(Id, bool)> = Vec::new();
let txn = self.read_txn()?;
for i in self.approved_events.iter(&txn)? {
let (key, val) = i?;
let id = Id(key.try_into().unwrap());
let approval: bool = val != 0;
output.push((id, approval));
}
Ok(output)
}
pub fn mark_pubkey_approval(&self, pubkey: Pubkey, approval: bool) -> Result<(), Error> {
let mut txn = self.write_txn()?;
self.approved_pubkeys
.put(&mut txn, pubkey.0.as_slice(), &(approval as u8))?;
txn.commit()?;
Ok(())
}
pub fn clear_pubkey_approval(&self, pubkey: Pubkey) -> Result<(), Error> {
let mut txn = self.write_txn()?;
self.approved_pubkeys
.delete(&mut txn, pubkey.0.as_slice())?;
txn.commit()?;
Ok(())
}
pub fn get_pubkey_approval(&self, pubkey: Pubkey) -> Result<Option<bool>, Error> {
let txn = self.read_txn()?;
Ok(self
.approved_pubkeys
.get(&txn, pubkey.0.as_slice())?
.map(|u| u != 0))
}
pub fn dump_pubkey_approvals(&self) -> Result<Vec<(Pubkey, bool)>, Error> {
let mut output: Vec<(Pubkey, bool)> = Vec::new();
let txn = self.read_txn()?;
for i in self.approved_pubkeys.iter(&txn)? {
let (key, val) = i?;
let pubkey = Pubkey(key.try_into().unwrap());
let approval: bool = val != 0;
output.push((pubkey, approval));
}
Ok(output)
}
pub fn i_iter<'a>(
&'a self,
txn: &'a RoTxn,
) -> Result<RoIter<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
Ok(self.i_index.iter(txn)?)
}
pub fn ci_iter<'a>(
&'a self,
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_ci_index(until, Id([0; 32]));
let end_prefix = Self::key_ci_index(since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.ci_index.range(txn, &range)?)
}
pub fn tc_iter<'a>(
&'a self,
tagbyte: u8,
tagvalue: &[u8],
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_tc_index(
tagbyte,
tagvalue,
until, // scan goes backwards in time
Id([0; 32]),
);
let end_prefix = Self::key_tc_index(tagbyte, tagvalue, since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.tc_index.range(txn, &range)?)
}
pub fn ac_iter<'a>(
&'a self,
author: Pubkey,
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_ac_index(author, until, Id([0; 32]));
let end_prefix = Self::key_ac_index(author, since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.ac_index.range(txn, &range)?)
}
pub fn akc_iter<'a>(
&'a self,
author: Pubkey,
kind: Kind,
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_akc_index(author, kind, until, Id([0; 32]));
let end_prefix = Self::key_akc_index(author, kind, since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.akc_index.range(txn, &range)?)
}
pub fn atc_iter<'a>(
&'a self,
author: Pubkey,
tagbyte: u8,
tagvalue: &[u8],
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_atc_index(
author,
tagbyte,
tagvalue,
until, // scan goes backwards in time
Id([0; 32]),
);
let end_prefix = Self::key_atc_index(author, tagbyte, tagvalue, since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.atc_index.range(txn, &range)?)
}
pub fn ktc_iter<'a>(
&'a self,
kind: Kind,
tagbyte: u8,
tagvalue: &[u8],
since: Time,
until: Time,
txn: &'a RoTxn,
) -> Result<RoRange<'_, UnalignedSlice<u8>, OwnedType<usize>>, Error> {
let start_prefix = Self::key_ktc_index(
kind,
tagbyte,
tagvalue,
until, // scan goes backwards in time
Id([0; 32]),
);
let end_prefix = Self::key_ktc_index(kind, tagbyte, tagvalue, since, Id([255; 32]));
let range = (
Bound::Included(&*start_prefix),
Bound::Excluded(&*end_prefix),
);
Ok(self.ktc_index.range(txn, &range)?)
}
fn key_ci_index(created_at: Time, id: Id) -> Vec<u8> {
let mut key: Vec<u8> =
Vec::with_capacity(std::mem::size_of::<Time>() + std::mem::size_of::<Id>());
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
// For looking up event by Tag
// tagletter(1) + fixlentag(182) + reversecreatedat(8) + id(32)
fn key_tc_index(letter: u8, tag_value: &[u8], created_at: Time, id: Id) -> Vec<u8> {
const PADLEN: usize = 182;
let mut key: Vec<u8> =
Vec::with_capacity(PADLEN + std::mem::size_of::<Time>() + std::mem::size_of::<Id>());
key.push(letter);
if tag_value.len() <= PADLEN {
key.extend(tag_value);
key.extend(core::iter::repeat(0).take(PADLEN - tag_value.len()));
} else {
key.extend(&tag_value[..PADLEN]);
}
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
// For looking up event by Author
// author(32) + reversecreatedat(8) + id(32)
fn key_ac_index(author: Pubkey, created_at: Time, id: Id) -> Vec<u8> {
let mut key: Vec<u8> = Vec::with_capacity(
std::mem::size_of::<Pubkey>() + std::mem::size_of::<Time>() + std::mem::size_of::<Id>(),
);
key.extend(author.as_slice());
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
// For looking up event by Author and Kind
// author(32) + kind(2) + reversecreatedat(8) + id(32)
fn key_akc_index(author: Pubkey, kind: Kind, created_at: Time, id: Id) -> Vec<u8> {
let mut key: Vec<u8> = Vec::with_capacity(
std::mem::size_of::<Pubkey>()
+ std::mem::size_of::<Kind>()
+ std::mem::size_of::<Time>()
+ std::mem::size_of::<Id>(),
);
key.extend(author.as_slice());
key.extend(kind.0.to_be_bytes());
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
// For looking up event by Author and Tag
// author(32) + tagletter(1) + fixlentag(182) + reversecreatedat(8) + id(32)
fn key_atc_index(
author: Pubkey,
letter: u8,
tag_value: &[u8],
created_at: Time,
id: Id,
) -> Vec<u8> {
const PADLEN: usize = 182;
let mut key: Vec<u8> = Vec::with_capacity(
std::mem::size_of::<Pubkey>()
+ PADLEN
+ std::mem::size_of::<Time>()
+ std::mem::size_of::<Id>(),
);
key.extend(author.as_slice());
key.push(letter);
if tag_value.len() <= PADLEN {
key.extend(tag_value);
key.extend(core::iter::repeat(0).take(PADLEN - tag_value.len()));
} else {
key.extend(&tag_value[..PADLEN]);
}
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
// For looking up event by Kind and Tag
// kind(2) + tagletter(1) + fixlentag(182) + reversecreatedat(8) + id(32)
fn key_ktc_index(
kind: Kind,
letter: u8,
tag_value: &[u8],
created_at: Time,
id: Id,
) -> Vec<u8> {
const PADLEN: usize = 182;
let mut key: Vec<u8> = Vec::with_capacity(
std::mem::size_of::<Kind>()
+ PADLEN
+ std::mem::size_of::<Time>()
+ std::mem::size_of::<Id>(),
);
key.extend(kind.0.to_be_bytes());
key.push(letter);
if tag_value.len() <= PADLEN {
key.extend(tag_value);
key.extend(core::iter::repeat(0).take(PADLEN - tag_value.len()));
} else {
key.extend(&tag_value[..PADLEN]);
}
key.extend((u64::MAX - created_at.0).to_be_bytes().as_slice());
key.extend(id.as_slice());
key
}
}

View File

@ -1,88 +0,0 @@
use super::Lmdb;
use crate::error::Error;
use crate::types::Event;
use heed::byteorder::BigEndian;
use heed::types::{UnalignedSlice, Unit, U64};
use heed::{Database, RwTxn};
impl Lmdb {
pub fn deleted_offsets(
&self,
txn: &mut RwTxn,
) -> Result<Database<U64<BigEndian>, Unit>, Error> {
Ok(self
.env
.database_options()
.types::<U64<BigEndian>, Unit>()
.name("deleted_offsets")
.create(txn)?)
}
pub fn deleted_events(
&self,
txn: &mut RwTxn,
) -> Result<Database<UnalignedSlice<u8>, Unit>, Error> {
Ok(self
.env
.database_options()
.types::<UnalignedSlice<u8>, Unit>()
.name("deleted-events")
.create(txn)?)
}
// used in migrate_to_1
pub fn index_ci_only(
&self,
txn: &mut RwTxn<'_>,
event: &Event,
offset: usize,
) -> Result<(), Error> {
self.ci_index.put(
txn,
&Self::key_ci_index(event.created_at(), event.id()),
&offset,
)?;
Ok(())
}
// used in migrate_to_2
pub fn index_ac_only(
&self,
txn: &mut RwTxn<'_>,
event: &Event,
offset: usize,
) -> Result<(), Error> {
self.ac_index.put(
txn,
&Self::key_ac_index(event.pubkey(), event.created_at(), event.id()),
&offset,
)?;
Ok(())
}
// used in migrate_to_2
pub fn index_tc_only(
&self,
txn: &mut RwTxn<'_>,
tagbyte: u8,
tagvalue: &[u8],
event: &Event,
offset: usize,
) -> Result<(), Error> {
self.tc_index.put(
txn,
&Self::key_tc_index(tagbyte, tagvalue, event.created_at(), event.id()),
&offset,
)?;
Ok(())
}
// used in migrate_to_3
pub fn clear_ip_data(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
self.ip_data.clear(txn)?;
Ok(())
}
}

View File

@ -1,115 +0,0 @@
use super::Store;
use crate::error::Error;
use crate::types::Id;
use heed::RwTxn;
pub const CURRENT_MIGRATION_LEVEL: u32 = 5;
impl Store {
pub fn migrate(&self) -> Result<(), Error> {
let mut txn = self.lmdb.write_txn()?;
let mut migration_level = self.lmdb.get_migration_level(&txn)?;
log::info!("Storage migration level = {}", migration_level);
while migration_level < CURRENT_MIGRATION_LEVEL {
self.migrate_to(&mut txn, migration_level + 1)?;
migration_level += 1;
self.lmdb.set_migration_level(&mut txn, migration_level)?
}
txn.commit()?;
Ok(())
}
fn migrate_to(&self, txn: &mut RwTxn<'_>, level: u32) -> Result<(), Error> {
log::info!("Migrating database to {}", level);
match level {
1 => self.migrate_to_1(txn)?,
2 => self.migrate_to_2(txn)?,
3 => self.migrate_to_3(txn)?,
4 => self.migrate_to_4(txn)?,
5 => self.migrate_to_5(txn)?,
_ => panic!("Unknown migration level {level}"),
}
Ok(())
}
// Populate ci_index
fn migrate_to_1(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
let loop_txn = self.lmdb.read_txn()?;
let iter = self.lmdb.i_iter(&loop_txn)?;
for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
self.lmdb.index_ci_only(txn, &event, offset)?;
}
Ok(())
}
// Populate tc_index and ac_index
fn migrate_to_2(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
let loop_txn = self.lmdb.read_txn()?;
let iter = self.lmdb.i_iter(&loop_txn)?;
for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
// Add to ac_index
self.lmdb.index_ac_only(txn, &event, offset)?;
// Add to tc_index
for mut tsi in event.tags()?.iter() {
if let Some(tagname) = tsi.next() {
if tagname.len() == 1 {
if let Some(tagvalue) = tsi.next() {
self.lmdb
.index_tc_only(txn, tagname[0], tagvalue, &event, offset)?;
}
}
}
}
}
Ok(())
}
// Clear IP data (we are hashing now)
fn migrate_to_3(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
self.lmdb.clear_ip_data(txn)?;
Ok(())
}
// Clear deleted_offsets (now retired)
fn migrate_to_4(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
let deleted_offsets = self.lmdb.deleted_offsets(txn)?;
deleted_offsets.clear(txn)?;
Ok(())
}
// Move data from deleted_events to deleted_ids
fn migrate_to_5(&self, txn: &mut RwTxn<'_>) -> Result<(), Error> {
let deleted_events = self.lmdb.deleted_events(txn)?;
let mut ids: Vec<Id> = Vec::new();
for i in deleted_events.iter(txn)? {
let (key, _val) = i?;
let id = Id(key[0..32].try_into().unwrap());
ids.push(id);
}
for id in ids.drain(..) {
self.lmdb.mark_deleted(txn, id)?;
}
deleted_events.clear(txn)?;
Ok(())
}
}

View File

@ -1,666 +0,0 @@
pub mod event_store;
pub use event_store::EventStore;
mod lmdb;
use lmdb::Lmdb;
mod migrations;
use crate::config::Config;
use crate::error::{ChorusError, Error};
use crate::ip::{HashedIp, IpData};
use crate::types::{Event, Filter, Id, Kind, Pubkey, Time};
use heed::RwTxn;
use std::collections::BTreeSet;
use std::fs;
#[derive(Debug)]
pub struct Store {
lmdb: Lmdb,
events: EventStore,
}
impl Store {
/// Setup persistent storage
pub fn new(config: &Config) -> Result<Store, Error> {
let dir = format!("{}/lmdb", &config.data_directory);
// Create the lmdb subdir if it doesn't exist, ignoring errors
let _ = std::fs::create_dir(&dir);
let lmdb = Lmdb::new(&dir)?;
let events_are_aligned = lmdb.get_if_events_are_aligned()?;
let events = {
let event_map_file = format!("{}/event.map", &config.data_directory);
EventStore::new(event_map_file, events_are_aligned)?
};
let store = Store { lmdb, events };
// This is in migrations.rs
store.migrate()?;
store.lmdb.log_stats()?;
Ok(store)
}
pub fn rebuild(config: &Config) -> Result<(), Error> {
let dir = format!("{}/lmdb", &config.data_directory);
let dir_bak = format!("{}/lmdb.bak", &config.data_directory);
fs::rename(&dir, &dir_bak)?;
let old_lmdb = Lmdb::new(&dir_bak)?;
let new_lmdb = Lmdb::new(&dir)?;
let old_align = old_lmdb.get_if_events_are_aligned()?;
let event_map_file = format!("{}/event.map", &config.data_directory);
let event_map_file_bak = format!("{}/event.map.bak", &config.data_directory);
fs::rename(&event_map_file, &event_map_file_bak)?;
let old_event_store = EventStore::new(event_map_file_bak, old_align)?;
let new_event_store = EventStore::new(event_map_file, true)?;
let old_store = Store {
lmdb: old_lmdb,
events: old_event_store,
};
old_store.migrate()?;
let new_store = Store {
lmdb: new_lmdb,
events: new_event_store,
};
new_store.migrate()?;
log::info!("Copying data...");
let old_txn = old_store.lmdb.read_txn()?;
let mut new_txn = new_store.lmdb.write_txn()?;
new_store
.lmdb
.set_if_events_are_aligned(&mut new_txn, true)?;
// Iterate through all IDs and copy and index all of those events
for i in old_store.lmdb.i_iter(&old_txn)? {
let (_key, val) = i?;
//let id = Id(key[0..32].try_into().unwrap());
let old_offset: usize = val;
let event = old_store.events.get_event_by_offset(old_offset)?;
let new_offset = new_store.events.store_event(&event)?;
new_store.lmdb.index(&mut new_txn, &event, new_offset)?;
}
// Copy deleted IDs
let mut deleted = old_store.lmdb.dump_deleted()?;
for id in deleted.drain(..) {
new_store.lmdb.mark_deleted(&mut new_txn, id)?;
}
new_txn.commit()?;
// Copy approved events
let mut approvals = old_store.dump_event_approvals()?;
for (id, approval) in approvals.drain(..) {
new_store.mark_event_approval(id, approval)?;
}
// Copy approved pubkeys
let mut approvals = old_store.dump_pubkey_approvals()?;
for (pubkey, approval) in approvals.drain(..) {
new_store.mark_pubkey_approval(pubkey, approval)?;
}
// Copy ip data
let mut ipdata = old_store.lmdb.dump_ip_data()?;
for (hashedip, ipdata) in ipdata.drain(..) {
new_store.update_ip_data(hashedip, &ipdata)?;
}
new_store.lmdb.sync()?;
log::info!("done.");
Ok(())
}
/// Sync the data to disk. This happens periodically, but sometimes it's useful to force
/// it.
pub fn sync(&self) -> Result<(), Error> {
self.lmdb.sync()?;
Ok(())
}
/// Store an event.
///
/// Returns the offset where the event is stored at, which can be used to fetch
/// the event via get_event_by_offset().
///
/// If the event already exists, you will get a ChorusError::Duplicate
///
/// If the event is ephemeral, it will be stored and you will get an offset, but
/// it will not be indexed.
pub fn store_event(&self, event: &Event) -> Result<usize, Error> {
// TBD: should we validate the event?
let mut txn = self.lmdb.write_txn()?;
let offset;
// Only if it doesn't already exist
if self.lmdb.get_offset_by_id(&txn, event.id())?.is_none() {
// Reject event if it was deleted
{
if self.lmdb.is_deleted(&txn, event.id())? {
return Err(ChorusError::Deleted.into());
}
}
// Store the event
offset = self.events.store_event(event)?;
// Index the event
if !event.kind().is_ephemeral() {
self.lmdb.index(&mut txn, event, offset)?;
}
// If replaceable or parameterized replaceable,
// find and delete all but the first one in the group
if event.kind().is_replaceable() || event.kind().is_parameterized_replaceable() {
self.delete_replaced(&mut txn, event)?;
}
// Handle deletion events
if event.kind() == Kind(5) {
self.handle_deletion_event(&mut txn, event)?;
}
txn.commit()?;
} else {
return Err(ChorusError::Duplicate.into());
}
Ok(offset)
}
fn handle_deletion_event(&self, txn: &mut RwTxn<'_>, event: &Event) -> Result<(), Error> {
for mut tag in event.tags()?.iter() {
if let Some(tagname) = tag.next() {
if tagname == b"e" {
if let Some(id_hex) = tag.next() {
if let Ok(id) = Id::read_hex(id_hex) {
// Add deletion pair to the event_deleted table
self.lmdb.mark_deleted(txn, id)?;
// Delete pair
if let Some(target) = self.get_event_by_id(id)? {
if target.pubkey() == event.pubkey() {
self.delete_by_id(txn, id)?;
}
}
}
}
}
}
}
Ok(())
}
/// Get an event by its offset.
pub fn get_event_by_offset(&self, offset: usize) -> Result<Event, Error> {
self.events.get_event_by_offset(offset)
}
/// Get an event by Id
pub fn get_event_by_id(&self, id: Id) -> Result<Option<Event>, Error> {
let txn = self.lmdb.read_txn()?;
if let Some(offset) = self.lmdb.get_offset_by_id(&txn, id)? {
Some(self.events.get_event_by_offset(offset)).transpose()
} else {
Ok(None)
}
}
/// Find all events that match the filter
pub fn find_events<F>(
&self,
filter: Filter,
screen: F,
config: &Config,
) -> Result<Vec<Event>, Error>
where
F: Fn(&Event) -> bool,
{
let txn = self.lmdb.read_txn()?;
// We insert into a BTreeSet to keep them time-ordered
let mut output: BTreeSet<Event> = BTreeSet::new();
if filter.num_ids() > 0 {
// Fetch by id
for id in filter.ids() {
// Stop if limited
if output.len() >= filter.limit() as usize {
break;
}
if let Some(event) = self.get_event_by_id(id)? {
// and check each against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
output.insert(event);
}
}
}
} else if filter.num_authors() > 0 && filter.num_kinds() > 0 {
// We may bring since forward if we hit the limit without going back that
// far, so we use a mutable since:
let mut since = filter.since();
for author in filter.authors() {
for kind in filter.kinds() {
let iter = self
.lmdb
.akc_iter(author, kind, since, filter.until(), &txn)?;
// Count how many we have found of this author-kind pair, so we
// can possibly update `since`
let mut paircount = 0;
'per_event: for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
// If we have gone beyond since, we can stop early
// (We have to check because `since` might change in this loop)
if event.created_at() < since {
break 'per_event;
}
// check against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
// Accept the event
output.insert(event);
paircount += 1;
// Stop this pair if limited
if paircount >= filter.limit() as usize {
// Since we found the limit just among this pair,
// potentially move since forward
if event.created_at() > since {
since = event.created_at();
}
break 'per_event;
}
// If kind is replaceable (and not parameterized)
// then don't take any more events for this author-kind
// pair.
// NOTE that this optimization is difficult to implement
// for other replaceable event situations
if kind.is_replaceable() {
break 'per_event;
}
}
}
}
}
} else if filter.num_authors() > 0 && !filter.tags()?.is_empty() {
// We may bring since forward if we hit the limit without going back that
// far, so we use a mutable since:
let mut since = filter.since();
for author in filter.authors() {
let tags = filter.tags()?;
for mut tag in tags.iter() {
if let Some(tag0) = tag.next() {
if let Some(tagvalue) = tag.next() {
let iter = self.lmdb.atc_iter(
author,
tag0[0],
tagvalue,
since,
filter.until(),
&txn,
)?;
// Count how many we have found of this author-tag pair, so we
// can possibly update `since`
let mut paircount = 0;
'per_event: for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
// If we have gone beyond since, we can stop early
// (We have to check because `since` might change in this loop)
if event.created_at() < since {
break 'per_event;
}
// check against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
// Accept the event
output.insert(event);
paircount += 1;
// Stop this pair if limited
if paircount >= filter.limit() as usize {
// Since we found the limit just among this pair,
// potentially move since forward
if event.created_at() > since {
since = event.created_at();
}
break 'per_event;
}
}
}
}
}
}
}
} else if filter.num_kinds() > 0 && !filter.tags()?.is_empty() {
// We may bring since forward if we hit the limit without going back that
// far, so we use a mutable since:
let mut since = filter.since();
for kind in filter.kinds() {
let tags = filter.tags()?;
for mut tag in tags.iter() {
if let Some(tag0) = tag.next() {
if let Some(tagvalue) = tag.next() {
let iter = self.lmdb.ktc_iter(
kind,
tag0[0],
tagvalue,
since,
filter.until(),
&txn,
)?;
// Count how many we have found of this kind-tag pair, so we
// can possibly update `since`
let mut paircount = 0;
'per_event: for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
// If we have gone beyond since, we can stop early
// (We have to check because `since` might change in this loop)
if event.created_at() < since {
break 'per_event;
}
// check against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
// Accept the event
output.insert(event);
paircount += 1;
// Stop this pair if limited
if paircount >= filter.limit() as usize {
// Since we found the limit just among this pair,
// potentially move since forward
if event.created_at() > since {
since = event.created_at();
}
break 'per_event;
}
}
}
}
}
}
}
} else if !filter.tags()?.is_empty() {
// We may bring since forward if we hit the limit without going back that
// far, so we use a mutable since:
let mut since = filter.since();
let tags = filter.tags()?;
for mut tag in tags.iter() {
if let Some(tag0) = tag.next() {
if let Some(tagvalue) = tag.next() {
let iter =
self.lmdb
.tc_iter(tag0[0], tagvalue, since, filter.until(), &txn)?;
let mut rangecount = 0;
'per_event: for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
if event.created_at() < since {
break 'per_event;
}
// check against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
// Accept the event
output.insert(event);
rangecount += 1;
// Stop this limited
if rangecount >= filter.limit() as usize {
if event.created_at() > since {
since = event.created_at();
}
break 'per_event;
}
}
}
}
}
}
} else if filter.num_authors() > 0 {
// We may bring since forward if we hit the limit without going back that
// far, so we use a mutable since:
let mut since = filter.since();
for author in filter.authors() {
let iter = self.lmdb.ac_iter(author, since, filter.until(), &txn)?;
let mut rangecount = 0;
'per_event: for result in iter {
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
if event.created_at() < filter.since() {
break 'per_event;
}
// check against the rest of the filter
if filter.event_matches(&event)? && screen(&event) {
// Accept the event
output.insert(event);
rangecount += 1;
// Stop this limited
if rangecount >= filter.limit() as usize {
if event.created_at() > since {
since = event.created_at();
}
break 'per_event;
}
}
}
}
} else {
// SCRAPE:
let maxtime = filter.until().0.min(Time::now().0);
let allow = config.allow_scraping
|| filter.limit() <= config.allow_scrape_if_limited_to
|| (maxtime - filter.since().0) < config.allow_scrape_if_max_seconds;
if !allow {
return Err(ChorusError::Scraper.into());
}
// This is INEFFICIENT as it scans through many events
let iter = self.lmdb.ci_iter(filter.since(), filter.until(), &txn)?;
for result in iter {
if output.len() >= filter.limit() as usize {
break;
}
let (_key, offset) = result?;
let event = self.events.get_event_by_offset(offset)?;
if filter.event_matches(&event)? && screen(&event) {
output.insert(event);
}
}
}
// Convert to a Vec, reverse time order, and apply limit
Ok(output
.iter()
.rev()
.take(filter.limit() as usize)
.copied()
.collect())
}
/// Delete an event by id.
///
/// This deindexes the event.
///
/// This does not add to the deleted_ids record, which is for events
/// that are deleted by other events
fn delete_by_id(&self, txn: &mut RwTxn<'_>, id: Id) -> Result<(), Error> {
if let Some(offset) = self.lmdb.get_offset_by_id(txn, id)? {
self.delete_by_offset(txn, offset)?;
}
Ok(())
}
/// Delete an event by offset.
///
/// This deindexes the event.
///
/// This does not add to the deleted_ids record, which is for events
/// that are deleted by other events
fn delete_by_offset(&self, txn: &mut RwTxn<'_>, offset: usize) -> Result<(), Error> {
// Get event
let event = self.events.get_event_by_offset(offset)?;
// Remove from indexes
self.lmdb.deindex(txn, &event)?;
// Also remove from the id index
self.lmdb.deindex_id(txn, event.id())?;
Ok(())
}
// This deletes an event without marking it as having been deleted by another event
pub fn delete_event(&self, id: Id) -> Result<(), Error> {
let mut txn = self.lmdb.write_txn()?;
self.delete_by_id(&mut txn, id)?;
txn.commit()?;
Ok(())
}
// If the event is replaceable or parameterized replaceable
// this deletes all the events in that group except the most recent one.
fn delete_replaced(&self, txn: &mut RwTxn<'_>, event: &Event) -> Result<(), Error> {
if event.kind().is_replaceable() {
let loop_txn = self.lmdb.read_txn()?;
let iter = self.lmdb.akc_iter(
event.pubkey(),
event.kind(),
Time::min(),
Time::max(),
&loop_txn,
)?;
let mut first = true;
for result in iter {
// Keep the first result
if first {
first = false;
continue;
}
let (_key, offset) = result?;
// Delete the event
self.delete_by_offset(txn, offset)?;
}
} else if event.kind().is_parameterized_replaceable() {
let tags = event.tags()?;
if let Some(identifier) = tags.get_value(b"d") {
let loop_txn = self.lmdb.read_txn()?;
let iter = self.lmdb.atc_iter(
event.pubkey(),
b'd',
identifier,
Time::min(),
Time::max(),
&loop_txn,
)?;
let mut first = true;
for result in iter {
// Keep the first result
if first {
first = false;
continue;
}
let (_key, offset) = result?;
// Delete the event
self.delete_by_offset(txn, offset)?;
}
}
}
Ok(())
}
pub fn get_ip_data(&self, ip: HashedIp) -> Result<IpData, Error> {
self.lmdb.get_ip_data(ip)
}
pub fn update_ip_data(&self, ip: HashedIp, data: &IpData) -> Result<(), Error> {
self.lmdb.update_ip_data(ip, data)
}
pub fn mark_event_approval(&self, id: Id, approval: bool) -> Result<(), Error> {
self.lmdb.mark_event_approval(id, approval)
}
pub fn clear_event_approval(&self, id: Id) -> Result<(), Error> {
self.lmdb.clear_event_approval(id)
}
pub fn get_event_approval(&self, id: Id) -> Result<Option<bool>, Error> {
self.lmdb.get_event_approval(id)
}
pub fn dump_event_approvals(&self) -> Result<Vec<(Id, bool)>, Error> {
self.lmdb.dump_event_approvals()
}
pub fn mark_pubkey_approval(&self, pubkey: Pubkey, approval: bool) -> Result<(), Error> {
self.lmdb.mark_pubkey_approval(pubkey, approval)
}
pub fn clear_pubkey_approval(&self, pubkey: Pubkey) -> Result<(), Error> {
self.lmdb.clear_pubkey_approval(pubkey)
}
pub fn get_pubkey_approval(&self, pubkey: Pubkey) -> Result<Option<bool>, Error> {
self.lmdb.get_pubkey_approval(pubkey)
}
pub fn dump_pubkey_approvals(&self) -> Result<Vec<(Pubkey, bool)>, Error> {
self.lmdb.dump_pubkey_approvals()
}
}

View File

@ -1,318 +0,0 @@
use crate::error::{ChorusError, Error};
use crate::types::parse::json_parse::*;
/// Parses a JSON event from the `input` buffer. Places the parsed event into the `output` buffer.
/// Returns the count of consumed bytes and output bytes
pub fn parse_json_event(input: &[u8], output: &mut [u8]) -> Result<(usize, usize), Error> {
// Minimum-sized JSON event is 204 characters long
if input.len() < 204 {
return Err(ChorusError::JsonBadEvent("Too Short", 0).into());
}
// NOTE: 152 is the minimum binary event
if output.len() < 152 {
return Err(ChorusError::BufferTooSmall.into());
}
// This tracks where we are currently looking in the input as we scan forward.
// It is short for INput POSition.
let mut inpos = 0;
// If tags comes before content, content can use this to know where to put itself.
// This is the length of the tags output section. 0 means it hasn't been written yet.
let mut tags_size: usize = 0;
// If content comes before tags, we cannot write it because we don't know how much
// space Tags will take. So we instead just remember where the content string
// begins so we can write it later.
let mut content_input_start: usize = 0;
// Remember which fields we have read using bit flags.
// We must get all seven of these fields for an event to be valid.
const HAVE_ID: u8 = 0x1 << 0;
const HAVE_PUBKEY: u8 = 0x1 << 1;
const HAVE_SIG: u8 = 0x1 << 2;
const HAVE_CREATED_AT: u8 = 0x1 << 3;
const HAVE_KIND: u8 = 0x1 << 4;
const HAVE_CONTENT: u8 = 0x1 << 5;
const HAVE_TAGS: u8 = 0x1 << 6;
let mut complete: u8 = 0;
eat_whitespace(input, &mut inpos);
verify_char(input, b'{', &mut inpos)?;
loop {
eat_whitespace(input, &mut inpos);
// Presuming that we must have at least one field, we don't have to look
// for the end of the object yet.
// Move to the start of the field name
verify_char(input, b'"', &mut inpos)?;
// No matter which field is next, we need at least 7 bytes for the smallest
// field and value: kind":1
// This allows us to skip length tests below that are shorter than inpos+7
if inpos + 7 > input.len() {
return Err(ChorusError::JsonBadEvent("Too Short or Missing Fields", inpos).into());
}
if &input[inpos..inpos + 3] == b"id\"" {
if complete & HAVE_ID == HAVE_ID {
return Err(ChorusError::JsonBadEvent("Duplicate id field", inpos).into());
}
inpos += 3;
eat_colon_with_whitespace(input, &mut inpos)?;
read_id(input, &mut inpos, &mut output[16..48])?;
complete |= HAVE_ID;
} else if &input[inpos..inpos + 4] == b"sig\"" {
if complete & HAVE_SIG == HAVE_SIG {
return Err(ChorusError::JsonBadEvent("Duplicate sig field", inpos).into());
}
inpos += 4;
eat_colon_with_whitespace(input, &mut inpos)?;
read_sig(input, &mut inpos, output)?;
complete |= HAVE_SIG;
} else if &input[inpos..inpos + 5] == b"kind\"" {
if complete & HAVE_KIND == HAVE_KIND {
return Err(ChorusError::JsonBadEvent("Duplicate kind field", inpos).into());
}
inpos += 5;
eat_colon_with_whitespace(input, &mut inpos)?;
let kind = read_kind(input, &mut inpos)?;
output[4..6].copy_from_slice(kind.to_ne_bytes().as_slice());
complete |= HAVE_KIND;
} else if &input[inpos..inpos + 5] == b"tags\"" {
if complete & HAVE_TAGS == HAVE_TAGS {
return Err(ChorusError::JsonBadEvent("Duplicate tags field", inpos).into());
}
inpos += 5;
eat_colon_with_whitespace(input, &mut inpos)?;
tags_size = read_tags_array(input, &mut inpos, &mut output[144..])?;
complete |= HAVE_TAGS;
if content_input_start != 0 {
// Content was found earlier than tags.
// Now that tags have been read, we should read the content
read_content(input, &mut content_input_start, output, 144 + tags_size)?;
complete |= HAVE_CONTENT;
}
} else if &input[inpos..inpos + 7] == b"pubkey\"" {
if complete & HAVE_PUBKEY == HAVE_PUBKEY {
return Err(ChorusError::JsonBadEvent("Duplicate pubkey field", inpos).into());
}
inpos += 7;
eat_colon_with_whitespace(input, &mut inpos)?;
read_pubkey(input, &mut inpos, &mut output[48..80])?;
complete |= HAVE_PUBKEY;
} else if inpos + 8 <= input.len() && &input[inpos..inpos + 8] == b"content\"" {
if complete & HAVE_CONTENT == HAVE_CONTENT {
return Err(ChorusError::JsonBadEvent("Duplicate pubkey field", inpos).into());
}
inpos += 8;
eat_colon_with_whitespace(input, &mut inpos)?;
if tags_size == 0 {
// Oops, we haven't read the tags yet. That means we don't yet know where
// to place the content. In this case we just remember the offset where
// this needs to be done, so we can do this later.
content_input_start = inpos;
// skip past it so we can read the subsequent fields
verify_char(input, b'"', &mut inpos)?;
burn_string(input, &mut inpos)?;
} else {
read_content(input, &mut inpos, output, 144 + tags_size)?;
complete |= HAVE_CONTENT;
}
} else if inpos + 11 <= input.len() && &input[inpos..inpos + 11] == b"created_at\"" {
if complete & HAVE_CREATED_AT == HAVE_CREATED_AT {
return Err(ChorusError::JsonBadEvent("Duplicate created_at field", inpos).into());
}
inpos += 11;
eat_colon_with_whitespace(input, &mut inpos)?;
let u = read_u64(input, &mut inpos)?;
output[8..16].copy_from_slice(u.to_ne_bytes().as_slice());
complete |= HAVE_CREATED_AT;
} else {
burn_key_and_value(input, &mut inpos)?;
}
// get past the comma, or detect the close brace and exit
if next_object_field(input, &mut inpos)? {
break;
}
}
if complete == 0b0111_1111 {
Ok((
inpos,
u32::from_ne_bytes(output[0..4].try_into().unwrap()) as usize,
))
} else {
Err(ChorusError::JsonBadEvent("Missing Fields", inpos).into())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_json_event() {
if 256_u16.to_ne_bytes() == [1, 0] {
test_parse_json_event_big_endian();
} else {
test_parse_json_event_little_endian();
}
}
fn test_parse_json_event_little_endian() {
let json = br#"{"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"kind":1,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","content":"He got snowed in","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]]}"#;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (_insize, size) = parse_json_event(&json[..], &mut buffer).unwrap();
assert_eq!(size, 372);
assert_eq!(
&buffer[0..size],
&[
116, 1, 0, 0, // 372 bytes long
1, 0, // kind 1
0, 0, // padding
102, 232, 61, 100, 0, 0, 0, 0, // created at 1681778790
169, 102, 48, 85, 22, 74, 184, 179, 13, 149, 36, 101, 99, 112, 196, 191, 147, 57,
59, 176, 81, 183, 237, 244, 85, 111, 64, 197, 41, 141, 192, 199, // id
238, 17, 165, 223, 244, 12, 25, 165, 85, 244, 31, 228, 43, 72, 240, 14, 97, 140,
145, 34, 86, 34, 174, 55, 182, 194, 187, 103, 183, 108, 78, 73, // pubkey
77, 254, 161, 166, 247, 49, 65, 213, 105, 30, 67, 175, 195, 35, 77, 190, 115, 1,
109, 176, 251, 32, 124, 242, 71, 224, 18, 124, 194, 89, 30, 230, 180, 190, 91, 70,
34, 114, 3, 10, 155, 222, 117, 136, 42, 174, 129, 15, 53, 150, 130, 177, 182, 206,
108, 187, 151, 32, 17, 65, 197, 118, 219, 66, // sig
// 144:
208, 0, // tags section is 208 bytes long
3, 0, // there are three tags
10, 0, // first tag is at offset 10
28, 0, // second tag is at offset 28
99, 0, // third tag is at offset 99
// 154: (144+10)
2, 0, // the first tag has 2 strings
6, 0, // the first string is 6 bytes long
99, 108, 105, 101, 110, 116, // "client"
6, 0, // the second string is 6 bytes long
103, 111, 115, 115, 105, 112, // "gossip"
// 172: (144+28)
2, 0, // the second tag has two strings
1, 0, // the first string is 1 char long
112, // "p"
64, 0, // the second string is 64 bytes long
101, 50, 99, 99, 102, 55, 99, 102, 50, 48, 52, 48, 51, 102, 51, 102, 50, 97, 52,
97, 53, 53, 98, 51, 50, 56, 102, 48, 100, 101, 51, 98, 101, 51, 56, 53, 53, 56, 97,
55, 100, 53, 102, 51, 51, 54, 51, 50, 102, 100, 97, 97, 101, 102, 99, 55, 50, 54,
99, 49, 99, 56, 101,
98, // "e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"
// 243: (144+99)
4, 0, // the third tag has 4 strings
1, 0, // the first string is 1 char long
101, // "e"
64, 0, // the second string is 64 bytes long
50, 99, 56, 54, 97, 98, 99, 99, 57, 56, 102, 55, 102, 100, 56, 97, 54, 55, 53, 48,
97, 97, 98, 56, 100, 102, 54, 99, 49, 56, 54, 51, 57, 48, 51, 102, 49, 48, 55, 50,
48, 54, 99, 99, 50, 100, 55, 50, 101, 56, 97, 102, 101, 98, 54, 99, 51, 56, 51, 53,
55, 97, 101,
100, // "2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"
30, 0, // the third string is 30 bytes long
119, 115, 115, 58, 47, 47, 110, 111, 115, 116, 114, 45, 112, 117, 98, 46, 119, 101,
108, 108, 111, 114, 100, 101, 114, 46, 110, 101, 116,
47, // "wss://nostr-pub.wellorder.net/"
4, 0, // the fourth string is 4 bytes long
114, 111, 111, 116, // "root"
// 352: (144+208)
16, 0, 0, 0, // the content is 16 bytes long
72, 101, 32, 103, 111, 116, 32, 115, 110, 111, 119, 101, 100, 32, 105,
110, // "He got snowed in"
// 372:
]
);
// Same event in a different order
let json2 = br#"{"kind":1,"pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]],"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","content":"He got snowed in"}"#;
let mut buffer2: Vec<u8> = Vec::with_capacity(4096);
buffer2.resize(4096, 0);
let (_insize, size) = parse_json_event(&json2[..], &mut buffer2).unwrap();
assert_eq!(size, 372);
assert_eq!(&buffer[..372], &buffer2[..372]);
}
fn test_parse_json_event_big_endian() {
let json = br#"{"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"kind":1,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","content":"He got snowed in","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]]}"#;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (_insize, size) = parse_json_event(&json[..], &mut buffer).unwrap();
assert_eq!(size, 372);
assert_eq!(
&buffer[0..size],
&[
0, 0, 1, 116, // 372 bytes long
0, 1, // kind 1
0, 0, // padding
0, 0, 0, 0, 100, 61, 232, 102, // created at 1681778790
169, 102, 48, 85, 22, 74, 184, 179, 13, 149, 36, 101, 99, 112, 196, 191, 147, 57,
59, 176, 81, 183, 237, 244, 85, 111, 64, 197, 41, 141, 192, 199, // id
238, 17, 165, 223, 244, 12, 25, 165, 85, 244, 31, 228, 43, 72, 240, 14, 97, 140,
145, 34, 86, 34, 174, 55, 182, 194, 187, 103, 183, 108, 78, 73, // pubkey
77, 254, 161, 166, 247, 49, 65, 213, 105, 30, 67, 175, 195, 35, 77, 190, 115, 1,
109, 176, 251, 32, 124, 242, 71, 224, 18, 124, 194, 89, 30, 230, 180, 190, 91, 70,
34, 114, 3, 10, 155, 222, 117, 136, 42, 174, 129, 15, 53, 150, 130, 177, 182, 206,
108, 187, 151, 32, 17, 65, 197, 118, 219, 66, // sig
// 144:
0, 208, // tags section is 208 bytes long
3, 0, // there are three tags
0, 10, // first tag is at offset 10
0, 28, // second tag is at offset 28
0, 99, // third tag is at offset 99
// 154: (144+10)
0, 2, // the first tag has 2 strings
0, 6, // the first string is 6 bytes long
99, 108, 105, 101, 110, 116, // "client"
0, 6, // the second string is 6 bytes long
103, 111, 115, 115, 105, 112, // "gossip"
// 172: (144+28)
0, 2, // the second tag has two strings
0, 1, // the first string is 1 char long
112, // "p"
0, 64, // the second string is 64 bytes long
101, 50, 99, 99, 102, 55, 99, 102, 50, 48, 52, 48, 51, 102, 51, 102, 50, 97, 52,
97, 53, 53, 98, 51, 50, 56, 102, 48, 100, 101, 51, 98, 101, 51, 56, 53, 53, 56, 97,
55, 100, 53, 102, 51, 51, 54, 51, 50, 102, 100, 97, 97, 101, 102, 99, 55, 50, 54,
99, 49, 99, 56, 101,
98, // "e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"
// 243: (144+99)
0, 4, // the third tag has 4 strings
0, 1, // the first string is 1 char long
101, // "e"
0, 64, // the second string is 64 bytes long
50, 99, 56, 54, 97, 98, 99, 99, 57, 56, 102, 55, 102, 100, 56, 97, 54, 55, 53, 48,
97, 97, 98, 56, 100, 102, 54, 99, 49, 56, 54, 51, 57, 48, 51, 102, 49, 48, 55, 50,
48, 54, 99, 99, 50, 100, 55, 50, 101, 56, 97, 102, 101, 98, 54, 99, 51, 56, 51, 53,
55, 97, 101,
100, // "2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"
0, 30, // the third string is 30 bytes long
119, 115, 115, 58, 47, 47, 110, 111, 115, 116, 114, 45, 112, 117, 98, 46, 119, 101,
108, 108, 111, 114, 100, 101, 114, 46, 110, 101, 116,
47, // "wss://nostr-pub.wellorder.net/"
0, 4, // the fourth string is 4 bytes long
114, 111, 111, 116, // "root"
// 352: (144+208)
0, 0, 0, 16, // the content is 16 bytes long
72, 101, 32, 103, 111, 116, 32, 115, 110, 111, 119, 101, 100, 32, 105,
110, // "He got snowed in"
// 372:
]
);
// Same event in a different order
let json2 = br#"{"kind":1,"pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]],"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","content":"He got snowed in"}"#;
let mut buffer2: Vec<u8> = Vec::with_capacity(4096);
buffer2.resize(4096, 0);
let (_insize, size) = parse_json_event(&json2[..], &mut buffer2).unwrap();
assert_eq!(size, 372);
assert_eq!(&buffer[..372], &buffer2[..372]);
}
}

View File

@ -1,243 +0,0 @@
use super::{Id, Kind, Pubkey, Sig, Tags, Time};
use crate::error::{ChorusError, Error};
use crate::types::parse::json_escape::json_escape;
use std::cmp::Ordering;
use std::fmt;
mod json_event;
use json_event::parse_json_event;
/*
* 0 [4 bytes] length of the event structure
* 4 [2 bytes] kind
* 6 [2 bytes] PADDING
* 8 [8 bytes] created_at
* 16 [32 bytes] id
* 48 [32 bytes] pubkey
* 80 [64 bytes] sig
* 144 [T bytes] Tags
* 144+T [4 bytes] content length
* 144+T+4 [C bytes] content
* 144+T+4+C <--- beginning of region beyond the event
*/
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Event<'a>(&'a [u8]);
impl<'a> Event<'a> {
// Parse json into an Event. Returns the count of consumed input bytes and the Event
pub fn from_json(
json: &[u8],
output_buffer: &'a mut [u8],
) -> Result<(usize, Event<'a>), Error> {
let (incount, outcount) = parse_json_event(json, output_buffer)?;
Ok((incount, Event(&output_buffer[..outcount])))
}
// this marks off the slice of bytes that represent an event from a potentially longer input
pub fn delineate(input: &'a [u8]) -> Result<Event<'a>, Error> {
if input.len() < 144 + 4 + 4 {
return Err(ChorusError::EndOfInput.into());
}
let len = parse_u32!(input, 0) as usize;
if input.len() < len {
return Err(ChorusError::EndOfInput.into());
}
Ok(Event(&input[0..len]))
}
// This copies
pub fn copy(&self, output: &mut [u8]) -> Result<(), Error> {
if output.len() < self.0.len() {
return Err(ChorusError::BufferTooSmall.into());
}
output[..self.0.len()].copy_from_slice(self.0);
Ok(())
}
// This copies, using the event_store mmap-append api
pub fn macopy(&self, output: &mut [u8]) -> Result<usize, std::io::Error> {
if output.len() < self.0.len() {
return Err(std::io::Error::other(ChorusError::BufferTooSmall));
}
output[..self.0.len()].copy_from_slice(self.0);
Ok(self.0.len())
}
pub fn as_bytes(&self) -> &[u8] {
self.0
}
pub fn length(&self) -> usize {
self.0.len()
}
pub fn kind(&self) -> Kind {
Kind(parse_u16!(self.0, 4))
}
pub fn created_at(&self) -> Time {
Time(parse_u64!(self.0, 8))
}
pub fn id(&self) -> Id {
Id(self.0[16..16 + 32].try_into().unwrap())
}
pub fn pubkey(&self) -> Pubkey {
Pubkey(self.0[48..48 + 32].try_into().unwrap())
}
pub fn sig(&self) -> Sig {
Sig(self.0[80..80 + 64].try_into().unwrap())
}
pub fn tags(&'a self) -> Result<Tags<'a>, Error> {
Tags::delineate(&self.0[144..])
}
pub fn content(&'a self) -> &'a [u8] {
let t = parse_u16!(self.0, 144) as usize;
let c = parse_u32!(self.0, 144 + t) as usize;
&self.0[144 + t + 4..144 + t + 4 + c]
}
pub fn as_json(&self) -> Result<Vec<u8>, Error> {
let mut output: Vec<u8> = Vec::with_capacity(256);
output.extend(br#"{"id":""#);
let pos = output.len();
output.resize(pos + 64, 0);
self.id().write_hex(&mut output[pos..]).unwrap();
output.extend(br#"","pubkey":""#);
let pos = output.len();
output.resize(pos + 64, 0);
self.pubkey().write_hex(&mut output[pos..]).unwrap();
output.extend(br#"","kind":"#);
output.extend(format!("{}", self.kind().0).as_bytes());
output.extend(br#","created_at":"#);
output.extend(format!("{}", self.created_at().0).as_bytes());
output.extend(br#","tags":"#);
output.extend(self.tags()?.as_json());
output.extend(br#","content":""#);
// This is okay if it is not accurate. It generally avoids
// lots of little mallocs when the capacity is already allocated
output.reserve(self.content().len() * 7 / 6);
let mut output = json_escape(self.content(), output)?;
output.extend(br#"","sig":""#);
let pos = output.len();
output.resize(pos + 128, 0);
self.sig().write_hex(&mut output[pos..]).unwrap();
output.extend(br#""}"#);
Ok(output)
}
pub fn verify(&self) -> Result<(), Error> {
use secp256k1::hashes::{sha256, Hash};
use secp256k1::schnorr::Signature;
use secp256k1::{Message, XOnlyPublicKey};
// This is okay if it is not accurate. It generally avoids
// lots of little mallocs when the capacity is already allocated
let escaped_content = Vec::with_capacity(self.content().len() * 7 / 6);
let escaped_content = json_escape(self.content(), escaped_content)?;
let signable = format!(
r#"[0,"{}",{},{},{},"{}"]"#,
self.pubkey(),
self.created_at(),
self.kind(),
self.tags()?,
unsafe { std::str::from_utf8_unchecked(&escaped_content[..]) },
);
drop(escaped_content);
let hash = sha256::Hash::hash(signable.as_bytes());
let hashref = <sha256::Hash as AsRef<[u8]>>::as_ref(&hash);
if hashref != self.id().as_slice() {
return Err(ChorusError::BadEventId.into());
}
let pubkey = XOnlyPublicKey::from_slice(self.pubkey().as_slice())?;
let sig = Signature::from_slice(self.sig().as_slice())?;
let message = Message::from_digest_slice(hashref)?;
sig.verify(&message, &pubkey)?;
Ok(())
}
pub fn is_expired(&self) -> Result<bool, Error> {
for mut tag in self.tags()?.iter() {
if tag.next() == Some(b"expiration") {
if let Some(expires) = tag.next() {
// Interpret string as a u64
let mut p = 0;
let time = super::parse::json_parse::read_u64(expires, &mut p)?;
if time <= Time::now().0 {
return Ok(true);
}
}
}
}
Ok(false)
}
}
impl fmt::Display for Event<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Ok(bytes) = self.as_json() {
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{s}")
} else {
write!(f, "{{Corrupted Event}}")
}
}
}
impl Eq for Event<'_> {}
impl PartialOrd for Event<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Event<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.created_at()
.cmp(&other.created_at())
.then(self.id().cmp(&other.id()))
}
}
#[derive(Debug, Clone)]
pub struct OwnedEvent(pub Vec<u8>);
impl OwnedEvent {
pub fn as_event(&self) -> Result<Event<'_>, Error> {
Event::delineate(&self.0)
}
}
#[cfg(test)]
mod test {
use super::Event;
#[test]
fn test_event_expired() {
let json = br#"{"id":"8b8b1d98f279b43f571ce55dce7cc51ced0c24e9558bfdaa0be0467f82f64708","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1712693549,"kind":1,"sig":"870497b1a254f2394a692decd46b5cffa044302179a42e985697b488fc408118c9ff7c5578d85393474c1a025f28c869148968ee3229aa24425800ae54f54e51","content":"He got snowed in","tags":[["expiration","1712693529"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]]}"#;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (_size, event) = Event::from_json(json.as_slice(), &mut buffer).unwrap();
assert_eq!(event.is_expired().unwrap(), true); // In the past
let json = br#"{"id":"120b3d99f889c6147972b0256413e84b0b7b7862a705964b7302f5392677e52a","pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1712693868,"kind":1,"sig":"ed0463b822f76f63c392b00d4a66c297f5e13371c800b139f2d40174bf77146201f29ae6e3a9da71a9346416d8b2ba4d2f5a2be693a9e75a91a33abfdc43ec71","content":"He got snowed in","tags":[["expiration","99712693529"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]]}"#;
let (_size, event) = Event::from_json(json.as_slice(), &mut buffer).unwrap();
assert_eq!(event.is_expired().unwrap(), false); // Too far in the future
let json = br#"{"kind":1,"pubkey":"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49","created_at":1681778790,"sig":"4dfea1a6f73141d5691e43afc3234dbe73016db0fb207cf247e0127cc2591ee6b4be5b462272030a9bde75882aae810f359682b1b6ce6cbb97201141c576db42","tags":[["client","gossip"],["p","e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"],["e","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","wss://nostr-pub.wellorder.net/","root"]],"id":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","content":"He got snowed in"}"#;
let (_size, event) = Event::from_json(json.as_slice(), &mut buffer).unwrap();
assert_eq!(event.is_expired().unwrap(), false); // Doesn't have the expiration tag
}
}

View File

@ -1,549 +0,0 @@
use super::{
ARRAYS_OFFSET, ID_SIZE, KIND_SIZE, LIMIT_OFFSET, NUM_AUTHORS_OFFSET, NUM_IDS_OFFSET,
NUM_KINDS_OFFSET, PUBKEY_SIZE, SINCE_OFFSET, UNTIL_OFFSET,
};
use crate::error::{ChorusError, Error};
use crate::types::parse::json_escape::json_unescape;
use crate::types::parse::json_parse::*;
use crate::types::parse::put;
/// Parses a JSON filter from the `input` buffer. Places the parsed filter into the `output` buffer.
/// Returns the count of consumed bytes and output bytes
pub fn parse_json_filter(input: &[u8], output: &mut [u8]) -> Result<(usize, usize), Error> {
if input.len() < 2 {
return Err(ChorusError::JsonBadFilter("Too short", 0).into());
}
// This tracks where we are currently looking in the input as we scan forward.
// It is short for INput POSition.
let mut inpos = 0;
// Remember which fields we have read using bit flags.
// We should only get at maximum one of each
const HAVE_IDS: u8 = 0x1 << 0;
const HAVE_AUTHORS: u8 = 0x1 << 1;
const HAVE_KINDS: u8 = 0x1 << 2;
const HAVE_LIMIT: u8 = 0x1 << 3;
const HAVE_SINCE: u8 = 0x1 << 4;
const HAVE_UNTIL: u8 = 0x1 << 5;
let mut found: u8 = 0;
// Remember which tags we have seen
// We track A-Z in the lower 26 bits, and a-z in the next 26 bits
let mut found_tags: u64 = 0;
let letter_to_tag_bit = |letter: u8| -> Option<u64> {
match letter {
65..=90 => Some(letter as u64 - 65),
97..=122 => Some(letter as u64 - 97 + 26),
_ => None,
}
};
// Start structure with that of an empty filter
put(
output,
0,
&[
0, 0, 0, 0, // length (we will fill it in later)
0, 0, // 0 ids
0, 0, // 0 authors
0, 0, // 0 kinds
0, 0, // padding
255, 255, 255, 255, // max limit
0, 0, 0, 0, 0, 0, 0, 0, // since 1970
255, 255, 255, 255, 255, 255, 255, 255, // until max unixtime
],
)?;
let mut end: usize = ARRAYS_OFFSET;
// We just store the position of ids, authors, kinds, and tags
// and come back to parse them properly again at the end,
// since we need to write them in a particular order.
let mut start_ids: Option<usize> = None;
let mut start_authors: Option<usize> = None;
let mut start_kinds: Option<usize> = None;
// Allowing up to 32 tag filter fields (plenty!)
// (we are not differentiating letters yet, just collecting offsets)
// (we make the array to avoid allocation)
let mut num_tag_fields = 0;
let mut start_tags: [usize; 32] = [usize::MAX; 32];
eat_whitespace(input, &mut inpos);
verify_char(input, b'{', &mut inpos)?;
loop {
eat_whitespace_and_commas(input, &mut inpos);
// Check for end
if input[inpos] == b'}' {
inpos += 1;
break;
}
verify_char(input, b'"', &mut inpos)?;
if inpos + 4 <= input.len() && &input[inpos..inpos + 4] == b"ids\"" {
// Check for duplicate
if found & HAVE_IDS == HAVE_IDS {
return Err(ChorusError::JsonBadFilter("Duplicate id field", inpos).into());
}
inpos += 4;
eat_colon_with_whitespace(input, &mut inpos)?;
verify_char(input, b'[', &mut inpos)?;
// Record for later
start_ids = Some(inpos);
// Burn the field
while inpos < input.len() && input[inpos] != b']' {
inpos += 1;
}
verify_char(input, b']', &mut inpos)?;
// Mark as found 'ids' FIXME this dups `start_ids`
found |= HAVE_IDS;
} else if inpos + 8 <= input.len() && &input[inpos..inpos + 8] == b"authors\"" {
// Check for duplicate
if found & HAVE_AUTHORS == HAVE_AUTHORS {
return Err(ChorusError::JsonBadFilter("Duplicate authors field", inpos).into());
}
inpos += 8;
eat_colon_with_whitespace(input, &mut inpos)?;
verify_char(input, b'[', &mut inpos)?;
// Save the input offset for post-processing
start_authors = Some(inpos);
// Burn the field
while inpos < input.len() && input[inpos] != b']' {
inpos += 1;
}
verify_char(input, b']', &mut inpos)?;
found |= HAVE_AUTHORS;
} else if inpos + 6 <= input.len() && &input[inpos..inpos + 6] == b"kinds\"" {
// Check for duplicate
if found & HAVE_KINDS == HAVE_KINDS {
return Err(ChorusError::JsonBadFilter("Duplicate kinds field", inpos).into());
}
inpos += 6;
eat_colon_with_whitespace(input, &mut inpos)?;
verify_char(input, b'[', &mut inpos)?;
// Mark this position and bypass this field
start_kinds = Some(inpos);
// Burn the field
while inpos < input.len() && input[inpos] != b']' {
inpos += 1;
}
verify_char(input, b']', &mut inpos)?;
found |= HAVE_KINDS;
} else if inpos + 6 <= input.len() && &input[inpos..inpos + 6] == b"since\"" {
// Check for duplicate
if found & HAVE_SINCE == HAVE_SINCE {
return Err(ChorusError::JsonBadFilter("Duplicate since field", inpos).into());
}
inpos += 6;
eat_colon_with_whitespace(input, &mut inpos)?;
let since = read_u64(input, &mut inpos)?;
put(output, SINCE_OFFSET, since.to_ne_bytes().as_slice())?;
found |= HAVE_SINCE;
} else if inpos + 6 <= input.len() && &input[inpos..inpos + 6] == b"until\"" {
// Check for duplicate
if found & HAVE_UNTIL == HAVE_UNTIL {
return Err(ChorusError::JsonBadFilter("Duplicate until field", inpos).into());
}
inpos += 6;
eat_colon_with_whitespace(input, &mut inpos)?;
let until = read_u64(input, &mut inpos)?;
put(output, UNTIL_OFFSET, until.to_ne_bytes().as_slice())?;
found |= HAVE_UNTIL;
} else if inpos + 6 <= input.len() && &input[inpos..inpos + 6] == b"limit\"" {
// Check for duplicate
if found & HAVE_LIMIT == HAVE_LIMIT {
return Err(ChorusError::JsonBadFilter("Duplicate limit field", inpos).into());
}
inpos += 6;
eat_colon_with_whitespace(input, &mut inpos)?;
let limit = read_u64(input, &mut inpos)?;
let limit: u32 = limit as u32;
put(output, LIMIT_OFFSET, limit.to_ne_bytes().as_slice())?;
found |= HAVE_LIMIT;
} else if inpos + 3 <= input.len()
&& input[inpos] == b'#'
&& ((input[inpos + 1] >= 65 && input[inpos + 1] <= 90)
|| (input[inpos + 1] >= 97 && input[inpos + 1] <= 122))
&& input[inpos + 2] == b'"'
{
inpos += 1; // pass the hash
// Mark this position (on the letter itself)
start_tags[num_tag_fields] = inpos;
num_tag_fields += 1;
let letter = input[inpos];
inpos += 2; // pass the letter and quote
// Remember we found this tag in the `found_tags` bitfield
if let Some(bit) = letter_to_tag_bit(letter) {
if found_tags & bit == bit {
return Err(ChorusError::JsonBadFilter("Duplicate tag", inpos).into());
}
found_tags |= bit;
}
// Burn the rest
eat_colon_with_whitespace(input, &mut inpos)?;
verify_char(input, b'[', &mut inpos)?;
burn_array(input, &mut inpos)?;
} else {
burn_key_and_value(input, &mut inpos)?;
}
}
// Copy ids
if let Some(mut inpos) = start_ids {
let mut num_ids: u16 = 0;
// `inpos` is right after the open bracket of the array
loop {
eat_whitespace_and_commas(input, &mut inpos);
if input[inpos] == b']' {
break;
}
read_id(input, &mut inpos, &mut output[end..])?;
num_ids += 1;
end += ID_SIZE;
}
// Write num_ids
put(output, NUM_IDS_OFFSET, num_ids.to_ne_bytes().as_slice())?;
}
// Copy authors
if let Some(mut inpos) = start_authors {
let mut num_authors: u16 = 0;
// `inpos` is right after the open bracket of the array
loop {
eat_whitespace_and_commas(input, &mut inpos);
if input[inpos] == b']' {
break;
}
read_pubkey(input, &mut inpos, &mut output[end..])?;
num_authors += 1;
end += PUBKEY_SIZE;
}
// write num_authors
put(
output,
NUM_AUTHORS_OFFSET,
num_authors.to_ne_bytes().as_slice(),
)?;
}
// Copy kinds
if let Some(mut inpos) = start_kinds {
let mut num_kinds: u16 = 0;
// `inpos` is right after the open bracket of the array
loop {
eat_whitespace_and_commas(input, &mut inpos);
if input[inpos] == b']' {
break;
}
let u = read_u64(input, &mut inpos)?;
if u > 65535 {
return Err(
ChorusError::JsonBadFilter("Filter has kind number too large", inpos).into(),
);
}
put(output, end, (u as u16).to_ne_bytes().as_slice())?;
num_kinds += 1;
end += KIND_SIZE;
}
// write num_kinds
put(output, NUM_KINDS_OFFSET, num_kinds.to_ne_bytes().as_slice())?;
}
// Copy tags
{
let write_tags_start = end;
// write number of tags
put(
output,
write_tags_start + 2,
(num_tag_fields as u16).to_ne_bytes().as_slice(),
)?;
// bump end past offset fields
end += 4 + 2 * num_tag_fields;
// Now pull in each tag
#[allow(clippy::needless_range_loop)]
for w in 0..num_tag_fields {
// Write it's offset
put(
output,
write_tags_start + 4 + (2 * w),
((end - write_tags_start) as u16).to_ne_bytes().as_slice(),
)?;
let mut inpos = start_tags[w];
let letter = input[inpos];
// bump past count output and write letter
let countindex = end;
end += 2;
put(output, end, 1_u16.to_ne_bytes().as_slice())?;
if output.len() < end + 2 {
return Err(crate::error::ChorusError::BufferTooSmall.into());
}
output[end + 2] = letter;
// bump past what we just wrote
end += 3;
// scan further in input
inpos += 1; // move off letter
verify_char(input, b'"', &mut inpos)?;
eat_colon_with_whitespace(input, &mut inpos)?;
verify_char(input, b'[', &mut inpos)?;
let mut count: u16 = 1; // the tag letter itself counts
loop {
eat_whitespace_and_commas(input, &mut inpos);
if input[inpos] == b']' {
break;
}
verify_char(input, b'"', &mut inpos)?;
// copy data
let (inlen, outlen) = json_unescape(&input[inpos..], &mut output[end + 2..])?;
// write len
put(output, end, (outlen as u16).to_ne_bytes().as_slice())?;
end += 2 + outlen;
inpos += inlen + 1;
count += 1;
}
// write count
put(output, countindex, count.to_ne_bytes().as_slice())?;
}
// write length of tags section
put(
output,
write_tags_start,
((end - write_tags_start) as u16).to_ne_bytes().as_slice(),
)?;
}
if end > u32::MAX as usize {
return Err(ChorusError::JsonBadFilter("Filter is too long", end).into());
}
// Write length of filter
put(output, 0, (end as u32).to_ne_bytes().as_slice())?;
Ok((inpos, end))
}
#[cfg(test)]
mod test {
use super::*;
use crate::types::{Filter, Kind, Pubkey, Tags, TagsIter, TagsStringIter, Time};
#[test]
fn test_parse_json_empty_filter() {
let json = br##"{}"##;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (consumed, size) = parse_json_filter(&json[..], &mut buffer).unwrap();
assert_eq!(consumed, 2);
assert_eq!(size, 36);
assert_eq!(
&buffer[0..size],
&[
36, 0, 0, 0, // length
0, 0, // 0 ids
0, 0, // 0 authors
0, 0, // 0 kinds
0, 0, // padding
255, 255, 255, 255, // max limit
0, 0, 0, 0, 0, 0, 0, 0, // since 1970
255, 255, 255, 255, 255, 255, 255, 255, // until max unixtime
4, 0, 0, 0, // empty tags section
]
);
}
#[test]
fn test_parse_json_filter1() {
let json = br##"{"kinds":[1,30023],"since":1681778790,"authors":["e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"],"until":1704238196,"ids" : [ "7089afc2e77f366bc0fd1662e4048f59f18391c04a35957f21bbd1f3e6a492c4"],"limit":10}"##;
// ,"#e":"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7"}"##;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (consumed, size) = parse_json_filter(&json[..], &mut buffer).unwrap();
assert_eq!(consumed, json.len());
assert_eq!(size, 136);
assert_eq!(
&buffer[0..size],
&[
136, 0, 0, 0, // length
1, 0, // 0 ids
2, 0, // 0 authors
2, 0, // 0 kinds
0, 0, // padding
10, 0, 0, 0, // max limit 10
102, 232, 61, 100, 0, 0, 0, 0, // since 1681778790
116, 156, 148, 101, 0, 0, 0, 0, // until 1704238196
// First ID:
112, 137, 175, 194, 231, 127, 54, 107, 192, 253, 22, 98, 228, 4, 143, 89, 241, 131,
145, 192, 74, 53, 149, 127, 33, 187, 209, 243, 230, 164, 146, 196,
// First author:
226, 204, 247, 207, 32, 64, 63, 63, 42, 74, 85, 179, 40, 240, 222, 59, 227, 133, 88,
167, 213, 243, 54, 50, 253, 170, 239, 199, 38, 193, 200, 235,
// Second author:
44, 134, 171, 204, 152, 247, 253, 138, 103, 80, 170, 184, 223, 108, 24, 99, 144, 63,
16, 114, 6, 204, 45, 114, 232, 175, 235, 108, 56, 53, 122, 237, // Kinds,
1, 0, // 1
71, 117, // 30023
4, 0, 0, 0, // empty tags section
]
);
}
#[test]
fn test_parse_json_filter2() {
let json = br##"{"kinds":[1,30023],"since":1681778790,"authors":["e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"],"until":1704238196,"ids" : [ "7089afc2e77f366bc0fd1662e4048f59f18391c04a35957f21bbd1f3e6a492c4"],"limit":10, "#e":["a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7"]}"##;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (consumed, size) = parse_json_filter(&json[..], &mut buffer).unwrap();
assert_eq!(consumed, json.len());
assert_eq!(size, 209);
assert_eq!(
&buffer[0..size],
&[
209, 0, 0, 0, // length
1, 0, // 0 ids
2, 0, // 0 authors
2, 0, // 0 kinds
0, 0, // padding
10, 0, 0, 0, // max limit 10
102, 232, 61, 100, 0, 0, 0, 0, // since 1681778790
116, 156, 148, 101, 0, 0, 0, 0, // until 1704238196
// First ID:
112, 137, 175, 194, 231, 127, 54, 107, 192, 253, 22, 98, 228, 4, 143, 89, 241, 131,
145, 192, 74, 53, 149, 127, 33, 187, 209, 243, 230, 164, 146, 196,
// First author:
226, 204, 247, 207, 32, 64, 63, 63, 42, 74, 85, 179, 40, 240, 222, 59, 227, 133, 88,
167, 213, 243, 54, 50, 253, 170, 239, 199, 38, 193, 200, 235,
// Second author:
44, 134, 171, 204, 152, 247, 253, 138, 103, 80, 170, 184, 223, 108, 24, 99, 144, 63,
16, 114, 6, 204, 45, 114, 232, 175, 235, 108, 56, 53, 122, 237, // Kinds,
1, 0, // 1
71, 117, // 30023
// Tag section:
77, 0, // tags section length is 77
1, 0, // just one tag
6, 0, // offset of 0th tag is 6
// First tag:
2, 0, // 2 fields
// Field 1:
1, 0, // 1 byte long
101, // 'e'
// Field 2:
64, 0, // 64 bytes long
97, 57, 54, 54, 51, 48, 53, 53, 49, 54, 52, 97, 98, 56, 98, 51, 48, 100, 57, 53,
50, 52, 54, 53, 54, 51, 55, 48, 99, 52, 98, 102, 57, 51, 51, 57, 51, 98, 98, 48,
53, 49, 98, 55, 101, 100, 102, 52, 53, 53, 54, 102, 52, 48, 99, 53, 50, 57, 56,
100, 99, 48, 99, 55
]
);
}
#[test]
fn test_filter_parse_and_check() {
let json = br##"{"kinds":[1,5,9,30023],"since":1681778790,"authors":["e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"], "#e":["a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"],"#p":["2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed","2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"]}"##;
let mut buffer: Vec<u8> = Vec::with_capacity(4096);
buffer.resize(4096, 0);
let (consumed, size) = parse_json_filter(&json[..], &mut buffer).unwrap();
assert_eq!(consumed, json.len());
assert_eq!(size, 452);
let filter = Filter::delineate(&buffer).unwrap();
assert_eq!(filter.len(), 452);
assert_eq!(filter.num_ids(), 0);
assert_eq!(filter.num_authors(), 2);
let mut author_iter = filter.authors();
assert_eq!(
author_iter.next(),
Some(
Pubkey::read_hex(
b"e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb"
)
.unwrap()
)
);
assert_eq!(
author_iter.next(),
Some(
Pubkey::read_hex(
b"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed"
)
.unwrap()
)
);
assert_eq!(author_iter.next(), None);
assert_eq!(filter.num_kinds(), 4);
let mut kind_iter = filter.kinds();
assert_eq!(kind_iter.next(), Some(Kind(1)));
assert_eq!(kind_iter.next(), Some(Kind(5)));
assert_eq!(kind_iter.next(), Some(Kind(9)));
assert_eq!(kind_iter.next(), Some(Kind(30023)));
assert_eq!(kind_iter.next(), None);
assert_eq!(filter.limit(), u32::MAX);
assert_eq!(filter.since(), Time(1681778790));
assert_eq!(filter.until(), Time::max());
let tags: Tags = filter.tags().unwrap();
let mut tag_iter: TagsIter = tags.iter();
let mut tag1_iter: TagsStringIter = tag_iter.next().unwrap();
assert_eq!(tag1_iter.next(), Some(b"e".as_slice()));
assert_eq!(
tag1_iter.next(),
Some(b"a9663055164ab8b30d9524656370c4bf93393bb051b7edf4556f40c5298dc0c7".as_slice())
);
assert_eq!(
tag1_iter.next(),
Some(b"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed".as_slice())
);
assert_eq!(tag1_iter.next(), None);
let mut tag2_iter = tag_iter.next().unwrap();
assert_eq!(tag2_iter.next(), Some(b"p".as_slice()));
assert_eq!(
tag2_iter.next(),
Some(b"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed".as_slice())
);
assert_eq!(
tag2_iter.next(),
Some(b"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed".as_slice())
);
assert_eq!(
tag2_iter.next(),
Some(b"2c86abcc98f7fd8a6750aab8df6c1863903f107206cc2d72e8afeb6c38357aed".as_slice())
);
assert_eq!(tag2_iter.next(), None);
assert!(tag_iter.next().is_none());
}
}

View File

@ -1,537 +0,0 @@
use super::{Event, Id, Kind, Pubkey, Tags, Time};
use crate::error::{ChorusError, Error};
use std::fmt;
mod json_filter;
use json_filter::parse_json_filter;
/*
* 0 [4 bytes] length of entire structure
* 4 [2 bytes] num_ids
* 6 [2 bytes] num_authors
* 8 [2 bytes] num_kinds
* 10 [2 bytes] PADDING
* 12 [4 bytes] limit u32. Set to u32::max if limit was not set.
* 16 [8 bytes] since u64. Set to 0 if since was not set.
* 24 [8 bytes] until u64. Set to u64::max if until was not set.
* 32 [ID] array
* [Pubkey] array starts at 32 + num_ids*32
* [Kind] array starts at 32 + num_ids*32 + num_authors*32
* [Tags] object starts at 32 + num_ids*32 + num_authors*32 * num_kinds*2
*/
const NUM_IDS_OFFSET: usize = 4;
const NUM_AUTHORS_OFFSET: usize = 6;
const NUM_KINDS_OFFSET: usize = 8;
const LIMIT_OFFSET: usize = 12;
const SINCE_OFFSET: usize = 16;
const UNTIL_OFFSET: usize = 24;
const ARRAYS_OFFSET: usize = 32;
const ID_SIZE: usize = 32;
const PUBKEY_SIZE: usize = 32;
const KIND_SIZE: usize = 2;
#[derive(Debug, Clone)]
pub struct Filter<'a>(&'a [u8]);
impl<'a> Filter<'a> {
// Parse json into a Filter. Returns the count of consumed input bytes and output
// bytes the Filter
pub fn from_json(
json: &[u8],
output_buffer: &'a mut [u8],
) -> Result<(usize, usize, Filter<'a>), Error> {
let (incount, outcount) = parse_json_filter(json, output_buffer)?;
Ok((incount, outcount, Filter(&output_buffer[..outcount])))
}
pub fn delineate(input: &'a [u8]) -> Result<Filter<'a>, Error> {
if input.len() < ARRAYS_OFFSET {
return Err(ChorusError::EndOfInput.into());
}
let len = parse_u32!(input, 0) as usize;
if input.len() < len {
return Err(ChorusError::EndOfInput.into());
}
Ok(Filter(&input[0..len]))
}
pub fn copy(&self, output: &mut [u8]) -> Result<(), Error> {
if output.len() < self.0.len() {
return Err(ChorusError::EndOfInput.into());
}
output[..self.0.len()].copy_from_slice(self.0);
Ok(())
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn num_ids(&self) -> usize {
parse_u16!(self.0, NUM_IDS_OFFSET) as usize
}
#[inline]
pub fn ids(&'a self) -> FilterIdIter<'a> {
FilterIdIter {
filter: self,
next: 0,
}
}
#[inline]
pub fn num_authors(&self) -> usize {
parse_u16!(self.0, NUM_AUTHORS_OFFSET) as usize
}
#[inline]
fn start_of_authors(&self) -> usize {
ARRAYS_OFFSET + self.num_ids() * ID_SIZE
}
#[inline]
pub fn authors(&'a self) -> FilterAuthorIter<'a> {
FilterAuthorIter {
filter: self,
start_of_authors: self.start_of_authors(),
next: 0,
}
}
#[inline]
pub fn num_kinds(&self) -> usize {
parse_u16!(self.0, NUM_KINDS_OFFSET) as usize
}
#[inline]
fn start_of_kinds(&self) -> usize {
ARRAYS_OFFSET + self.num_ids() * ID_SIZE + self.num_authors() * PUBKEY_SIZE
}
#[inline]
pub fn kinds(&'a self) -> FilterKindIter<'a> {
FilterKindIter {
filter: self,
start_of_kinds: self.start_of_kinds(),
next: 0,
}
}
#[inline]
fn start_of_tags(&self) -> usize {
ARRAYS_OFFSET
+ self.num_ids() * ID_SIZE
+ self.num_authors() * PUBKEY_SIZE
+ self.num_kinds() * KIND_SIZE
}
#[inline]
pub fn tags(&'a self) -> Result<Tags<'a>, Error> {
Tags::delineate(&self.0[self.start_of_tags()..])
}
#[inline]
pub fn limit(&self) -> u32 {
parse_u32!(self.0, LIMIT_OFFSET)
}
#[inline]
pub fn since(&self) -> Time {
Time(parse_u64!(self.0, SINCE_OFFSET))
}
#[inline]
pub fn until(&self) -> Time {
Time(parse_u64!(self.0, UNTIL_OFFSET))
}
pub fn event_matches(&self, event: &Event) -> Result<bool, Error> {
// ids
if self.num_ids() != 0 && !self.ids().any(|id| id == event.id()) {
return Ok(false);
}
// authors
if self.num_authors() != 0 && !self.authors().any(|pk| pk == event.pubkey()) {
return Ok(false);
}
// kinds
if self.num_kinds() != 0 && !self.kinds().any(|kind| kind == event.kind()) {
return Ok(false);
}
// since
if event.created_at() < self.since() {
return Ok(false);
}
// until
if event.created_at() > self.until() {
return Ok(false);
}
// tags
let filter_tags = self.tags()?;
if !filter_tags.is_empty() {
let event_tags = event.tags()?;
if event_tags.is_empty() {
return Ok(false);
}
let mut i = 0;
while let Some(letter) = filter_tags.get_string(i, 0) {
let mut j = 1;
let mut found = false;
while let Some(value) = filter_tags.get_string(i, j) {
if event_tags.matches(letter, value) {
found = true;
break;
}
j += 1;
}
if !found {
return Ok(false);
}
i += 1;
}
}
Ok(true)
}
pub fn as_json(&self) -> Result<Vec<u8>, Error> {
let mut output: Vec<u8> = Vec::with_capacity(256);
output.push(b'{');
let mut first = true;
if self.num_ids() > 0 {
output.extend(br#""ids":["#);
for (i, id) in self.ids().enumerate() {
if i > 0 {
output.push(b',');
}
output.push(b'"');
let pos = output.len();
output.resize(pos + 64, 0);
id.write_hex(&mut output[pos..])?;
output.push(b'"');
}
output.push(b']');
first = false;
}
if self.num_authors() > 0 {
if !first {
output.push(b',');
}
output.extend(br#""authors":["#);
for (i, pk) in self.authors().enumerate() {
if i > 0 {
output.push(b',');
}
output.push(b'"');
let pos = output.len();
output.resize(pos + 64, 0);
pk.write_hex(&mut output[pos..])?;
output.push(b'"');
}
output.push(b']');
first = false;
}
if self.num_kinds() > 0 {
if !first {
output.push(b',');
}
output.extend(br#""kinds":["#);
for (i, k) in self.kinds().enumerate() {
if i > 0 {
output.push(b',');
}
output.extend(format!("{}", k.0).as_bytes());
}
output.push(b']');
first = false;
}
let tags = self.tags()?;
if !tags.is_empty() {
// Filter 'tags' are not an array of arrays, they are just a convenient
// way to store similar data. They also elide the '#'. So we have to
// iterate here, we cannot use tags.as_json()
for tag in tags.iter() {
if !first {
output.push(b',');
}
for (i, bytes) in tag.enumerate() {
if i == 0 {
output.extend(b"\"#");
output.extend(bytes);
output.extend(b"\":[");
} else {
if i > 1 {
output.push(b',');
}
output.push(b'"');
output.extend(bytes);
output.push(b'"');
}
}
output.push(b']');
first = false;
}
}
if self.limit() != u32::MAX {
if !first {
output.push(b',');
}
output.extend(format!(r#""limit":{}"#, self.limit()).as_bytes());
first = false;
}
if self.since() != Time::min() {
if !first {
output.push(b',');
}
output.extend(format!(r#""since":{}"#, self.since().0).as_bytes());
first = false;
}
if self.until() != Time::max() {
if !first {
output.push(b',');
}
output.extend(format!(r#""until":{}"#, self.until().0).as_bytes());
}
output.push(b'}');
Ok(output)
}
}
impl fmt::Display for Filter<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Ok(bytes) = self.as_json() {
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{s}")
} else {
write!(f, "{{Corrupted Event}}")
}
}
}
#[derive(Debug)]
pub struct FilterIdIter<'a> {
filter: &'a Filter<'a>,
next: usize,
}
impl<'a> Iterator for FilterIdIter<'a> {
type Item = Id;
fn next(&mut self) -> Option<Self::Item> {
let num_ids = parse_u16!(self.filter.0, NUM_IDS_OFFSET) as usize;
if self.next >= num_ids {
None
} else {
let offset = ARRAYS_OFFSET + self.next * ID_SIZE;
self.next += 1;
if self.filter.0.len() < offset + ID_SIZE {
None
} else {
Some(Id(self.filter.0[offset..offset + ID_SIZE]
.try_into()
.unwrap()))
}
}
}
}
#[derive(Debug)]
pub struct FilterAuthorIter<'a> {
filter: &'a Filter<'a>,
start_of_authors: usize,
next: usize,
}
impl<'a> Iterator for FilterAuthorIter<'a> {
type Item = Pubkey;
fn next(&mut self) -> Option<Self::Item> {
let num_authors = parse_u16!(self.filter.0, NUM_AUTHORS_OFFSET) as usize;
if self.next >= num_authors {
None
} else {
let offset = self.start_of_authors + self.next * PUBKEY_SIZE;
self.next += 1;
if self.filter.0.len() < offset + PUBKEY_SIZE {
None
} else {
Some(Pubkey(
self.filter.0[offset..offset + PUBKEY_SIZE]
.try_into()
.unwrap(),
))
}
}
}
}
#[derive(Debug)]
pub struct FilterKindIter<'a> {
filter: &'a Filter<'a>,
start_of_kinds: usize,
next: usize,
}
impl<'a> Iterator for FilterKindIter<'a> {
type Item = Kind;
fn next(&mut self) -> Option<Self::Item> {
let num_kinds = parse_u16!(self.filter.0, NUM_KINDS_OFFSET) as usize;
if self.next >= num_kinds {
None
} else {
let offset = self.start_of_kinds + self.next * KIND_SIZE;
self.next += 1;
if self.filter.0.len() < offset + KIND_SIZE {
None
} else {
Some(Kind(parse_u16!(self.filter.0, offset)))
}
}
}
}
#[derive(Debug, Clone)]
pub struct OwnedFilter(pub Vec<u8>);
impl OwnedFilter {
pub fn as_filter(&self) -> Result<Filter<'_>, Error> {
Filter::delineate(&self.0)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_filter() {
/*
* {
* "ids": [ "6b43bc2e373b6d9330ff571f3f4e6d897b32d01d65227df3fa41cdf731c63c3a",
* "1f47034c9d6d0539382a86ba31766f00f2b8312ab167c036729422ec9e7085e8"],
* "authors": [ "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd",
* "ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49" ],
* "kinds": [ 1, 5, 30023 ],
* "since": 1702161345,
* "#p": [ "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52" ],
* }
*/
// For comparison
let id1 = Id::read_hex(b"6b43bc2e373b6d9330ff571f3f4e6d897b32d01d65227df3fa41cdf731c63c3a")
.unwrap();
let id2 = Id::read_hex(b"1f47034c9d6d0539382a86ba31766f00f2b8312ab167c036729422ec9e7085e8")
.unwrap();
let pk1 =
Pubkey::read_hex(b"52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd")
.unwrap();
let pk2 =
Pubkey::read_hex(b"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49")
.unwrap();
let tagged =
Pubkey::read_hex(b"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52")
.unwrap();
let data: Vec<u8> = vec![
211, 0, 0, 0, // length of structure
2, 0, // number of IDs
2, 0, // number of authors
3, 0, // number of kinds
0, 0, // padding
255, 255, 255, 255, // limit
0xC1, 0xEB, 0x74, 0x65, 0, 0, 0, 0, // since
255, 255, 255, 255, 255, 255, 255, 255, // until
0x6b, 0x43, 0xbc, 0x2e, 0x37, 0x3b, 0x6d, 0x93, 0x30, 0xff, 0x57, 0x1f, 0x3f, 0x4e,
0x6d, 0x89, 0x7b, 0x32, 0xd0, 0x1d, 0x65, 0x22, 0x7d, 0xf3, 0xfa, 0x41, 0xcd, 0xf7,
0x31, 0xc6, 0x3c, 0x3a, // ID 1
0x1f, 0x47, 0x03, 0x4c, 0x9d, 0x6d, 0x05, 0x39, 0x38, 0x2a, 0x86, 0xba, 0x31, 0x76,
0x6f, 0x00, 0xf2, 0xb8, 0x31, 0x2a, 0xb1, 0x67, 0xc0, 0x36, 0x72, 0x94, 0x22, 0xec,
0x9e, 0x70, 0x85, 0xe8, // ID 2
0x52, 0xb4, 0xa0, 0x76, 0xbc, 0xbb, 0xbd, 0xc3, 0xa1, 0xae, 0xfa, 0x37, 0x35, 0x81,
0x6c, 0xf7, 0x49, 0x93, 0xb1, 0xb8, 0xdb, 0x20, 0x2b, 0x01, 0xc8, 0x83, 0xc5, 0x8b,
0xe7, 0xfa, 0xd8, 0xbd, // Pubkey 1
0xee, 0x11, 0xa5, 0xdf, 0xf4, 0x0c, 0x19, 0xa5, 0x55, 0xf4, 0x1f, 0xe4, 0x2b, 0x48,
0xf0, 0x0e, 0x61, 0x8c, 0x91, 0x22, 0x56, 0x22, 0xae, 0x37, 0xb6, 0xc2, 0xbb, 0x67,
0xb7, 0x6c, 0x4e, 0x49, // Pubkey 2
1, 0, 5, 0, 71, 117, // 3 kinds
// Tags
45, 0, // tags_len
1, 0, // num_tags
6, 0, // first tag offset at 6
2, 0, // 2 fields long
1, 0, // 1st field is 1 byte
112, // "p"
32, 0, // 2nd field is 32 bytes
// 2nd field
0xfa, 0x98, 0x4b, 0xd7, 0xdb, 0xb2, 0x82, 0xf0, 0x7e, 0x16, 0xe7, 0xae, 0x87, 0xb2,
0x6a, 0x2a, 0x7b, 0x9b, 0x90, 0xb7, 0x24, 0x6a, 0x44, 0x77, 0x1f, 0x0c, 0xf5, 0xae,
0x58, 0x01, 0x8f, 0x52,
];
let filter = Filter::delineate(&data).unwrap();
assert_eq!(filter.num_ids(), 2);
let mut ids = filter.ids();
assert_eq!(ids.next().unwrap(), id1);
assert_eq!(ids.next().unwrap(), id2);
assert!(ids.next().is_none());
assert_eq!(filter.num_authors(), 2);
let mut authors = filter.authors();
assert_eq!(authors.next().unwrap(), pk1);
assert_eq!(authors.next().unwrap(), pk2);
assert!(authors.next().is_none());
assert_eq!(filter.num_kinds(), 3);
let mut kinds = filter.kinds();
assert_eq!(kinds.next().unwrap(), Kind(1));
assert_eq!(kinds.next().unwrap(), Kind(5));
assert_eq!(kinds.next().unwrap(), Kind(30023));
assert!(kinds.next().is_none());
assert_eq!(filter.limit(), u32::MAX);
assert_eq!(filter.since(), Time(1702161345));
assert_eq!(filter.until(), Time::max());
let tags = filter.tags().unwrap();
assert_eq!(tags.len(), 1);
let mut iter = tags.iter();
let mut tag = iter.next().unwrap();
assert!(iter.next().is_none());
assert_eq!(tag.next().unwrap(), b"p");
let p_bytes = tag.next().unwrap();
assert!(tag.next().is_none());
let pk = Pubkey(p_bytes.try_into().unwrap());
assert_eq!(pk, tagged);
}
}

View File

@ -1,47 +0,0 @@
use crate::error::Error;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Id(pub [u8; 32]);
impl Id {
pub fn write_hex(&self, output: &mut [u8]) -> Result<(), Error> {
write_hex!(self.0, output, 32)
}
pub fn read_hex(input: &[u8]) -> Result<Id, Error> {
let mut out: [u8; 32] = [0; 32];
read_hex!(input, &mut out, 32)?;
Ok(Id(out))
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes: [u8; 64] = [0; 64];
self.write_hex(&mut bytes).unwrap();
let hex = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{hex}")
}
}
#[cfg(test)]
mod test {
use super::Id;
#[test]
fn test_id_hex_functions() {
let hex = b"1110ee4ff957fa9c55832eaccb4dc1c45bfc6304e1e4e9fa478f53df4b20062d";
let id = Id::read_hex(hex).unwrap();
eprintln!("{:?}", id);
let mut hex2: [u8; 64] = [0; 64];
id.write_hex(&mut hex2).unwrap();
assert_eq!(hex, &hex2);
assert_eq!(format!("{}", id).as_bytes(), hex);
}
}

View File

@ -1,28 +0,0 @@
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Kind(pub u16);
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Kind {
// Some kinds a relay may need to treat differently
pub const SEAL: u16 = 13;
pub const DM_CHAT: u16 = 14;
pub fn is_replaceable(&self) -> bool {
(10000..20000).contains(&self.0) || self.0 == 0 || self.0 == 3
}
pub fn is_ephemeral(&self) -> bool {
(20000..30000).contains(&self.0)
}
pub fn is_parameterized_replaceable(&self) -> bool {
(30000..40000).contains(&self.0)
}
}

View File

@ -1,25 +0,0 @@
mod event;
pub use event::{Event, OwnedEvent};
mod filter;
pub use filter::{Filter, OwnedFilter};
mod id;
pub use id::Id;
mod kind;
pub use kind::Kind;
pub mod parse;
mod pubkey;
pub use pubkey::Pubkey;
mod sig;
pub use sig::Sig;
mod tags;
pub use tags::{Tags, TagsIter, TagsStringIter};
mod time;
pub use time::Time;

View File

@ -1,254 +0,0 @@
use super::utf8::{encode_utf8, next_code_point};
use crate::error::{ChorusError, Error};
// LITERAL UNESCAPED: 0x20-0x21, 0x23-0x5B, 0x5D-10FFFF
// ESCAPES: \" \\ \/ /b /f /n /r /t
// UTF ESCAPE: \uXXXX or \uXXXX\uXXXX
// This escapes a string with JSON escapes. It takes an output buffer,
// and returns it filled with the escaped string.
// For performance you should try to pass in a buffer that is already
// allocated big enough.
pub fn json_escape(input: &[u8], mut out: Vec<u8>) -> Result<Vec<u8>, Error> {
let mut read_pos: usize = 0;
while let Some((codepoint, size)) = next_code_point(&input[read_pos..])? {
if is_safe_char(codepoint) {
out.extend(&input[read_pos..read_pos + size]);
} else {
match codepoint {
0x08 => out.extend("\\b".as_bytes()),
0x09 => out.extend("\\t".as_bytes()),
0x0A => out.extend("\\n".as_bytes()),
0x0C => out.extend("\\f".as_bytes()),
0x0D => out.extend("\\r".as_bytes()),
0x22 => out.extend("\\\"".as_bytes()),
0x5C => out.extend("\\\\".as_bytes()),
_ => {
if codepoint > 0x20 {
panic!("unnecessary encoding requested");
}
// This violates NIP-01 which doesn't allow characters like 0x00
// even though JSON UTF-8 does.
out.extend(format!("\\u{:04x}", codepoint).as_bytes());
}
}
}
read_pos += size;
}
Ok(out)
}
macro_rules! output_slice {
($slice:expr, $out:expr, $pos:expr) => {
if $out.len() < *$pos + $slice.len() {
Err(Into::<crate::error::Error>::into(
crate::error::ChorusError::BufferTooSmall,
))
} else {
$out[*$pos..*$pos + $slice.len()].copy_from_slice($slice);
*$pos += $slice.len();
Ok(())
}
};
}
macro_rules! output_byte {
($byte:expr, $out:expr, $pos:expr) => {
if $out.len() < *$pos + 1 {
Err(Into::<crate::error::Error>::into(
crate::error::ChorusError::BufferTooSmall,
))
} else {
unsafe { *$out.get_unchecked_mut(*$pos) = $byte };
*$pos += 1;
Ok(())
}
};
}
/// This unescapes a JSON string into the output.
///
/// The input should start on the first character of the string, and may extend
/// to the ending double-quote and even further.
///
/// This will return how much input was consumed and how much output was written
/// in that order (input_len, output_len)
pub fn json_unescape(input: &[u8], out: &mut [u8]) -> Result<(usize, usize), Error> {
const BACKSPACE: u8 = 0x08;
const FORMFEED: u8 = 0x0C;
const LINEFEED: u8 = 0x0A;
const CR: u8 = 0x0D;
const TAB: u8 = 0x09;
const QUOTE: u8 = 0x22;
const BACKSLASH: u8 = 0x5C;
const SLASH: u8 = 0x2F;
// Write position in the output buffer
let mut write_pos: usize = 0;
let mut inescape: bool = false;
let mut uescape: Option<(usize, u32)> = None;
let mut p: usize = 0;
while let Some((codepoint, size)) = next_code_point(&input[p..])? {
if inescape {
inescape = false;
if codepoint > 255 {
return Err(ChorusError::JsonEscape.into());
}
match codepoint as u8 {
QUOTE | BACKSLASH | SLASH => {
output_slice!(&input[p..p + size], out, &mut write_pos)?
}
b'b' => output_byte!(BACKSPACE, out, &mut write_pos)?,
b'f' => output_byte!(FORMFEED, out, &mut write_pos)?,
b'n' => output_byte!(LINEFEED, out, &mut write_pos)?,
b'r' => output_byte!(CR, out, &mut write_pos)?,
b't' => output_byte!(TAB, out, &mut write_pos)?,
// This violates NIP-01 but we accept it
b'u' => uescape = Some((0, 0)),
_ => return Err(ChorusError::JsonEscape.into()), // nothing else is a legal escape
}
} else if let Some((digit, total)) = uescape {
// must be a digit
if !(48..=57).contains(&codepoint) {
return Err(ChorusError::JsonEscape.into());
}
let total = total + ((codepoint - 48) << (4 * (3 - digit)));
if digit >= 3 {
if (0xD800..=0xDFFF).contains(&total) {
return Err(ChorusError::JsonEscapeSurrogate.into());
}
let s = encode_utf8(total, &mut out[write_pos..])?;
write_pos += s;
uescape = None;
} else {
uescape = Some((digit + 1, total));
}
} else if codepoint == 0x5C {
// backslash
inescape = true;
} else if is_safe_char(codepoint) {
output_slice!(&input[p..p + size], out, &mut write_pos)?;
} else if codepoint == 0x22 {
// ending double quote
break;
} else {
return Err(ChorusError::JsonBadStringChar(codepoint).into());
}
p += size;
}
Ok((p, write_pos))
}
#[inline]
fn is_safe_char(c: u32) -> bool {
let safe_ranges = [(0x20..=0x21), (0x23..=0x5B), (0x5D..=0x10FFFF)];
safe_ranges.iter().any(|range| range.contains(&c))
}
#[cfg(test)]
mod test {
use super::{json_escape, json_unescape};
#[test]
fn test_json_escape() {
let buffer = Vec::with_capacity(255);
let input = "hello\t\tworld
!!!";
let mut buffer = json_escape(input.as_bytes(), buffer).unwrap();
assert_eq!(&buffer[0..19], br#"hello\t\tworld\n!!!"#);
buffer.clear();
let input: [u8; 11] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut buffer = json_escape(input.as_slice(), buffer).unwrap();
assert_eq!(
&buffer[0..54],
br#"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n"#
);
buffer.clear();
let input: [u8; 12] = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22];
let mut buffer = json_escape(input.as_slice(), buffer).unwrap();
assert_eq!(
&buffer[0..64],
br#"\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016"#
);
buffer.clear();
let input: [u8; 4] = [32, 33, 34, 35];
let mut buffer = json_escape(input.as_slice(), buffer).unwrap();
assert_eq!(&buffer[0..5], br##" !\"#"##);
buffer.clear();
let input: [u8; 1] = [92];
let buffer = json_escape(input.as_slice(), buffer).unwrap();
assert_eq!(&buffer[0..2], br#"\\"#);
}
#[test]
fn test_json_unescape() {
let is_ok = |s: &[u8], equals: &[u8]| {
let mut buffer: Vec<u8> = Vec::with_capacity(1024);
buffer.resize(1024, 0);
let r = json_unescape(s, &mut buffer);
assert!(r.is_ok());
let (_inlen, outlen) = r.unwrap();
assert_eq!(outlen, equals.len());
assert_eq!(&buffer[0..equals.len()], equals);
};
let is_err = |s: &[u8]| {
let mut buffer: Vec<u8> = Vec::with_capacity(1024);
buffer.resize(1024, 0);
let r = json_unescape(s, &mut buffer);
assert!(r.is_err());
};
// simple string
is_ok(&b"abc".as_slice(), b"abc");
// carraige return
is_ok(&br#"ab\nc"#.as_slice(), b"ab\nc");
// escaping a character that is not allowed
is_err(&br#"ab\zc"#.as_slice());
// escaping quotes is allowed
is_ok(&br#" \"abc\" "#.as_slice(), br#" "abc" "#);
// high character
is_ok(r#"𝄞"#.as_bytes(), "𝄞".as_bytes());
// high character is interpreted as these four bytes
is_ok(r#"𝄞"#.as_bytes(), b"\xF0\x9D\x84\x9E");
// esacaping a character that is not allowed
is_err(r#"\𝄞"#.as_bytes());
// actual unescaped tab is disallowed
is_err("\t".as_bytes());
// unicode escape and more
is_ok(
r#"{\"name\":\"BagMan\",\"about\":\"Father.\nHusband.\nNerd: \u2020.\"}"#.as_bytes(),
"{\"name\":\"BagMan\",\"about\":\"Father.\nHusband.\nNerd: †.\"}".as_bytes(),
);
// bad unicode escape
is_err(r#"\u8f00"#.as_bytes());
// Check output values
let mut buffer: Vec<u8> = Vec::with_capacity(1024);
buffer.resize(1024, 0);
let (inlen, outlen) = json_unescape(br#"the\nclient", "gossip""#, &mut buffer).unwrap();
assert_eq!(inlen, 11);
assert_eq!(outlen, 10);
}
}

View File

@ -1,476 +0,0 @@
use super::json_escape::json_unescape;
use super::put;
use crate::error::{ChorusError, Error};
#[inline]
pub fn eat_whitespace(input: &[u8], inposp: &mut usize) {
while *inposp < input.len() && [0x20, 0x09, 0x0A, 0x0D].contains(&input[*inposp]) {
*inposp += 1;
}
}
#[inline]
pub fn eat_whitespace_and_commas(input: &[u8], inposp: &mut usize) {
while *inposp < input.len() && [0x20, 0x09, 0x0A, 0x0D, b','].contains(&input[*inposp]) {
*inposp += 1;
}
}
#[inline]
pub fn verify_char(input: &[u8], ch: u8, inposp: &mut usize) -> Result<(), Error> {
if *inposp >= input.len() {
Err(ChorusError::JsonBad("Too Short or Missing Fields", *inposp).into())
} else if input[*inposp] == ch {
*inposp += 1;
Ok(())
} else {
Err(ChorusError::JsonBadCharacter(input[*inposp] as char, *inposp, ch as char).into())
}
}
pub fn eat_colon_with_whitespace(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
eat_whitespace(input, inposp);
verify_char(input, b':', inposp)?;
eat_whitespace(input, inposp);
Ok(())
}
pub fn next_object_field(input: &[u8], inposp: &mut usize) -> Result<bool, Error> {
eat_whitespace(input, inposp);
// next comes either comma or end brace
if *inposp >= input.len() {
return Err(ChorusError::JsonBad("Too short", *inposp).into());
}
if input[*inposp] == b'}' {
*inposp += 1;
Ok(true)
} else if input[*inposp] == b',' {
*inposp += 1;
Ok(false)
} else {
Err(ChorusError::JsonBad("Unexpected char", *inposp).into())
}
}
pub fn read_id(input: &[u8], inposp: &mut usize, output: &mut [u8]) -> Result<(), Error> {
if output.len() < 32 {
return Err(ChorusError::BufferTooSmall.into());
}
verify_char(input, b'"', inposp)?;
if *inposp + 64 >= input.len() {
return Err(ChorusError::JsonBad("Too short reading id", *inposp).into());
}
// Read the hex ID and write the binary ID into the output event structure
read_hex!(&input[*inposp..*inposp + 64], &mut output[..32], 32)?;
*inposp += 64;
verify_char(input, b'"', inposp)?;
Ok(())
}
pub fn read_pubkey(input: &[u8], inposp: &mut usize, output: &mut [u8]) -> Result<(), Error> {
if output.len() < 32 {
return Err(ChorusError::BufferTooSmall.into());
}
verify_char(input, b'"', inposp)?;
if *inposp + 64 >= input.len() {
return Err(ChorusError::JsonBad("Too short reading pubkey", *inposp).into());
}
// Read the hex pubkey and write the binary pubkey into the output event structure
read_hex!(&input[*inposp..*inposp + 64], &mut output[..32], 32)?;
*inposp += 64;
verify_char(input, b'"', inposp)?;
Ok(())
}
pub fn read_u64(input: &[u8], inposp: &mut usize) -> Result<u64, Error> {
let mut value: u64 = 0;
let mut any: bool = false;
while *inposp < input.len() && b"0123456789".contains(&input[*inposp]) {
any = true;
value = (value * 10) + (input[*inposp] - 48) as u64;
*inposp += 1;
}
if !any {
return Err(ChorusError::JsonBad("Expected a positive integer", *inposp).into());
}
Ok(value)
}
pub fn read_kind(input: &[u8], inposp: &mut usize) -> Result<u16, Error> {
let mut value: u32 = 0;
let mut any: bool = false;
while *inposp < input.len() && b"0123456789".contains(&input[*inposp]) {
any = true;
value = (value * 10) + (input[*inposp] - 48) as u32;
*inposp += 1;
}
if !any {
return Err(ChorusError::JsonBad(
"Kind at must be a positive or zero valued number",
*inposp,
)
.into());
}
if value > 65535 {
Err(ChorusError::JsonBad("Kind larger than 65535", *inposp).into())
} else {
Ok(value as u16)
}
}
// From the outer bracket through to the character after the close outer bracket
// returns the size of data written to the output
pub fn read_tags_array(
input: &[u8],
inposp: &mut usize,
output: &mut [u8],
) -> Result<usize, Error> {
verify_char(input, b'[', inposp)?; // outer array open brace
eat_whitespace(input, inposp);
if output.len() < 4 {
return Err(ChorusError::BufferTooSmall.into());
}
// NOTE: we cannot write any tag strings until after we have counted the tags.
// (our tags structure is optimized for reading, not writing)
let num_tags: usize = count_tags(input, *inposp)?;
put(output, 2, (num_tags as u16).to_ne_bytes().as_slice())?;
// Case where we have no tags
if num_tags == 0 {
put(output, 0, 4_u16.to_ne_bytes().as_slice())?;
burn_array(input, inposp)?;
return Ok(4);
}
verify_char(input, b'[', inposp)?; // opening brace of first tag
eat_whitespace(input, inposp);
let mut tag_num = 0;
let mut outpos: usize = 4 + num_tags * 2;
if output.len() < outpos {
return Err(ChorusError::BufferTooSmall.into());
}
loop {
// Write the offset of this tag
let offset_slot = 4 + tag_num * 2;
put(
output,
offset_slot,
(outpos as u16).to_ne_bytes().as_slice(),
)?;
// Read the tag (bumps inpos and outpos)
read_tag(input, inposp, output, &mut outpos)?;
eat_whitespace(input, inposp);
// Check what is next
match input[*inposp] {
b']' => {
*inposp += 1;
if tag_num != num_tags - 1 {
panic!("Tag count mismatch");
}
break;
}
b',' => {
*inposp += 1;
eat_whitespace(input, inposp);
verify_char(input, b'[', inposp)?;
tag_num += 1;
if tag_num >= num_tags {
panic!("Tag count mismatch");
}
eat_whitespace(input, inposp);
}
_ => return Err(ChorusError::JsonBad("Tag array bad character", *inposp).into()),
}
}
// Write length of tags section
put(output, 0, (outpos as u16).to_ne_bytes().as_slice())?;
Ok(outpos)
}
// From the first inner tag bracket, ending after the outer bracket.
// This just counts the tags, it does not write output or modify the inpos
// This does a quicker pass over the content than actual tag parsing does.
pub fn count_tags(input: &[u8], mut inpos: usize) -> Result<usize, Error> {
// First non-whitespace character after the opening brace
match input[inpos] {
b']' => return Ok(0), // no tags
b'[' => (), // expected
_ => return Err(ChorusError::JsonBad("Tag array bad initial character", inpos).into()),
}
let mut count = 1;
inpos += 1;
burn_tag(input, &mut inpos)?;
eat_whitespace(input, &mut inpos);
loop {
match input[inpos] {
b']' => return Ok(count),
b',' => {
inpos += 1;
eat_whitespace(input, &mut inpos);
verify_char(input, b'[', &mut inpos)?;
count += 1;
burn_tag(input, &mut inpos)?;
eat_whitespace(input, &mut inpos);
}
_ => return Err(ChorusError::JsonBad("Tag array bad character", inpos).into()),
}
}
}
pub fn read_tag(
input: &[u8],
inposp: &mut usize,
output: &mut [u8],
outposp: &mut usize,
) -> Result<(), Error> {
let countpos = *outposp;
*outposp += 2;
// handle empty tag
if input[*inposp] == b']' {
*inposp += 1;
put(output, countpos, 0_u16.to_ne_bytes().as_slice())?;
return Ok(());
}
verify_char(input, b'"', inposp)?;
let mut num_strings: usize = 1;
loop {
// read string
let (inlen, outlen) = json_unescape(&input[*inposp..], &mut output[*outposp + 2..])?;
// write the length before it
put(output, *outposp, (outlen as u16).to_ne_bytes().as_slice())?;
// bump the outposp past it
*outposp += 2 + outlen;
// bump the inpos past the string (and the ending quote which isn't counted in the len)
*inposp += inlen + 1;
eat_whitespace(input, inposp);
match input[*inposp] {
b',' => {
*inposp += 1;
eat_whitespace(input, inposp);
verify_char(input, b'"', inposp)?;
num_strings += 1;
continue;
}
b']' => {
*inposp += 1;
break;
}
_ => return Err(ChorusError::JsonBad("Tag array bad character", *inposp).into()),
}
}
// Write the count of strings at the very start
put(
output,
countpos,
(num_strings as u16).to_ne_bytes().as_slice(),
)?;
Ok(())
}
pub fn read_content(
input: &[u8],
inposp: &mut usize,
output: &mut [u8],
after_tags: usize,
) -> Result<(), Error> {
verify_char(input, b'"', inposp)?;
// Place content 4 bytes beyond tags, to reserve space for content length
let (inlen, outlen) = json_unescape(&input[*inposp..], &mut output[after_tags + 4..])?;
*inposp += inlen + 1; // +1 to pass the end quote
// Write content length
put(output, after_tags, (outlen as u32).to_ne_bytes().as_slice())?;
// Write event size
let event_len = after_tags + 4 + outlen;
put(output, 0, (event_len as u32).to_ne_bytes().as_slice())?;
Ok(())
}
// FIXME this is too event-offset specific
pub fn read_sig(input: &[u8], inposp: &mut usize, output: &mut [u8]) -> Result<(), Error> {
if output.len() < 144 {
return Err(ChorusError::BufferTooSmall.into());
}
verify_char(input, b'"', inposp)?;
if *inposp + 128 >= input.len() {
return Err(ChorusError::JsonBad("Too short reading sig", *inposp).into());
}
// Read the hex sig and write the binary sig into the output event structure
read_hex!(&input[*inposp..*inposp + 128], &mut output[80..144], 64)?;
*inposp += 128;
verify_char(input, b'"', inposp)?;
Ok(())
}
// from the character after the start quote
// ending on the character following the end quote
pub fn burn_string(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
while *inposp < input.len() && input[*inposp] != b'"' {
if input[*inposp] == b'\\' && *inposp + 1 < input.len() {
*inposp += 2;
} else {
*inposp += 1;
}
}
if input[*inposp] == b'"' {
*inposp += 1;
Ok(())
} else {
Err(ChorusError::JsonBad("Unterminated string", *inposp).into())
}
}
// from the character after the open brace
// ending on the character following the close brace
pub fn burn_tag(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
eat_whitespace(input, inposp);
// handle empty tag
if input[*inposp] == b']' {
*inposp += 1;
return Ok(());
}
verify_char(input, b'"', inposp)?;
burn_string(input, inposp)?;
eat_whitespace(input, inposp);
while input[*inposp] == b',' {
*inposp += 1;
eat_whitespace(input, inposp);
verify_char(input, b'"', inposp)?;
burn_string(input, inposp)?;
eat_whitespace(input, inposp);
}
verify_char(input, b']', inposp)?;
Ok(())
}
pub fn burn_key_and_value(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
verify_char(input, b'"', inposp)?;
burn_string(input, inposp)?;
eat_colon_with_whitespace(input, inposp)?;
burn_value(input, inposp)?;
Ok(())
}
// from the character after the open brace
// ending on the character following the close brace
pub fn burn_object(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
loop {
eat_whitespace_and_commas(input, inposp);
// Check for the end
if input[*inposp] == b'}' {
*inposp += 1;
return Ok(());
}
burn_key_and_value(input, inposp)?;
}
}
// from the character after the open bracket
// ending on the character following the close bracket
pub fn burn_array(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
loop {
eat_whitespace_and_commas(input, inposp);
// Check for the end
if input[*inposp] == b']' {
*inposp += 1;
return Ok(());
}
burn_value(input, inposp)?;
}
}
pub fn burn_value(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
if *inposp >= input.len() {
return Err(ChorusError::JsonBad("Too short burning an unused JSON value", *inposp).into());
}
match input[*inposp] {
b'"' => {
*inposp += 1;
burn_string(input, inposp)?
}
b'[' => {
*inposp += 1;
burn_array(input, inposp)?
}
b'{' => {
*inposp += 1;
burn_object(input, inposp)?
}
b't' => burn_true(input, inposp)?,
b'f' => burn_false(input, inposp)?,
b'n' => burn_null(input, inposp)?,
b'-' => burn_number(input, inposp)?,
_ => {
if b"123456789".contains(&input[*inposp]) {
burn_number(input, inposp)?
} else {
return Err(ChorusError::JsonBad(
"Too short burning an unused JSON value",
*inposp,
)
.into());
}
}
}
Ok(())
}
pub fn burn_null(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
if *inposp + 4 <= input.len() && &input[*inposp..*inposp + 4] == b"null" {
*inposp += 4;
Ok(())
} else {
Err(ChorusError::JsonBad("Expected null", *inposp).into())
}
}
pub fn burn_true(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
if *inposp + 4 <= input.len() && &input[*inposp..*inposp + 4] == b"true" {
*inposp += 4;
Ok(())
} else {
Err(ChorusError::JsonBad("Expected true", *inposp).into())
}
}
pub fn burn_false(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
if *inposp + 5 <= input.len() && &input[*inposp..*inposp + 5] == b"false" {
*inposp += 5;
Ok(())
} else {
Err(ChorusError::JsonBad("Expected false", *inposp).into())
}
}
pub fn burn_number(input: &[u8], inposp: &mut usize) -> Result<(), Error> {
// For burning, we don't check validity.
while *inposp < input.len() && b".+-0123456789abcdefABCDEF_oOxXn".contains(&input[*inposp]) {
*inposp += 1;
}
Ok(())
}

View File

@ -1,15 +0,0 @@
pub mod json_escape;
pub mod json_parse;
pub mod utf8;
#[inline]
pub fn put(output: &mut [u8], offset: usize, data: &[u8]) -> Result<(), crate::error::Error> {
if output.len() < offset + data.len() {
Err(crate::error::ChorusError::BufferTooSmall.into())
} else {
output[offset..offset + data.len()].copy_from_slice(data);
Ok(())
}
}

View File

@ -1,165 +0,0 @@
use crate::error::{ChorusError, Error};
// Reads the next code point if UTF-8, and returns it along with the number of characters
// that make it up.
pub fn next_code_point(input: &[u8]) -> Result<Option<(u32, usize)>, Error> {
let len = input.len();
if len < 1 {
return Ok(None);
}
// Decode UTF-8
let x = input[0];
if x < 128 {
return Ok(Some((x as u32, 1)));
}
// Multibyte case follows
// Decode from a byte combination out of: [[[x y] z] w]
let init = utf8_first_byte(x, 2);
if len < 2 {
return Err(ChorusError::Utf8Error.into());
}
let y = input[1];
let mut ch = utf8_acc_cont_byte(init, y);
if x >= 0xE0 {
// [[x y z] w] case
// 5th bit in 0xE0 .. 0xEF is always clear, so `init` is still valid
if len < 3 {
return Err(ChorusError::Utf8Error.into());
}
let z = input[2];
let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, z);
ch = init << 12 | y_z;
if x >= 0xF0 {
// [x y z w] case
// use only the lower 3 bits of `init`
if len < 4 {
return Err(ChorusError::Utf8Error.into());
}
let w = input[3];
ch = (init & 7) << 18 | utf8_acc_cont_byte(y_z, w);
Ok(Some((ch, 4)))
} else {
Ok(Some((ch, 3)))
}
} else {
Ok(Some((ch, 2)))
}
}
pub fn encode_utf8(code: u32, dst: &mut [u8]) -> Result<usize, Error> {
// UTF-8 ranges and tags for encoding characters
const TAG_CONT: u8 = 0b1000_0000;
const TAG_TWO_B: u8 = 0b1100_0000;
const TAG_THREE_B: u8 = 0b1110_0000;
const TAG_FOUR_B: u8 = 0b1111_0000;
const MAX_ONE_B: u32 = 0x80;
const MAX_TWO_B: u32 = 0x800;
const MAX_THREE_B: u32 = 0x10000;
let len = unsafe {
if code < MAX_ONE_B && !dst.is_empty() {
*dst.get_unchecked_mut(0) = code as u8;
1
} else if code < MAX_TWO_B && dst.len() >= 2 {
*dst.get_unchecked_mut(0) = (code >> 6 & 0x1F) as u8 | TAG_TWO_B;
*dst.get_unchecked_mut(1) = (code & 0x3F) as u8 | TAG_CONT;
2
} else if code < MAX_THREE_B && dst.len() >= 3 {
*dst.get_unchecked_mut(0) = (code >> 12 & 0x0F) as u8 | TAG_THREE_B;
*dst.get_unchecked_mut(1) = (code >> 6 & 0x3F) as u8 | TAG_CONT;
*dst.get_unchecked_mut(2) = (code & 0x3F) as u8 | TAG_CONT;
3
} else if dst.len() >= 4 {
*dst.get_unchecked_mut(0) = (code >> 18 & 0x07) as u8 | TAG_FOUR_B;
*dst.get_unchecked_mut(1) = (code >> 12 & 0x3F) as u8 | TAG_CONT;
*dst.get_unchecked_mut(2) = (code >> 6 & 0x3F) as u8 | TAG_CONT;
*dst.get_unchecked_mut(3) = (code & 0x3F) as u8 | TAG_CONT;
4
} else {
return Err(ChorusError::BufferTooSmall.into());
}
};
Ok(len)
}
/// Returns the initial codepoint accumulator for the first byte.
/// The first byte is special, only want bottom 5 bits for width 2, 4 bits
/// for width 3, and 3 bits for width 4.
#[inline]
const fn utf8_first_byte(byte: u8, width: u32) -> u32 {
(byte & (0x7F >> width)) as u32
}
/// Returns the value of `ch` updated with continuation byte `byte`.
#[inline]
const fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 {
(ch << 6) | (byte & CONT_MASK) as u32
}
/// Mask of the value bits of a continuation byte.
const CONT_MASK: u8 = 0b0011_1111;
#[cfg(test)]
mod test {
use super::{encode_utf8, next_code_point};
#[test]
fn test_next_code_point() {
let (codepoint, size) = next_code_point(r#"𝄞"#.as_bytes()).unwrap().unwrap();
assert_eq!(codepoint, 119070);
assert_eq!(size, 4);
let (codepoint, size) = next_code_point(r#"†"#.as_bytes()).unwrap().unwrap();
assert_eq!(codepoint, 0x2020);
assert_eq!(size, 3);
// four codepoints
let s = [
0x61, 0xE0, 0xA4, 0xA8, 0xE0, 0xA4, 0xBF, 0xE4, 0xBA, 0x9C, 0xF0, 0x90, 0x82, 0x83,
];
let (codepoint, size) = next_code_point(s.as_slice()).unwrap().unwrap();
assert_eq!(codepoint, 0x61);
assert_eq!(size, 1);
let mut start = size;
let (codepoint, size) = next_code_point(&s[start..]).unwrap().unwrap();
assert_eq!(codepoint, 0x928);
assert_eq!(size, 3);
start += size;
let (codepoint, size) = next_code_point(&s[start..]).unwrap().unwrap();
assert_eq!(codepoint, 0x93F);
assert_eq!(size, 3);
start += size;
let (codepoint, size) = next_code_point(&s[start..]).unwrap().unwrap();
assert_eq!(codepoint, 0x4E9C);
assert_eq!(size, 3);
start += size;
let (codepoint, size) = next_code_point(&s[start..]).unwrap().unwrap();
assert_eq!(codepoint, 0x10083);
assert_eq!(size, 4);
assert_eq!(next_code_point(&s[0..0]).unwrap(), None);
}
#[test]
fn test_encode_utf8() {
let mut buffer: Vec<u8> = vec![0, 0, 0, 0];
assert_eq!(encode_utf8(0x69, &mut buffer).unwrap(), 1);
assert_eq!(buffer[0], 0x69);
assert_eq!(encode_utf8(0xEC, &mut buffer).unwrap(), 2);
assert_eq!(&buffer[0..2], &[0xC3, 0xAC]);
assert_eq!(encode_utf8(0x5450, &mut buffer).unwrap(), 3);
assert_eq!(&buffer[0..3], &[0xE5, 0x91, 0x90]);
assert_eq!(encode_utf8(0x2825F, &mut buffer).unwrap(), 4);
assert_eq!(&buffer[..], &[0xF0, 0xA8, 0x89, 0x9F]);
}
}

View File

@ -1,47 +0,0 @@
use crate::error::Error;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub struct Pubkey(pub [u8; 32]);
impl Pubkey {
pub fn write_hex(&self, output: &mut [u8]) -> Result<(), Error> {
write_hex!(self.0, output, 32)
}
pub fn read_hex(input: &[u8]) -> Result<Pubkey, Error> {
let mut out: [u8; 32] = [0; 32];
read_hex!(input, &mut out, 32)?;
Ok(Pubkey(out))
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
}
impl fmt::Display for Pubkey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes: [u8; 64] = [0; 64];
self.write_hex(&mut bytes).unwrap();
let hex = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{hex}")
}
}
#[cfg(test)]
mod test {
use super::Pubkey;
#[test]
fn test_pubkey_hex_functions() {
let hex = b"1110ee4ff957fa9c55832eaccb4dc1c45bfc6304e1e4e9fa478f53df4b20062d";
let pubkey = Pubkey::read_hex(hex).unwrap();
eprintln!("{:?}", pubkey);
let mut hex2: [u8; 64] = [0; 64];
pubkey.write_hex(&mut hex2).unwrap();
assert_eq!(hex, &hex2);
assert_eq!(format!("{}", pubkey).as_bytes(), hex);
}
}

View File

@ -1,46 +0,0 @@
use crate::error::Error;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Sig(pub [u8; 64]);
impl Sig {
pub fn write_hex(&self, output: &mut [u8]) -> Result<(), Error> {
write_hex!(self.0, output, 64)
}
pub fn read_hex(input: &[u8]) -> Result<Sig, Error> {
let mut out: [u8; 64] = [0; 64];
read_hex!(input, &mut out, 64)?;
Ok(Sig(out))
}
pub fn as_slice(&self) -> &[u8] {
self.0.as_slice()
}
}
impl fmt::Display for Sig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut bytes: [u8; 128] = [0; 128];
self.write_hex(&mut bytes).unwrap();
let hex = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{hex}")
}
}
#[cfg(test)]
mod test {
use super::Sig;
#[test]
fn test_sig_hex_functions() {
let hex = b"f4165cd621d387e0f723c3ca7484ca3da9ede00ffc97eb57c3e695384e095dea1a6215e7328b793e878f436f508006f95957c7e6b652e80d4c3c47b9f9110e7d";
let sig = Sig::read_hex(hex).unwrap();
eprintln!("{:?}", sig);
let mut hex2: [u8; 128] = [0; 128];
sig.write_hex(&mut hex2).unwrap();
assert_eq!(hex, &hex2);
assert_eq!(format!("{}", sig).as_bytes(), hex);
}
}

View File

@ -1,332 +0,0 @@
use crate::error::{ChorusError, Error};
use crate::types::parse::json_parse::read_tags_array;
use std::fmt;
/*
* 0 .. 2 u16 Length of the tags section
* 2 .. 4 u16 num_tags
* 4 .. 6 u16 offset of zeroeth tag
* 6 .. 8 u16 offset of first tag
* ...
*
* 4+num_tags*2 .. beginning of actual tag data
*
* Tag data looks like this for each tag:
* count, (len, data), (len, data), ...
*/
/// This stores an array of tags, each tag being an array of byte-strings.
/// It is stored in a single packed linear byte array.
#[derive(Debug, Clone)]
pub struct Tags<'a>(&'a [u8]);
impl<'a> Tags<'a> {
// this marks off the slice of bytes that represent the tags from a potentially longer input
pub fn delineate(input: &'a [u8]) -> Result<Tags<'a>, Error> {
if input.len() < 2 {
return Err(ChorusError::EndOfInput.into());
}
let len = parse_u16!(input, 0) as usize;
if input.len() < len {
return Err(ChorusError::EndOfInput.into());
}
Ok(Tags(&input[0..len]))
}
/// Parse JSON input into a Tags.
///
/// Returns the count of consumed input bytes and the Tags
pub fn from_json(json: &[u8], output_buffer: &'a mut [u8]) -> Result<(usize, Tags<'a>), Error> {
let mut inpos: usize = 0;
let tags_size = read_tags_array(json, &mut inpos, output_buffer)?;
Ok((inpos, Tags(&output_buffer[..tags_size])))
}
// This copies
pub fn copy(&self, output: &mut [u8]) -> Result<(), Error> {
if output.len() < self.0.len() {
return Err(ChorusError::BufferTooSmall.into());
}
output[..self.0.len()].copy_from_slice(self.0);
Ok(())
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0
}
#[inline]
pub fn len(&self) -> usize {
parse_u16!(self.0, 2) as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&'a self) -> TagsIter<'a> {
TagsIter {
tags: self,
next: 0,
}
}
pub fn get_string(&'a self, tag: usize, string: usize) -> Option<&'a [u8]> {
if tag >= self.len() {
return None;
}
let mut offset = parse_u16!(self.0, 4 + tag * 2) as usize;
let count = parse_u16!(self.0, offset) as usize;
offset += 2;
if string >= count {
return None;
}
let end = parse_u16!(self.0, 0) as usize;
// pass the fields we aren't reading
for _ in 0..string {
let len = parse_u16!(self.0, offset) as usize;
offset += 2 + len;
if offset > end {
// safety check
return None;
}
}
let len = parse_u16!(self.0, offset) as usize;
offset += 2;
if offset + len > end {
// safety check
return None;
}
Some(&self.0[offset..offset + len])
}
pub fn get_value(&'a self, key: &[u8]) -> Option<&'a [u8]> {
for tag in 0..self.len() {
if let Some(thing) = self.get_string(tag, 0) {
if thing == key {
return self.get_string(tag, 1);
}
}
}
None
}
pub fn matches(&self, letter: &[u8], value: &[u8]) -> bool {
for mut tag in self.iter() {
if tag.next() == Some(letter) && tag.next() == Some(value) {
return true;
}
}
false
}
pub fn as_json(&self) -> Vec<u8> {
let mut output: Vec<u8> = Vec::with_capacity(256);
let mut first = true;
output.push(b'[');
for tag in self.iter() {
if !first {
output.push(b',');
}
output.push(b'[');
let mut firststring = true;
for bytes in tag {
if !firststring {
output.push(b',');
}
output.push(b'"');
output.extend(bytes);
output.push(b'"');
firststring = false;
}
output.push(b']');
first = false;
}
output.push(b']');
output
}
}
impl fmt::Display for Tags<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.as_json();
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
write!(f, "{s}")
}
}
#[derive(Debug)]
pub struct TagsIter<'a> {
tags: &'a Tags<'a>,
next: usize,
}
impl<'a> Iterator for TagsIter<'a> {
type Item = TagsStringIter<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.tags.len() {
None
} else {
let offset_slot = 4 + self.next * 2;
let offset = parse_u16!(self.tags.0, offset_slot) as usize;
let count = parse_u16!(self.tags.0, offset) as usize;
self.next += 1;
Some(TagsStringIter {
tags: self.tags,
count,
cur_offset: offset + 2,
next: 0,
})
}
}
}
#[derive(Debug)]
pub struct TagsStringIter<'a> {
tags: &'a Tags<'a>,
count: usize,
cur_offset: usize,
next: usize,
}
impl<'a> Iterator for TagsStringIter<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.next >= self.count {
None
} else {
// Read len
let len = parse_u16!(self.tags.0, self.cur_offset) as usize;
let s = &self.tags.0[self.cur_offset + 2..self.cur_offset + 2 + len];
self.cur_offset += 2 + len;
self.next += 1;
Some(s)
}
}
}
#[cfg(test)]
mod test {
use super::Tags;
#[test]
fn test_tag() {
/*
* [
* ["Hello world!", "Hello", "world!"],
* ["p", ""ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49"],
* ]
*/
let data: Vec<u8> = vec![
110, 0, // tags_len
2, 0, // num_tags
8, 0, // first tag at offset 8
39, 0, // second tag at offset 39
// 8:
3, 0, // three fields long
12, 0, // first field 12 bytes long
72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, // "Hello world!"
5, 0, // second field 5 bytes long
72, 101, 108, 108, 111, // "Hello"
6, 0, // third field 6 bytes long
119, 111, 114, 108, 100, 33, // world!
// 39:
2, 0, // two fields long
1, 0, // first field 1 bytes long
112, // "p"
64, 0, // second field 64 bytes long
101, 101, 49, 49, 97, 53, 100, 102, 102, 52, 48, 99, 49, 57, 97, 53, 53, 53, 102, 52,
49, 102, 101, 52, 50, 98, 52, 56, 102, 48, 48, 101, 54, 49, 56, 99, 57, 49, 50, 50, 53,
54, 50, 50, 97, 101, 51, 55, 98, 54, 99, 50, 98, 98, 54, 55, 98, 55, 54, 99, 52, 101,
52, 57,
// "ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49"
];
let tags = Tags::delineate(&data).unwrap();
// Test tag access with get_string()
assert_eq!(tags.get_string(0, 0), Some(b"Hello world!".as_slice()));
assert_eq!(tags.get_string(0, 1), Some(b"Hello".as_slice()));
assert_eq!(tags.get_string(0, 2), Some(b"world!".as_slice()));
assert_eq!(tags.get_string(0, 3), None);
assert_eq!(tags.get_string(1, 0), Some(b"p".as_slice()));
assert_eq!(
tags.get_string(1, 1),
Some(b"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49".as_slice())
);
assert_eq!(tags.get_string(1, 2), None);
assert_eq!(tags.get_string(2, 0), None);
// Test tag access with iterators
let mut iter = tags.iter();
let mut tag1 = iter.next().unwrap();
println!("TagsStringIter 1 {:?}", tag1);
assert_eq!(tag1.next(), Some(b"Hello world!".as_slice()));
assert_eq!(tag1.next(), Some(b"Hello".as_slice()));
assert_eq!(tag1.next(), Some(b"world!".as_slice()));
assert!(tag1.next().is_none());
let mut tag2 = iter.next().unwrap();
assert_eq!(tag2.next(), Some(b"p".as_slice()));
assert_eq!(
tag2.next(),
Some(b"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49".as_slice())
);
assert!(tag2.next().is_none());
let tag3 = iter.next();
assert!(tag3.is_none());
assert_eq!(
format!("{tags}"),
r#"[["Hello world!","Hello","world!"],["p","ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49"]]"#
);
}
#[test]
fn test_tags_from_json() {
let mut output: Vec<u8> = Vec::with_capacity(4096);
output.resize(4096, 0);
let json = r#"[["Hello world!","Hello","world!"],["p","ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49"]]"#;
let (_, tags) = Tags::from_json(json.as_bytes(), &mut output).unwrap();
assert_eq!(tags.get_string(0, 0), Some(b"Hello world!".as_slice()));
assert_eq!(tags.get_string(0, 1), Some(b"Hello".as_slice()));
assert_eq!(tags.get_string(0, 2), Some(b"world!".as_slice()));
assert_eq!(tags.get_string(0, 3), None);
assert_eq!(tags.get_string(1, 0), Some(b"p".as_slice()));
assert_eq!(
tags.get_string(1, 1),
Some(b"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49".as_slice())
);
assert_eq!(tags.get_string(1, 2), None);
assert_eq!(tags.get_string(2, 0), None);
}
#[test]
fn test_empty_tag() {
let mut output: Vec<u8> = Vec::with_capacity(256);
output.resize(256, 0);
let json = r#"[[]]"#;
let (_, _tags) = Tags::from_json(json.as_bytes(), &mut output).unwrap();
let json = r#"[["-"],[]]"#;
let (_, tags) = Tags::from_json(json.as_bytes(), &mut output).unwrap();
assert_eq!(tags.get_string(0, 0), Some(b"-".as_slice()));
}
}

View File

@ -1,36 +0,0 @@
use speedy::{Readable, Writable};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Readable, Writable)]
pub struct Time(pub u64);
impl Default for Time {
fn default() -> Time {
Time::now()
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Time {
pub fn as_u64(&self) -> u64 {
self.0
}
pub fn min() -> Time {
Time(0)
}
pub fn max() -> Time {
Time(u64::MAX)
}
pub fn now() -> Time {
// Safety: unwrap() can only panic if the system time is before UNIX_EPOCH
Time(std::time::UNIX_EPOCH.elapsed().unwrap().as_secs())
}
}

173
src/bin/chorus.rs Normal file
View File

@ -0,0 +1,173 @@
use chorus::config::{Config, FriendlyConfig};
use chorus::error::Error;
use chorus::globals::GLOBALS;
use chorus::ip::HashedPeer;
use chorus::tls::MaybeTlsStream;
use std::env;
use std::fs::OpenOptions;
use std::io::Read;
use std::sync::atomic::Ordering;
use std::time::Duration;
use tokio::net::TcpListener;
use tokio::signal::unix::{signal, SignalKind};
#[tokio::main]
async fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
let config = chorus::load_config(&config_path)?;
chorus::setup_logging(&config);
// Log host name
log::info!(target: "Server", "HOSTNAME = {}", config.hostname);
let store = chorus::setup_store(&config)?;
let _ = GLOBALS.store.set(store);
// TLS setup
let maybe_tls_acceptor = if config.use_tls {
log::info!(target: "Server", "Using TLS");
Some(chorus::tls::tls_acceptor(&config)?)
} else {
log::info!(target: "Server", "Not using TLS");
None
};
// Bind listener to port
let listener = TcpListener::bind((&*config.ip_address, config.port)).await?;
log::info!(target: "Server", "Running on {}:{}", config.ip_address, config.port);
// Store config into GLOBALS
*GLOBALS.config.write() = config;
let mut interrupt_signal = signal(SignalKind::interrupt())?;
let mut quit_signal = signal(SignalKind::quit())?;
let mut terminate_signal = signal(SignalKind::terminate())?;
let mut hup_signal = signal(SignalKind::hangup())?;
loop {
tokio::select! {
// Exits gracefully upon exit-type signals
v = interrupt_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGINT");
break;
},
v = quit_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGQUIT");
break;
},
v = terminate_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGTERM");
break;
},
// Reload config on HUP
v = hup_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGHUP: Reloading configuration");
// Reload the config file
let mut file = OpenOptions::new().read(true).open(config_path.clone())?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let config: Config = friendly_config.into_config()?;
*GLOBALS.config.write() = config;
chorus::print_stats();
},
// Accepts network connections and spawn a task to serve each one
v = listener.accept() => {
let (tcp_stream, hashed_peer) = {
let (tcp_stream, peer_addr) = v?;
let hashed_peer = HashedPeer::new(peer_addr);
(tcp_stream, hashed_peer)
};
// Possibly IP block
if GLOBALS.config.read().enable_ip_blocking {
let ip_data = chorus::get_ip_data(GLOBALS.store.get().unwrap(), hashed_peer.ip())?;
if ip_data.is_banned() {
log::debug!(target: "Client",
"{}: Blocking reconnection until {}",
hashed_peer.ip(),
ip_data.ban_until);
continue;
}
}
if let Some(tls_acceptor) = &maybe_tls_acceptor {
let tls_acceptor_clone = tls_acceptor.clone();
tokio::spawn(async move {
match tls_acceptor_clone.accept(tcp_stream).await {
Err(e) => log::error!(
target: "Client",
"{}: {}", hashed_peer, e
),
Ok(tls_stream) => {
if let Err(e) = chorus::serve(MaybeTlsStream::Rustls(tls_stream), hashed_peer).await {
log::error!(
target: "Client",
"{}: {}", hashed_peer, e
);
}
}
}
});
} else {
chorus::serve(MaybeTlsStream::Plain(tcp_stream), hashed_peer).await?;
}
}
};
}
// Pre-sync in case something below hangs up
let _ = GLOBALS.store.get().unwrap().sync();
// Set the shutting down signal
let _ = GLOBALS.shutting_down.send(true);
// Wait for active websockets to shutdown gracefully
let mut num_clients = GLOBALS.num_clients.load(Ordering::Relaxed);
if num_clients != 0 {
log::info!(target: "Server", "Waiting for {num_clients} websockets to shutdown...");
// We will check if all clients have shutdown every 25ms
let interval = tokio::time::interval(Duration::from_millis(25));
tokio::pin!(interval);
while num_clients != 0 {
// If we get another shutdown signal, stop waiting for websockets
tokio::select! {
v = interrupt_signal.recv() => if v.is_some() {
break;
},
v = quit_signal.recv() => if v.is_some() {
break;
},
v = terminate_signal.recv() => if v.is_some() {
break;
},
_instant = interval.tick() => {
num_clients = GLOBALS.num_clients.load(Ordering::Relaxed);
continue;
}
}
}
}
log::info!(target: "Server", "Syncing and shutting down.");
let _ = GLOBALS.store.get().unwrap().sync();
chorus::print_stats();
Ok(())
}

View File

@ -0,0 +1,22 @@
use chorus::error::Error;
use std::env;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_compress <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
let config = chorus::load_config(config_path)?;
chorus::setup_logging(&config);
let store = chorus::setup_store(&config)?;
let _store = unsafe { store.rebuild()? };
Ok(())
}

43
src/bin/chorus_dump.rs Normal file
View File

@ -0,0 +1,43 @@
use chorus::error::Error;
use pocket_types::{Event, Filter};
use std::env;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_dump <chorus_config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
let mut config = chorus::load_config(config_path)?;
// Force allow of scraping (this program is a scraper)
config.allow_scraping = true;
chorus::setup_logging(&config);
// Setup store
let store = chorus::setup_store(&config)?;
let mut buffer: [u8; 128] = [0; 128];
let (_incount, _outcount, filter) = Filter::from_json(b"{}", &mut buffer)?;
let screen = |_: &Event| -> bool { true };
let mut events = store.find_events(
filter,
config.allow_scraping,
config.allow_scrape_if_limited_to,
config.allow_scrape_if_max_seconds,
screen,
)?;
for event in events.drain(..) {
let bytes = event.as_json()?;
let s = unsafe { std::str::from_utf8_unchecked(&bytes) };
println!("{s}");
}
Ok(())
}

View File

@ -0,0 +1,32 @@
use chorus::error::Error;
use std::env;
fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus_moderate <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
let mut config = chorus::load_config(config_path)?;
// Force allow of scraping (this program is a scraper)
config.allow_scraping = true;
chorus::setup_logging(&config);
// Setup store
let store = chorus::setup_store(&config)?;
for (id, approved) in chorus::dump_event_approvals(&store)? {
println!("ID {} = {}", id, approved);
}
for (pubkey, approved) in chorus::dump_pubkey_approvals(&store)? {
println!("PUBKEY {} = {}", pubkey, approved);
}
Ok(())
}

View File

@ -1,10 +1,7 @@
use chorus_lib::config::{Config, FriendlyConfig};
use chorus_lib::error::Error;
use chorus_lib::store::Store;
use chorus_lib::types::{Event, Filter, Kind};
use chorus::error::Error;
use pocket_types::{Event, Filter, Kind};
use std::env;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::io::Write;
fn main() -> Result<(), Error> {
// Get args (config path)
@ -15,35 +12,25 @@ fn main() -> Result<(), Error> {
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let mut config: Config = friendly_config.into_config()?;
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
let mut config = chorus::load_config(config_path)?;
// Force allow of scraping (this program is a scraper)
config.allow_scraping = true;
// Setup store
let store = Store::new(&config)?;
chorus::setup_logging(&config);
let store = chorus::setup_store(&config)?;
let mut buffer: [u8; 128] = [0; 128];
let (_incount, _outcount, filter) = Filter::from_json(b"{}", &mut buffer)?;
let screen = |_: &Event<'_>| -> bool { true };
let screen = |_: &Event| -> bool { true };
let mut events = store.find_events(filter, screen, &config)?;
let mut events = store.find_events(
filter,
config.allow_scraping,
config.allow_scrape_if_limited_to,
config.allow_scrape_if_max_seconds,
screen,
)?;
let mut stdout = std::io::stdout();
let stdin = std::io::stdin();
@ -51,12 +38,12 @@ fn main() -> Result<(), Error> {
'eventloop: for event in events.drain(..) {
// Skip DMs which don't need approval
if event.kind() == Kind(4) || event.kind() == Kind(1059) {
if event.kind() == Kind::from(4) || event.kind() == Kind::from(1059) {
continue;
}
// Skip relay lists which don't need approval
if event.kind() == Kind(10002) {
if event.kind() == Kind::from(10002) {
continue;
}
@ -75,18 +62,27 @@ fn main() -> Result<(), Error> {
//println!("{s}");
// Skip if event marked approved
if matches!(store.get_event_approval(event.id()), Ok(Some(true))) {
if matches!(
chorus::get_event_approval(&store, event.id()),
Ok(Some(true))
) {
continue;
}
// Skip if pubkey marked approved
if matches!(store.get_pubkey_approval(event.pubkey()), Ok(Some(true))) {
if matches!(
chorus::get_pubkey_approval(&store, event.pubkey()),
Ok(Some(true))
) {
continue;
}
// Delete if pubkey marked banned
if matches!(store.get_pubkey_approval(event.pubkey()), Ok(Some(false))) {
store.delete_event(event.id())?;
if matches!(
chorus::get_pubkey_approval(&store, event.pubkey()),
Ok(Some(false))
) {
store.remove_event(event.id())?;
continue;
}
@ -109,24 +105,24 @@ fn main() -> Result<(), Error> {
}
match input.bytes().next().unwrap() {
b'p' => {
store.mark_pubkey_approval(event.pubkey(), true)?;
chorus::mark_pubkey_approval(&store, event.pubkey(), true)?;
println!("User approved.");
break;
}
b'P' => {
store.mark_pubkey_approval(event.pubkey(), false)?;
store.delete_event(event.id())?;
chorus::mark_pubkey_approval(&store, event.pubkey(), false)?;
store.remove_event(event.id())?;
println!("User banned.");
break;
}
b'i' => {
store.mark_event_approval(event.id(), true)?;
chorus::mark_event_approval(&store, event.id(), true)?;
println!("Event approved.");
break;
}
b'I' => {
store.mark_event_approval(event.id(), false)?;
store.delete_event(event.id())?;
chorus::mark_event_approval(&store, event.id(), false)?;
store.remove_event(event.id())?;
println!("Event banned.");
break;
}

View File

@ -1,5 +1,5 @@
use crate::error::Error;
use crate::types::Pubkey;
use pocket_types::Pubkey;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use url::Host;

View File

@ -47,7 +47,7 @@ pub enum ChorusError {
ChannelRecv(tokio::sync::broadcast::error::RecvError),
// Channel Send
ChannelSend(tokio::sync::broadcast::error::SendError<usize>),
ChannelSend(tokio::sync::broadcast::error::SendError<u64>),
// Config
Config(toml::de::Error),
@ -100,8 +100,8 @@ pub enum ChorusError {
// JSON Escape Surrogate
JsonEscapeSurrogate,
// LMDB
Lmdb(heed::Error),
// Missing Table
MissingTable(&'static str),
// No private key
NoPrivateKey,
@ -112,6 +112,15 @@ pub enum ChorusError {
// Protected Event
ProtectedEvent,
// Pocket Db Error
PocketDb(pocket_db::Error),
// Pocket Db Heed Error
PocketDbHeed(pocket_db::heed::Error),
// Pocket Types Error
PocketType(pocket_types::Error),
// Range error
RangeError,
@ -190,9 +199,12 @@ impl std::fmt::Display for ChorusError {
f,
"JSON string escape surrogate (ancient style) is not supported"
),
ChorusError::Lmdb(e) => write!(f, "{e}"),
ChorusError::MissingTable(t) => write!(f, "Missing table: {t}"),
ChorusError::NoPrivateKey => write!(f, "Private Key Not Found"),
ChorusError::NoSuchSubscription => write!(f, "No such subscription"),
ChorusError::PocketDb(e) => write!(f, "{e}"),
ChorusError::PocketDbHeed(e) => write!(f, "{e}"),
ChorusError::PocketType(e) => write!(f, "{e}"),
ChorusError::ProtectedEvent => write!(f, "Protected event"),
ChorusError::RangeError => write!(f, "Range error"),
ChorusError::Restricted => write!(f, "Restricted"),
@ -220,7 +232,9 @@ impl StdError for ChorusError {
ChorusError::Http(e) => Some(e),
ChorusError::Hyper(e) => Some(e),
ChorusError::Io(e) => Some(e),
ChorusError::Lmdb(e) => Some(e),
ChorusError::PocketDb(e) => Some(e),
ChorusError::PocketDbHeed(e) => Some(e),
ChorusError::PocketType(e) => Some(e),
ChorusError::Rustls(e) => Some(e),
ChorusError::Speedy(e) => Some(e),
ChorusError::Tungstenite(e) => Some(e),
@ -261,9 +275,12 @@ impl ChorusError {
ChorusError::JsonBadStringChar(_) => 0.2,
ChorusError::JsonEscape => 0.2,
ChorusError::JsonEscapeSurrogate => 0.05,
ChorusError::Lmdb(_) => 0.0,
ChorusError::MissingTable(_) => 0.0,
ChorusError::NoPrivateKey => 0.0,
ChorusError::NoSuchSubscription => 0.05,
ChorusError::PocketDb(_) => 0.0,
ChorusError::PocketDbHeed(_) => 0.0,
ChorusError::PocketType(_) => 0.0,
ChorusError::ProtectedEvent => 0.35,
ChorusError::RangeError => 0.0,
ChorusError::Restricted => 0.1,
@ -308,9 +325,9 @@ impl From<tokio::sync::broadcast::error::RecvError> for Error {
}
}
impl From<tokio::sync::broadcast::error::SendError<usize>> for Error {
impl From<tokio::sync::broadcast::error::SendError<u64>> for Error {
#[track_caller]
fn from(err: tokio::sync::broadcast::error::SendError<usize>) -> Self {
fn from(err: tokio::sync::broadcast::error::SendError<u64>) -> Self {
Error {
inner: ChorusError::ChannelSend(err),
location: std::panic::Location::caller(),
@ -368,11 +385,31 @@ impl From<std::io::Error> for Error {
}
}
impl From<heed::Error> for Error {
impl From<pocket_db::Error> for Error {
#[track_caller]
fn from(err: heed::Error) -> Self {
fn from(err: pocket_db::Error) -> Self {
Error {
inner: ChorusError::Lmdb(err),
inner: ChorusError::PocketDb(err),
location: std::panic::Location::caller(),
}
}
}
impl From<pocket_db::heed::Error> for Error {
#[track_caller]
fn from(err: pocket_db::heed::Error) -> Self {
Error {
inner: ChorusError::PocketDbHeed(err),
location: std::panic::Location::caller(),
}
}
}
impl From<pocket_types::Error> for Error {
#[track_caller]
fn from(err: pocket_types::Error) -> Self {
Error {
inner: ChorusError::PocketType(err),
location: std::panic::Location::caller(),
}
}

View File

@ -1,8 +1,8 @@
use chorus_lib::config::Config;
use chorus_lib::store::Store;
use crate::config::Config;
use hyper::server::conn::Http;
use lazy_static::lazy_static;
use parking_lot::RwLock;
use pocket_db::Store;
use std::sync::atomic::{AtomicU64, AtomicUsize};
use std::sync::OnceLock;
use std::time::Instant;
@ -22,7 +22,7 @@ pub struct Globals {
/// Every handler needs to listen to it and check if the incoming event matches any
/// subscribed fitlers for their client, and if so, send the event to their client under
/// that subscription.
pub new_events: BroadcastSender<usize>,
pub new_events: BroadcastSender<u64>,
pub num_clients: AtomicUsize,
pub shutting_down: WatchSender<bool>,

View File

@ -1,4 +1,4 @@
use crate::types::Time;
use pocket_types::Time;
use speedy::{Readable, Writable};
use std::net::{IpAddr, SocketAddr};
@ -118,7 +118,7 @@ impl IpReputation {
// Memory-only short-term record of IP handling
#[derive(Debug, Clone, Default, Readable, Writable)]
pub struct IpData {
pub ban_until: Time,
pub ban_until: u64,
pub reputation: IpReputation,
}
@ -134,15 +134,15 @@ impl IpData {
// Compute ban_until
let mut until = Time::now();
let seconds = self.ban_seconds(session_exit, minimum_ban_seconds);
until.0 += seconds;
until = until + seconds;
self.ban_until = Time(self.ban_until.0.max(until.0));
self.ban_until = self.ban_until.max(until.as_u64());
seconds
}
pub fn is_banned(&self) -> bool {
self.ban_until > Time::now()
Time::from_u64(self.ban_until) > Time::now()
}
fn ban_seconds(&self, session_exit: SessionExit, minimum_ban_seconds: u64) -> u64 {

View File

@ -1,240 +1,46 @@
pub mod config;
pub mod error;
pub mod globals;
pub mod ip;
pub mod nostr;
pub mod reply;
pub mod tls;
pub mod web;
use crate::config::{Config, FriendlyConfig};
use crate::error::{ChorusError, Error};
use crate::globals::GLOBALS;
use crate::ip::{HashedIp, HashedPeer, IpData, SessionExit};
use crate::reply::NostrReply;
use crate::tls::MaybeTlsStream;
use chorus_lib::config::{Config, FriendlyConfig};
use chorus_lib::error::{ChorusError, Error};
use chorus_lib::ip::{HashedIp, HashedPeer, SessionExit};
use chorus_lib::reply::NostrReply;
use chorus_lib::store::Store;
use chorus_lib::types::{OwnedFilter, Pubkey};
use futures::{sink::SinkExt, stream::StreamExt};
use hyper::service::Service;
use hyper::upgrade::Upgraded;
use hyper::{Body, Request, Response};
use hyper_tungstenite::{tungstenite, WebSocketStream};
use hyper_tungstenite::tungstenite;
use hyper_tungstenite::WebSocketStream;
use pocket_db::Store;
use pocket_types::{Id, OwnedFilter, Pubkey};
use speedy::{Readable, Writable};
use std::collections::HashMap;
use std::env;
use std::error::Error as StdError;
use std::fs::OpenOptions;
use std::future::Future;
use std::io::Read;
use std::net::IpAddr;
use std::path::Path;
use std::pin::Pin;
use std::sync::atomic::Ordering;
use std::task::{Context, Poll};
use std::time::Duration;
use textnonce::TextNonce;
use tokio::net::{TcpListener, TcpStream};
use tokio::signal::unix::{signal, SignalKind};
use tokio::net::TcpStream;
use tokio::time::Instant;
use tungstenite::protocol::WebSocketConfig;
use tungstenite::Message;
#[tokio::main]
async fn main() -> Result<(), Error> {
// Get args (config path)
let mut args = env::args();
if args.len() <= 1 {
panic!("USAGE: chorus <config_path>");
}
let _ = args.next(); // ignore program name
let config_path = args.next().unwrap();
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path.clone())?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let config: Config = friendly_config.into_config()?;
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
// Log host name
log::info!(target: "Server", "HOSTNAME = {}", config.hostname);
// Setup store
let store = Store::new(&config)?;
let _ = GLOBALS.store.set(store);
// TLS setup
let maybe_tls_acceptor = if config.use_tls {
log::info!(target: "Server", "Using TLS");
Some(tls::tls_acceptor(&config)?)
} else {
log::info!(target: "Server", "Not using TLS");
None
};
// Bind listener to port
let listener = TcpListener::bind((&*config.ip_address, config.port)).await?;
log::info!(target: "Server", "Running on {}:{}", config.ip_address, config.port);
// Store config into GLOBALS
*GLOBALS.config.write() = config;
let mut interrupt_signal = signal(SignalKind::interrupt())?;
let mut quit_signal = signal(SignalKind::quit())?;
let mut terminate_signal = signal(SignalKind::terminate())?;
let mut hup_signal = signal(SignalKind::hangup())?;
loop {
tokio::select! {
// Exits gracefully upon exit-type signals
v = interrupt_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGINT");
break;
},
v = quit_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGQUIT");
break;
},
v = terminate_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGTERM");
break;
},
// Reload config on HUP
v = hup_signal.recv() => if v.is_some() {
log::info!(target: "Server", "SIGHUP: Reloading configuration");
// Reload the config file
let mut file = OpenOptions::new().read(true).open(config_path.clone())?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let config: Config = friendly_config.into_config()?;
*GLOBALS.config.write() = config;
print_stats();
},
// Accepts network connections and spawn a task to serve each one
v = listener.accept() => {
let (tcp_stream, hashed_peer) = {
let (tcp_stream, peer_addr) = v?;
let hashed_peer = HashedPeer::new(peer_addr);
(tcp_stream, hashed_peer)
};
// Possibly IP block
if GLOBALS.config.read().enable_ip_blocking {
let ip_data = GLOBALS.store.get().unwrap().get_ip_data(hashed_peer.ip())?;
if ip_data.is_banned() {
log::debug!(target: "Client",
"{}: Blocking reconnection until {}",
hashed_peer.ip(),
ip_data.ban_until);
continue;
}
}
if let Some(tls_acceptor) = &maybe_tls_acceptor {
let tls_acceptor_clone = tls_acceptor.clone();
tokio::spawn(async move {
match tls_acceptor_clone.accept(tcp_stream).await {
Err(e) => log::error!(
target: "Client",
"{}: {}", hashed_peer, e
),
Ok(tls_stream) => {
if let Err(e) = serve(MaybeTlsStream::Rustls(tls_stream), hashed_peer).await {
log::error!(
target: "Client",
"{}: {}", hashed_peer, e
);
}
}
}
});
} else {
serve(MaybeTlsStream::Plain(tcp_stream), hashed_peer).await?;
}
}
};
}
// Pre-sync in case something below hangs up
let _ = GLOBALS.store.get().unwrap().sync();
// Set the shutting down signal
let _ = GLOBALS.shutting_down.send(true);
// Wait for active websockets to shutdown gracefully
let mut num_clients = GLOBALS.num_clients.load(Ordering::Relaxed);
if num_clients != 0 {
log::info!(target: "Server", "Waiting for {num_clients} websockets to shutdown...");
// We will check if all clients have shutdown every 25ms
let interval = tokio::time::interval(Duration::from_millis(25));
tokio::pin!(interval);
while num_clients != 0 {
// If we get another shutdown signal, stop waiting for websockets
tokio::select! {
v = interrupt_signal.recv() => if v.is_some() {
break;
},
v = quit_signal.recv() => if v.is_some() {
break;
},
v = terminate_signal.recv() => if v.is_some() {
break;
},
_instant = interval.tick() => {
num_clients = GLOBALS.num_clients.load(Ordering::Relaxed);
continue;
}
}
}
}
log::info!(target: "Server", "Syncing and shutting down.");
let _ = GLOBALS.store.get().unwrap().sync();
print_stats();
Ok(())
}
fn print_stats() {
let mut runtime: u64 = GLOBALS.start_time.elapsed().as_secs();
if runtime < 1 {
runtime = 1;
}
log::info!(
target: "Server",
"Runtime: {} seconds", runtime
);
log::info!(
target: "Server",
"Inbound: {} bytes ({} B/s)",
GLOBALS.bytes_inbound.load(Ordering::Relaxed),
(GLOBALS.bytes_inbound.load(Ordering::Relaxed) as f32) / (runtime as f32)
);
log::info!(
target: "Server",
"Outbound: {} bytes ({} B/s)",
GLOBALS.bytes_outbound.load(Ordering::Relaxed),
(GLOBALS.bytes_outbound.load(Ordering::Relaxed) as f32) / (runtime as f32)
);
}
// Serve a single network connection
async fn serve(stream: MaybeTlsStream<TcpStream>, peer: HashedPeer) -> Result<(), Error> {
/// Serve a single network connection
pub async fn serve(stream: MaybeTlsStream<TcpStream>, peer: HashedPeer) -> Result<(), Error> {
// Serve the network stream with our http server and our HttpService
let service = HttpService { peer };
@ -397,14 +203,10 @@ async fn handle_http_request(
// if GLOBALS.config.read().enable_ip_blocking {
let mut ban_seconds = 0;
let minimum_ban_seconds = GLOBALS.config.read().minimum_ban_seconds;
if let Ok(mut ip_data) = GLOBALS.store.get().unwrap().get_ip_data(peer.ip()) {
if let Ok(mut ip_data) = get_ip_data(GLOBALS.store.get().unwrap(), peer.ip()) {
ban_seconds =
ip_data.update_on_session_close(session_exit, minimum_ban_seconds);
let _ = GLOBALS
.store
.get()
.unwrap()
.update_ip_data(peer.ip(), &ip_data);
let _ = update_ip_data(GLOBALS.store.get().unwrap(), peer.ip(), &ip_data);
}
// we cheat somewhat and log these websocket open and close messages
@ -512,7 +314,7 @@ impl WebSocketService {
}
// If the event matches a subscription they have open, send them the event
async fn handle_new_event(&mut self, new_event_offset: usize) -> Result<(), Error> {
async fn handle_new_event(&mut self, new_event_offset: u64) -> Result<(), Error> {
if self.subscriptions.is_empty() {
return Ok(());
}
@ -523,13 +325,13 @@ impl WebSocketService {
.unwrap()
.get_event_by_offset(new_event_offset)?;
let event_flags = nostr::event_flags(&event, &self.user);
let event_flags = nostr::event_flags(event, &self.user);
let authorized_user = nostr::authorized_user(&self.user);
'subs: for (subid, filters) in self.subscriptions.iter() {
for filter in filters.iter() {
if filter.as_filter()?.event_matches(&event)?
&& nostr::screen_outgoing_event(&event, &event_flags, authorized_user)
if filter.event_matches(event)?
&& nostr::screen_outgoing_event(event, &event_flags, authorized_user)
{
let message = NostrReply::Event(subid, event);
self.websocket
@ -609,3 +411,220 @@ impl WebSocketService {
Ok(())
}
}
/// Print statistics
pub fn print_stats() {
let mut runtime: u64 = GLOBALS.start_time.elapsed().as_secs();
if runtime < 1 {
runtime = 1;
}
log::info!(
target: "Server",
"Runtime: {} seconds", runtime
);
log::info!(
target: "Server",
"Inbound: {} bytes ({} B/s)",
GLOBALS.bytes_inbound.load(Ordering::Relaxed),
(GLOBALS.bytes_inbound.load(Ordering::Relaxed) as f32) / (runtime as f32)
);
log::info!(
target: "Server",
"Outbound: {} bytes ({} B/s)",
GLOBALS.bytes_outbound.load(Ordering::Relaxed),
(GLOBALS.bytes_outbound.load(Ordering::Relaxed) as f32) / (runtime as f32)
);
}
/// Load config file
pub fn load_config<P: AsRef<Path>>(config_path: P) -> Result<Config, Error> {
// Read config file
let mut file = OpenOptions::new().read(true).open(config_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let friendly_config: FriendlyConfig = toml::from_str(&contents)?;
let config: Config = friendly_config.into_config()?;
Ok(config)
}
/// Setup logging
pub fn setup_logging(config: &Config) {
env_logger::Builder::new()
.filter_level(config.library_log_level)
.filter(Some("Server"), config.server_log_level)
.filter(Some("Client"), config.client_log_level)
.format_target(true)
.format_module_path(false)
.format_timestamp_millis()
.init();
log::debug!(target: "Server", "Loaded config file.");
}
/// Setup storage
pub fn setup_store(config: &Config) -> Result<Store, Error> {
let store = Store::new(
&config.data_directory,
vec![
"approved-events", // id.as_slice() -> u8(bool)
"approved-pubkeys", // pubkey.as_slice() -> u8(bool)
"ip_data", // HashedIp.0 -> IpData
],
)?;
Ok(store)
}
/// Get IpData from storage about this remote HashedIp
pub fn get_ip_data(store: &Store, ip: HashedIp) -> Result<IpData, Error> {
let ip_data = store
.extra_table("ip_data")
.ok_or(Into::<Error>::into(ChorusError::MissingTable("ip_data")))?;
let txn = store.read_txn()?;
let key = &ip.0;
let bytes = match ip_data.get(&txn, key)? {
Some(b) => b,
None => return Ok(Default::default()),
};
Ok(IpData::read_from_buffer(bytes)?)
}
/// Get IpData in storage about this remote HashedIp
pub fn update_ip_data(store: &Store, ip: HashedIp, data: &IpData) -> Result<(), Error> {
let ip_data = store
.extra_table("ip_data")
.ok_or(Into::<Error>::into(ChorusError::MissingTable("ip_data")))?;
let mut txn = store.write_txn()?;
let key = &ip.0;
let bytes = data.write_to_vec()?;
ip_data.put(&mut txn, key, &bytes)?;
txn.commit()?;
Ok(())
}
/// Dump all IpData from storage
pub fn dump_ip_data(store: &Store) -> Result<Vec<(HashedIp, IpData)>, Error> {
let ip_data = store
.extra_table("ip_data")
.ok_or(Into::<Error>::into(ChorusError::MissingTable("ip_data")))?;
let txn = store.read_txn()?;
let mut output: Vec<(HashedIp, IpData)> = Vec::new();
for i in ip_data.iter(&txn)? {
let (key, val) = i?;
let hashedip = HashedIp::from_bytes(key);
let data = IpData::read_from_buffer(val)?;
output.push((hashedip, data));
}
Ok(output)
}
/// Mark an event as approved or not
pub fn mark_event_approval(store: &Store, id: Id, approval: bool) -> Result<(), Error> {
let approved_events = store
.extra_table("approved-events")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-events",
)))?;
let mut txn = store.write_txn()?;
approved_events.put(&mut txn, id.as_slice(), &[approval as u8])?;
txn.commit()?;
Ok(())
}
/// Clear an event approval status
pub fn clear_event_approval(store: &Store, id: Id) -> Result<(), Error> {
let approved_events = store
.extra_table("approved-events")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-events",
)))?;
let mut txn = store.write_txn()?;
approved_events.delete(&mut txn, id.as_slice())?;
txn.commit()?;
Ok(())
}
/// Fetch an event approval status
pub fn get_event_approval(store: &Store, id: Id) -> Result<Option<bool>, Error> {
let approved_events = store
.extra_table("approved-events")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-events",
)))?;
let txn = store.read_txn()?;
Ok(approved_events.get(&txn, id.as_slice())?.map(|u| u[0] != 0)) // FIXME in case data is zero length this will panic
}
/// Dump all event approval statuses
pub fn dump_event_approvals(store: &Store) -> Result<Vec<(Id, bool)>, Error> {
let mut output: Vec<(Id, bool)> = Vec::new();
let approved_events = store
.extra_table("approved-events")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-events",
)))?;
let txn = store.read_txn()?;
for i in approved_events.iter(&txn)? {
let (key, val) = i?;
let id = Id::from_bytes(key.try_into().unwrap());
let approval: bool = val[0] != 0; // FIXME in case data is zero length this will panic
output.push((id, approval));
}
Ok(output)
}
/// Mark a pubkey as approved or not
pub fn mark_pubkey_approval(store: &Store, pubkey: Pubkey, approval: bool) -> Result<(), Error> {
let approved_pubkeys = store
.extra_table("approved-pubkeys")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-pubkeys",
)))?;
let mut txn = store.write_txn()?;
approved_pubkeys.put(&mut txn, pubkey.as_slice(), &[approval as u8])?;
txn.commit()?;
Ok(())
}
/// Clear a pubkey approval status
pub fn clear_pubkey_approval(store: &Store, pubkey: Pubkey) -> Result<(), Error> {
let approved_pubkeys = store
.extra_table("approved-pubkeys")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-pubkeys",
)))?;
let mut txn = store.write_txn()?;
approved_pubkeys.delete(&mut txn, pubkey.as_slice())?;
txn.commit()?;
Ok(())
}
/// Fetch a pubkey approval status
pub fn get_pubkey_approval(store: &Store, pubkey: Pubkey) -> Result<Option<bool>, Error> {
let approved_pubkeys = store
.extra_table("approved-pubkeys")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-pubkeys",
)))?;
let txn = store.read_txn()?;
Ok(approved_pubkeys
.get(&txn, pubkey.as_slice())?
.map(|u| u[0] != 0)) // FIXME in case data is zero length this will panic
}
/// Dump all pubkey approval statuses
pub fn dump_pubkey_approvals(store: &Store) -> Result<Vec<(Pubkey, bool)>, Error> {
let mut output: Vec<(Pubkey, bool)> = Vec::new();
let approved_pubkeys = store
.extra_table("approved-pubkeys")
.ok_or(Into::<Error>::into(ChorusError::MissingTable(
"approved-pubkeys",
)))?;
let txn = store.read_txn()?;
for i in approved_pubkeys.iter(&txn)? {
let (key, val) = i?;
let pubkey = Pubkey::from_bytes(key.try_into().unwrap());
let approval: bool = val[0] != 0; // FIXME in case data is zero length this will panic
output.push((pubkey, approval));
}
Ok(output)
}

View File

@ -1,12 +1,11 @@
use crate::error::{ChorusError, Error};
use crate::globals::GLOBALS;
use crate::reply::{NostrReply, NostrReplyPrefix};
use crate::WebSocketService;
use chorus_lib::error::{ChorusError, Error};
use chorus_lib::reply::{NostrReply, NostrReplyPrefix};
use chorus_lib::types::parse::json_escape::json_unescape;
use chorus_lib::types::parse::json_parse::*;
use chorus_lib::types::{Event, Filter, Kind, OwnedFilter, Pubkey, Time};
use futures::SinkExt;
use hyper_tungstenite::tungstenite::Message;
use pocket_types::json::{eat_whitespace, json_unescape, verify_char};
use pocket_types::{Event, Filter, Kind, OwnedFilter, Pubkey, Time};
use url::Url;
impl WebSocketService {
@ -75,8 +74,7 @@ impl WebSocketService {
inpos += incount;
outpos += outcount;
let filterbytes = filter.as_bytes().to_owned();
filters.push(OwnedFilter(filterbytes));
filters.push(filter.to_owned());
}
if let Err(e) = self.req_inner(&subid, filters).await {
@ -116,12 +114,14 @@ impl WebSocketService {
if user.is_none() {
for filter in filters.iter() {
// If any DM kinds were requested
if filter.as_filter()?.num_kinds() == 0
|| filter.as_filter()?.kinds().any(|k| k.0 == 4 || k.0 == 1059)
if filter.num_kinds() == 0
|| filter
.kinds()
.any(|k| k.as_u16() == 4 || k.as_u16() == 1059)
{
// They need to AUTH first to request DMs
let reply = NostrReply::Closed(
&subid,
subid,
NostrReplyPrefix::AuthRequired,
"DM kinds were included in the REQ".to_owned(),
);
@ -140,7 +140,7 @@ impl WebSocketService {
// Serve events matching subscription
{
let mut events: Vec<Event> = Vec::new();
let mut events: Vec<&Event> = Vec::new();
for filter in filters.iter() {
let screen = |event: &Event| {
@ -149,11 +149,13 @@ impl WebSocketService {
};
let filter_events = {
let config = &*GLOBALS.config.read();
GLOBALS
.store
.get()
.unwrap()
.find_events(filter.as_filter()?, screen, config)?
GLOBALS.store.get().unwrap().find_events(
filter,
config.allow_scraping,
config.allow_scrape_if_limited_to,
config.allow_scrape_if_max_seconds,
screen,
)?
};
events.extend(filter_events);
}
@ -261,9 +263,9 @@ impl WebSocketService {
let authorized_user = authorized_user(&user);
// Delineate the event back out of the session buffer
let event = Event::delineate(&self.buffer)?;
let event = unsafe { Event::delineate(&self.buffer)? };
let event_flags = event_flags(&event, &user);
let event_flags = event_flags(event, &user);
if !event_flags.author_is_an_authorized_user || GLOBALS.config.read().verify_events {
// Verify the event is valid (id is hash, signature is valid)
@ -273,7 +275,7 @@ impl WebSocketService {
}
// Screen the event to see if we are willing to accept it
if !screen_incoming_event(&event, event_flags, authorized_user).await? {
if !screen_incoming_event(event, event_flags, authorized_user).await? {
if self.user.is_some() {
return Err(ChorusError::Restricted.into());
} else {
@ -282,7 +284,7 @@ impl WebSocketService {
}
// Store and index the event
let offset = GLOBALS.store.get().unwrap().store_event(&event)?;
let offset = GLOBALS.store.get().unwrap().store_event(event)?;
GLOBALS.new_events.send(offset)?; // advertise the new event
Ok(())
@ -349,14 +351,14 @@ impl WebSocketService {
async fn auth_inner(&mut self) -> Result<(), Error> {
// Delineate the event back out of the session buffer
let event = Event::delineate(&self.buffer)?;
let event = unsafe { Event::delineate(&self.buffer)? };
// Verify the event (even if config.verify_events is off, because this is
// strictly necessary for AUTH)
event.verify()?;
// Verify the event is the right kind
if event.kind() != Kind(22242) {
if event.kind() != Kind::from(22242) {
return Err(ChorusError::AuthFailure("Wrong event kind".to_string()).into());
}
@ -402,7 +404,7 @@ impl WebSocketService {
}
// Verify the created_at timestamp is within reason
let timediff = (Time::now().0 as i64).abs_diff(event.created_at().0 as i64);
let timediff = (Time::now().as_u64() as i64).abs_diff(event.created_at().as_u64() as i64);
if timediff > 600 {
return Err(
ChorusError::AuthFailure("Time is more than 10 minutes off".to_string()).into(),
@ -417,27 +419,17 @@ impl WebSocketService {
}
async fn screen_incoming_event(
event: &Event<'_>,
event: &Event,
event_flags: EventFlags,
authorized_user: bool,
) -> Result<bool, Error> {
// Reject if event approval is false
if let Some(false) = GLOBALS
.store
.get()
.unwrap()
.get_event_approval(event.id())?
{
if let Some(false) = crate::get_event_approval(GLOBALS.store.get().unwrap(), event.id())? {
return Err(ChorusError::BannedEvent.into());
}
// Reject if pubkey approval is false
if let Some(false) = GLOBALS
.store
.get()
.unwrap()
.get_pubkey_approval(event.pubkey())?
{
if let Some(false) = crate::get_pubkey_approval(GLOBALS.store.get().unwrap(), event.pubkey())? {
return Err(ChorusError::BannedUser.into());
}
@ -463,7 +455,7 @@ async fn screen_incoming_event(
}
// Accept relay lists from anybody
if event.kind() == Kind(10002) && GLOBALS.config.read().serve_relay_lists {
if event.kind() == Kind::from(10002) && GLOBALS.config.read().serve_relay_lists {
return Ok(true);
}
@ -494,19 +486,19 @@ async fn screen_incoming_event(
}
pub fn screen_outgoing_event(
event: &Event<'_>,
event: &Event,
event_flags: &EventFlags,
authorized_user: bool,
) -> bool {
// Forbid if it is a private event (DM or GiftWrap) and theey are neither the recipient
// nor the author
if event.kind() == Kind(4) || event.kind() == Kind(1059) {
if event.kind() == Kind::from(4) || event.kind() == Kind::from(1059) {
return event_flags.tags_current_user || event_flags.author_is_current_user;
}
// Forbid (and delete) if it has an expired expiration tag
if matches!(event.is_expired(), Ok(true)) {
let _ = GLOBALS.store.get().unwrap().delete_event(event.id());
let _ = GLOBALS.store.get().unwrap().remove_event(event.id());
return false;
}
@ -516,7 +508,7 @@ pub fn screen_outgoing_event(
}
// Allow Relay Lists
if event.kind() == Kind(10002) && GLOBALS.config.read().serve_relay_lists {
if event.kind() == Kind::from(10002) && GLOBALS.config.read().serve_relay_lists {
return true;
}
@ -536,16 +528,12 @@ pub fn screen_outgoing_event(
}
// Allow if event is explicitly approved
if let Ok(Some(true)) = GLOBALS.store.get().unwrap().get_event_approval(event.id()) {
if let Ok(Some(true)) = crate::get_event_approval(GLOBALS.store.get().unwrap(), event.id()) {
return true;
}
// Allow if author is explicitly approved
if let Ok(Some(true)) = GLOBALS
.store
.get()
.unwrap()
.get_pubkey_approval(event.pubkey())
if let Ok(Some(true)) = crate::get_pubkey_approval(GLOBALS.store.get().unwrap(), event.pubkey())
{
return true;
}
@ -568,7 +556,7 @@ pub struct EventFlags {
pub tags_current_user: bool,
}
pub fn event_flags(event: &Event<'_>, user: &Option<Pubkey>) -> EventFlags {
pub fn event_flags(event: &Event, user: &Option<Pubkey>) -> EventFlags {
let author_is_an_authorized_user = GLOBALS.config.read().user_keys.contains(&event.pubkey());
let author_is_current_user = match user {

View File

@ -1,4 +1,4 @@
use crate::types::{Event, Id};
use pocket_types::{Event, Id};
use std::fmt;
pub enum NostrReplyPrefix {
@ -31,7 +31,7 @@ impl fmt::Display for NostrReplyPrefix {
pub enum NostrReply<'a> {
Auth(String),
Event(&'a str, Event<'a>),
Event(&'a str, &'a Event),
Ok(Id, bool, NostrReplyPrefix, String),
Eose(&'a str),
Closed(&'a str, NostrReplyPrefix, String),

View File

@ -1,6 +1,6 @@
use crate::config::Config;
use crate::error::{ChorusError, Error};
use crate::globals::GLOBALS;
use chorus_lib::config::Config;
use chorus_lib::error::{ChorusError, Error};
use rustls::{Certificate, PrivateKey};
use std::fs::File;
use std::io::BufReader;

View File

@ -1,7 +1,7 @@
use crate::config::Config;
use crate::error::Error;
use crate::globals::GLOBALS;
use chorus_lib::config::Config;
use chorus_lib::error::Error;
use chorus_lib::ip::HashedPeer;
use crate::ip::HashedPeer;
use hyper::{Body, Request, Response, StatusCode};
pub async fn serve_http(peer: HashedPeer, request: Request<Body>) -> Result<Response<Body>, Error> {