Your locally hosted lumina server for IDAPro

Compare changes

Choose any two refs to compare.

+5
.github/ISSUE_TEMPLATE/config.yml
··· 1 + blank_issues_enabled: false 2 + contact_links: 3 + - name: Community support 4 + url: https://github.com/naim94a/lumen/discussions 5 + about: Please ask questions here
+2
.github/workflows/rust.yml
··· 35 35 toolchain: stable 36 36 override: true 37 37 profile: minimal 38 + - name: Code Formatting 39 + run: cargo fmt --check 38 40 - name: Build 39 41 run: cargo build 40 42 - name: Run tests
+30 -3
CHANGELOG.md
··· 2 2 3 3 ## [Unreleased] - _TBD_ 4 4 5 + ### Added 6 + 7 + - User management 8 + 9 + ## [v0.4.0] - 2024-03-19 10 + 11 + ### Added 12 + 13 + - Implemented the function histories command. 14 + - Configurable time limits. 15 + 16 + ### Fixed 17 + 18 + - RPC HelloResult will now report if deletes are enabled. 19 + 20 + ### Changes 21 + 22 + - Applied code formatting using `cargo fmt` 23 + 5 24 ## [v0.3.0] - 2023-08-22 25 + 6 26 ### Added 27 + 7 28 - This changelog. 8 29 - Support for IDA 8.1+ delete command. 9 30 - Pooling for connections to database. ··· 13 34 - Add Metrics for prometheus. 14 35 15 36 ### Fixed 37 + 16 38 - 8K stack size is too small for debug builds. 17 39 18 40 ## [v0.2.0] - 2022-10-12 41 + 19 42 ### Added 43 + 20 44 - Protocol: support for IDA 8.1+ user authentication. 21 45 - Client connection duration limitations. 46 + 22 47 ### Changed 48 + 23 49 - Tokio's thread size is reduced from 4M to 8K. 24 50 25 - ## [v0.1.0] - 2021-01-21 26 - This is Lumen's first tagged release. It contains a few fixes and dependency updates since the initial commit (2020-12-17). 51 + ## [v0.1.0] - 2021-01-21 27 52 53 + This is Lumen's first tagged release. It contains a few fixes and dependency updates since the initial commit (2020-12-17). 28 54 29 - [Unreleased]: https://github.com/naim94a/lumen/compare/v0.3.0...HEAD 55 + [Unreleased]: https://github.com/naim94a/lumen/compare/v0.4.0...HEAD 56 + [v0.4.0]: https://github.com/naim94a/lumen/compare/v0.3.0...v0.4.0 30 57 [v0.3.0]: https://github.com/naim94a/lumen/compare/v0.2.0...v0.3.0 31 58 [v0.2.0]: https://github.com/naim94a/lumen/compare/v0.1.0...v0.2.0 32 59 [v0.1.0]: https://github.com/naim94a/lumen/releases/tag/v0.1.0
+533 -351
Cargo.lock
··· 4 4 5 5 [[package]] 6 6 name = "addr2line" 7 - version = "0.20.0" 7 + version = "0.21.0" 8 8 source = "registry+https://github.com/rust-lang/crates.io-index" 9 - checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" 9 + checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 10 dependencies = [ 11 11 "gimli", 12 12 ] ··· 19 19 20 20 [[package]] 21 21 name = "aho-corasick" 22 - version = "1.0.4" 22 + version = "1.1.3" 23 23 source = "registry+https://github.com/rust-lang/crates.io-index" 24 - checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" 24 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 25 dependencies = [ 26 26 "memchr", 27 27 ] 28 28 29 29 [[package]] 30 30 name = "anstream" 31 - version = "0.3.2" 31 + version = "0.6.13" 32 32 source = "registry+https://github.com/rust-lang/crates.io-index" 33 - checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" 33 + checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 34 34 dependencies = [ 35 35 "anstyle", 36 36 "anstyle-parse", 37 37 "anstyle-query", 38 38 "anstyle-wincon", 39 39 "colorchoice", 40 - "is-terminal", 41 40 "utf8parse", 42 41 ] 43 42 44 43 [[package]] 45 44 name = "anstyle" 46 - version = "1.0.1" 45 + version = "1.0.6" 47 46 source = "registry+https://github.com/rust-lang/crates.io-index" 48 - checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" 47 + checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 49 48 50 49 [[package]] 51 50 name = "anstyle-parse" 52 - version = "0.2.1" 51 + version = "0.2.3" 53 52 source = "registry+https://github.com/rust-lang/crates.io-index" 54 - checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" 53 + checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 55 54 dependencies = [ 56 55 "utf8parse", 57 56 ] 58 57 59 58 [[package]] 60 59 name = "anstyle-query" 61 - version = "1.0.0" 60 + version = "1.0.2" 62 61 source = "registry+https://github.com/rust-lang/crates.io-index" 63 - checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" 62 + checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 64 63 dependencies = [ 65 - "windows-sys", 64 + "windows-sys 0.52.0", 66 65 ] 67 66 68 67 [[package]] 69 68 name = "anstyle-wincon" 70 - version = "1.0.2" 69 + version = "3.0.2" 71 70 source = "registry+https://github.com/rust-lang/crates.io-index" 72 - checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" 71 + checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 73 72 dependencies = [ 74 73 "anstyle", 75 - "windows-sys", 74 + "windows-sys 0.52.0", 76 75 ] 77 76 78 77 [[package]] 79 78 name = "anyhow" 80 - version = "1.0.75" 79 + version = "1.0.81" 81 80 source = "registry+https://github.com/rust-lang/crates.io-index" 82 - checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 81 + checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" 83 82 84 83 [[package]] 85 84 name = "async-trait" 86 - version = "0.1.73" 85 + version = "0.1.78" 87 86 source = "registry+https://github.com/rust-lang/crates.io-index" 88 - checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 87 + checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" 89 88 dependencies = [ 90 89 "proc-macro2", 91 90 "quote", ··· 100 99 101 100 [[package]] 102 101 name = "backtrace" 103 - version = "0.3.68" 102 + version = "0.3.70" 104 103 source = "registry+https://github.com/rust-lang/crates.io-index" 105 - checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" 104 + checksum = "95d8e92cac0961e91dbd517496b00f7e9b92363dbe6d42c3198268323798860c" 106 105 dependencies = [ 107 106 "addr2line", 108 107 "cc", ··· 115 114 116 115 [[package]] 117 116 name = "base64" 118 - version = "0.13.1" 119 - source = "registry+https://github.com/rust-lang/crates.io-index" 120 - checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 121 - 122 - [[package]] 123 - name = "base64" 124 - version = "0.21.2" 117 + version = "0.21.7" 125 118 source = "registry+https://github.com/rust-lang/crates.io-index" 126 - checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" 119 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 127 120 128 121 [[package]] 129 122 name = "bb8" 130 - version = "0.8.1" 123 + version = "0.8.3" 131 124 source = "registry+https://github.com/rust-lang/crates.io-index" 132 - checksum = "98b4b0f25f18bcdc3ac72bdb486ed0acf7e185221fd4dc985bc15db5800b0ba2" 125 + checksum = "df7c2093d15d6a1d33b1f972e1c5ea3177748742b97a5f392aa83a65262c6780" 133 126 dependencies = [ 134 127 "async-trait", 135 128 "futures-channel", ··· 152 145 153 146 [[package]] 154 147 name = "bitflags" 155 - version = "2.4.0" 148 + version = "2.5.0" 156 149 source = "registry+https://github.com/rust-lang/crates.io-index" 157 - checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" 150 + checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 158 151 159 152 [[package]] 160 153 name = "block-buffer" ··· 166 159 ] 167 160 168 161 [[package]] 162 + name = "bumpalo" 163 + version = "3.15.4" 164 + source = "registry+https://github.com/rust-lang/crates.io-index" 165 + checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" 166 + 167 + [[package]] 169 168 name = "byteorder" 170 - version = "1.4.3" 169 + version = "1.5.0" 171 170 source = "registry+https://github.com/rust-lang/crates.io-index" 172 - checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 171 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 173 172 174 173 [[package]] 175 174 name = "bytes" 176 - version = "1.4.0" 175 + version = "1.5.0" 177 176 source = "registry+https://github.com/rust-lang/crates.io-index" 178 - checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 177 + checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 179 178 180 179 [[package]] 181 180 name = "cc" 182 - version = "1.0.82" 181 + version = "1.0.90" 183 182 source = "registry+https://github.com/rust-lang/crates.io-index" 184 - checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" 185 - dependencies = [ 186 - "libc", 187 - ] 183 + checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 188 184 189 185 [[package]] 190 186 name = "cfg-if" ··· 194 190 195 191 [[package]] 196 192 name = "clap" 197 - version = "4.3.22" 193 + version = "4.5.3" 198 194 source = "registry+https://github.com/rust-lang/crates.io-index" 199 - checksum = "b417ae4361bca3f5de378294fc7472d3c4ed86a5ef9f49e93ae722f432aae8d2" 195 + checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" 200 196 dependencies = [ 201 197 "clap_builder", 202 198 ] 203 199 204 200 [[package]] 205 201 name = "clap_builder" 206 - version = "4.3.22" 202 + version = "4.5.2" 207 203 source = "registry+https://github.com/rust-lang/crates.io-index" 208 - checksum = "9c90dc0f0e42c64bff177ca9d7be6fcc9ddb0f26a6e062174a61c84dd6c644d4" 204 + checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 209 205 dependencies = [ 210 206 "anstream", 211 207 "anstyle", ··· 215 211 216 212 [[package]] 217 213 name = "clap_lex" 218 - version = "0.5.0" 214 + version = "0.7.0" 219 215 source = "registry+https://github.com/rust-lang/crates.io-index" 220 - checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" 216 + checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 221 217 222 218 [[package]] 223 219 name = "colorchoice" ··· 236 232 "futures-util", 237 233 "log", 238 234 "native-tls", 235 + "pbkdf2", 239 236 "postgres-native-tls", 240 237 "prometheus-client", 238 + "rand", 241 239 "serde", 240 + "sha2", 241 + "time", 242 242 "tokio", 243 243 "tokio-postgres", 244 244 "toml", ··· 247 247 248 248 [[package]] 249 249 name = "core-foundation" 250 - version = "0.9.3" 250 + version = "0.9.4" 251 251 source = "registry+https://github.com/rust-lang/crates.io-index" 252 - checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 252 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 253 253 dependencies = [ 254 254 "core-foundation-sys", 255 255 "libc", ··· 257 257 258 258 [[package]] 259 259 name = "core-foundation-sys" 260 - version = "0.8.4" 260 + version = "0.8.6" 261 261 source = "registry+https://github.com/rust-lang/crates.io-index" 262 - checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 262 + checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 263 263 264 264 [[package]] 265 265 name = "cpufeatures" 266 - version = "0.2.9" 266 + version = "0.2.12" 267 267 source = "registry+https://github.com/rust-lang/crates.io-index" 268 - checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" 268 + checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 269 269 dependencies = [ 270 270 "libc", 271 271 ] ··· 281 281 ] 282 282 283 283 [[package]] 284 + name = "data-encoding" 285 + version = "2.5.0" 286 + source = "registry+https://github.com/rust-lang/crates.io-index" 287 + checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" 288 + 289 + [[package]] 284 290 name = "deranged" 285 - version = "0.3.7" 291 + version = "0.3.11" 286 292 source = "registry+https://github.com/rust-lang/crates.io-index" 287 - checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" 293 + checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 294 + dependencies = [ 295 + "powerfmt", 296 + ] 288 297 289 298 [[package]] 290 299 name = "diesel" 291 - version = "2.1.0" 300 + version = "2.1.5" 292 301 source = "registry+https://github.com/rust-lang/crates.io-index" 293 - checksum = "f7a532c1f99a0f596f6960a60d1e119e91582b24b39e2d83a190e61262c3ef0c" 302 + checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559" 294 303 dependencies = [ 295 - "bitflags 2.4.0", 304 + "bitflags 2.5.0", 296 305 "byteorder", 297 306 "diesel_derives", 298 307 "itoa", ··· 301 310 302 311 [[package]] 303 312 name = "diesel-async" 304 - version = "0.3.2" 313 + version = "0.4.1" 305 314 source = "registry+https://github.com/rust-lang/crates.io-index" 306 - checksum = "c7e7974099f0d9bde0e010dd3a673555276a474f3362a7a52ab535a57b7c5056" 315 + checksum = "acada1517534c92d3f382217b485db8a8638f111b0e3f2a2a8e26165050f77be" 307 316 dependencies = [ 308 317 "async-trait", 309 318 "bb8", ··· 316 325 317 326 [[package]] 318 327 name = "diesel_derives" 319 - version = "2.1.0" 328 + version = "2.1.3" 320 329 source = "registry+https://github.com/rust-lang/crates.io-index" 321 - checksum = "74398b79d81e52e130d991afeed9c86034bb1b7735f46d2f5bf7deb261d80303" 330 + checksum = "5d02eecb814ae714ffe61ddc2db2dd03e6c49a42e269b5001355500d431cce0c" 322 331 dependencies = [ 323 332 "diesel_table_macro_syntax", 324 333 "proc-macro2", ··· 354 363 355 364 [[package]] 356 365 name = "encoding_rs" 357 - version = "0.8.32" 366 + version = "0.8.33" 358 367 source = "registry+https://github.com/rust-lang/crates.io-index" 359 - checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" 368 + checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 360 369 dependencies = [ 361 370 "cfg-if", 362 371 ] 363 372 364 373 [[package]] 365 374 name = "env_logger" 366 - version = "0.10.0" 375 + version = "0.10.2" 367 376 source = "registry+https://github.com/rust-lang/crates.io-index" 368 - checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" 377 + checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 369 378 dependencies = [ 370 379 "humantime", 371 380 "is-terminal", ··· 382 391 383 392 [[package]] 384 393 name = "errno" 385 - version = "0.3.2" 394 + version = "0.3.8" 386 395 source = "registry+https://github.com/rust-lang/crates.io-index" 387 - checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" 396 + checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 388 397 dependencies = [ 389 - "errno-dragonfly", 390 398 "libc", 391 - "windows-sys", 392 - ] 393 - 394 - [[package]] 395 - name = "errno-dragonfly" 396 - version = "0.1.2" 397 - source = "registry+https://github.com/rust-lang/crates.io-index" 398 - checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 399 - dependencies = [ 400 - "cc", 401 - "libc", 399 + "windows-sys 0.52.0", 402 400 ] 403 401 404 402 [[package]] ··· 409 407 410 408 [[package]] 411 409 name = "fastrand" 412 - version = "2.0.0" 410 + version = "2.0.1" 413 411 source = "registry+https://github.com/rust-lang/crates.io-index" 414 - checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" 412 + checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" 413 + 414 + [[package]] 415 + name = "finl_unicode" 416 + version = "1.2.0" 417 + source = "registry+https://github.com/rust-lang/crates.io-index" 418 + checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" 415 419 416 420 [[package]] 417 421 name = "fnv" ··· 436 440 437 441 [[package]] 438 442 name = "form_urlencoded" 439 - version = "1.2.0" 443 + version = "1.2.1" 440 444 source = "registry+https://github.com/rust-lang/crates.io-index" 441 - checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" 445 + checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 442 446 dependencies = [ 443 447 "percent-encoding", 444 448 ] 445 449 446 450 [[package]] 447 451 name = "futures" 448 - version = "0.3.28" 452 + version = "0.3.30" 449 453 source = "registry+https://github.com/rust-lang/crates.io-index" 450 - checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" 454 + checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 451 455 dependencies = [ 452 456 "futures-channel", 453 457 "futures-core", ··· 460 464 461 465 [[package]] 462 466 name = "futures-channel" 463 - version = "0.3.28" 467 + version = "0.3.30" 464 468 source = "registry+https://github.com/rust-lang/crates.io-index" 465 - checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" 469 + checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 466 470 dependencies = [ 467 471 "futures-core", 468 472 "futures-sink", ··· 470 474 471 475 [[package]] 472 476 name = "futures-core" 473 - version = "0.3.28" 477 + version = "0.3.30" 474 478 source = "registry+https://github.com/rust-lang/crates.io-index" 475 - checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 479 + checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 476 480 477 481 [[package]] 478 482 name = "futures-executor" 479 - version = "0.3.28" 483 + version = "0.3.30" 480 484 source = "registry+https://github.com/rust-lang/crates.io-index" 481 - checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" 485 + checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 482 486 dependencies = [ 483 487 "futures-core", 484 488 "futures-task", ··· 487 491 488 492 [[package]] 489 493 name = "futures-io" 490 - version = "0.3.28" 494 + version = "0.3.30" 491 495 source = "registry+https://github.com/rust-lang/crates.io-index" 492 - checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" 496 + checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 493 497 494 498 [[package]] 495 499 name = "futures-macro" 496 - version = "0.3.28" 500 + version = "0.3.30" 497 501 source = "registry+https://github.com/rust-lang/crates.io-index" 498 - checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" 502 + checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 499 503 dependencies = [ 500 504 "proc-macro2", 501 505 "quote", ··· 504 508 505 509 [[package]] 506 510 name = "futures-sink" 507 - version = "0.3.28" 511 + version = "0.3.30" 508 512 source = "registry+https://github.com/rust-lang/crates.io-index" 509 - checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 513 + checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 510 514 511 515 [[package]] 512 516 name = "futures-task" 513 - version = "0.3.28" 517 + version = "0.3.30" 514 518 source = "registry+https://github.com/rust-lang/crates.io-index" 515 - checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" 519 + checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 516 520 517 521 [[package]] 518 522 name = "futures-util" 519 - version = "0.3.28" 523 + version = "0.3.30" 520 524 source = "registry+https://github.com/rust-lang/crates.io-index" 521 - checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" 525 + checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 522 526 dependencies = [ 523 527 "futures-channel", 524 528 "futures-core", ··· 544 548 545 549 [[package]] 546 550 name = "getrandom" 547 - version = "0.2.10" 551 + version = "0.2.12" 548 552 source = "registry+https://github.com/rust-lang/crates.io-index" 549 - checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" 553 + checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 550 554 dependencies = [ 551 555 "cfg-if", 552 556 "libc", ··· 555 559 556 560 [[package]] 557 561 name = "gimli" 558 - version = "0.27.3" 562 + version = "0.28.1" 559 563 source = "registry+https://github.com/rust-lang/crates.io-index" 560 - checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" 564 + checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 561 565 562 566 [[package]] 563 567 name = "h2" 564 - version = "0.3.20" 568 + version = "0.3.25" 565 569 source = "registry+https://github.com/rust-lang/crates.io-index" 566 - checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" 570 + checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" 567 571 dependencies = [ 568 572 "bytes", 569 573 "fnv", ··· 571 575 "futures-sink", 572 576 "futures-util", 573 577 "http", 574 - "indexmap 1.9.3", 578 + "indexmap", 575 579 "slab", 576 580 "tokio", 577 581 "tokio-util", ··· 580 584 581 585 [[package]] 582 586 name = "hashbrown" 583 - version = "0.12.3" 584 - source = "registry+https://github.com/rust-lang/crates.io-index" 585 - checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 586 - 587 - [[package]] 588 - name = "hashbrown" 589 - version = "0.14.0" 587 + version = "0.14.3" 590 588 source = "registry+https://github.com/rust-lang/crates.io-index" 591 - checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" 589 + checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 592 590 593 591 [[package]] 594 592 name = "headers" 595 - version = "0.3.8" 593 + version = "0.3.9" 596 594 source = "registry+https://github.com/rust-lang/crates.io-index" 597 - checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" 595 + checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" 598 596 dependencies = [ 599 - "base64 0.13.1", 600 - "bitflags 1.3.2", 597 + "base64", 601 598 "bytes", 602 599 "headers-core", 603 600 "http", ··· 617 614 618 615 [[package]] 619 616 name = "hermit-abi" 620 - version = "0.3.2" 617 + version = "0.3.9" 621 618 source = "registry+https://github.com/rust-lang/crates.io-index" 622 - checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" 619 + checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 623 620 624 621 [[package]] 625 622 name = "hmac" ··· 632 629 633 630 [[package]] 634 631 name = "http" 635 - version = "0.2.9" 632 + version = "0.2.12" 636 633 source = "registry+https://github.com/rust-lang/crates.io-index" 637 - checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" 634 + checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 638 635 dependencies = [ 639 636 "bytes", 640 637 "fnv", ··· 643 640 644 641 [[package]] 645 642 name = "http-body" 646 - version = "0.4.5" 643 + version = "0.4.6" 647 644 source = "registry+https://github.com/rust-lang/crates.io-index" 648 - checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 645 + checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 649 646 dependencies = [ 650 647 "bytes", 651 648 "http", ··· 672 669 673 670 [[package]] 674 671 name = "hyper" 675 - version = "0.14.27" 672 + version = "0.14.28" 676 673 source = "registry+https://github.com/rust-lang/crates.io-index" 677 - checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" 674 + checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" 678 675 dependencies = [ 679 676 "bytes", 680 677 "futures-channel", ··· 687 684 "httpdate", 688 685 "itoa", 689 686 "pin-project-lite", 690 - "socket2 0.4.9", 687 + "socket2", 691 688 "tokio", 692 689 "tower-service", 693 690 "tracing", ··· 696 693 697 694 [[package]] 698 695 name = "idna" 699 - version = "0.4.0" 696 + version = "0.5.0" 700 697 source = "registry+https://github.com/rust-lang/crates.io-index" 701 - checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" 698 + checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 702 699 dependencies = [ 703 700 "unicode-bidi", 704 701 "unicode-normalization", ··· 706 703 707 704 [[package]] 708 705 name = "indexmap" 709 - version = "1.9.3" 706 + version = "2.2.5" 710 707 source = "registry+https://github.com/rust-lang/crates.io-index" 711 - checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 712 - dependencies = [ 713 - "autocfg", 714 - "hashbrown 0.12.3", 715 - ] 716 - 717 - [[package]] 718 - name = "indexmap" 719 - version = "2.0.0" 720 - source = "registry+https://github.com/rust-lang/crates.io-index" 721 - checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" 708 + checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" 722 709 dependencies = [ 723 710 "equivalent", 724 - "hashbrown 0.14.0", 711 + "hashbrown", 725 712 ] 726 713 727 714 [[package]] 728 715 name = "is-terminal" 729 - version = "0.4.9" 716 + version = "0.4.12" 730 717 source = "registry+https://github.com/rust-lang/crates.io-index" 731 - checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" 718 + checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" 732 719 dependencies = [ 733 720 "hermit-abi", 734 - "rustix", 735 - "windows-sys", 721 + "libc", 722 + "windows-sys 0.52.0", 736 723 ] 737 724 738 725 [[package]] 739 726 name = "itoa" 740 - version = "1.0.9" 727 + version = "1.0.10" 741 728 source = "registry+https://github.com/rust-lang/crates.io-index" 742 - checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 729 + checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 730 + 731 + [[package]] 732 + name = "js-sys" 733 + version = "0.3.69" 734 + source = "registry+https://github.com/rust-lang/crates.io-index" 735 + checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 736 + dependencies = [ 737 + "wasm-bindgen", 738 + ] 743 739 744 740 [[package]] 745 741 name = "lazy_static" ··· 749 745 750 746 [[package]] 751 747 name = "libc" 752 - version = "0.2.147" 748 + version = "0.2.153" 753 749 source = "registry+https://github.com/rust-lang/crates.io-index" 754 - checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 750 + checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 755 751 756 752 [[package]] 757 753 name = "linux-raw-sys" 758 - version = "0.4.5" 754 + version = "0.4.13" 759 755 source = "registry+https://github.com/rust-lang/crates.io-index" 760 - checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" 756 + checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 761 757 762 758 [[package]] 763 759 name = "lock_api" 764 - version = "0.4.10" 760 + version = "0.4.11" 765 761 source = "registry+https://github.com/rust-lang/crates.io-index" 766 - checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" 762 + checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 767 763 dependencies = [ 768 764 "autocfg", 769 765 "scopeguard", ··· 771 767 772 768 [[package]] 773 769 name = "log" 774 - version = "0.4.20" 770 + version = "0.4.21" 775 771 source = "registry+https://github.com/rust-lang/crates.io-index" 776 - checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 772 + checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 777 773 778 774 [[package]] 779 775 name = "lumen" ··· 785 781 "native-tls", 786 782 "pretty_env_logger", 787 783 "prometheus-client", 784 + "rpassword", 788 785 "tokio", 789 786 "tokio-native-tls", 790 787 "warp", ··· 792 789 793 790 [[package]] 794 791 name = "md-5" 795 - version = "0.10.5" 792 + version = "0.10.6" 796 793 source = "registry+https://github.com/rust-lang/crates.io-index" 797 - checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 794 + checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 798 795 dependencies = [ 796 + "cfg-if", 799 797 "digest", 800 798 ] 801 799 802 800 [[package]] 803 801 name = "memchr" 804 - version = "2.5.0" 802 + version = "2.7.1" 805 803 source = "registry+https://github.com/rust-lang/crates.io-index" 806 - checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 804 + checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 807 805 808 806 [[package]] 809 807 name = "mime" ··· 823 821 824 822 [[package]] 825 823 name = "miniz_oxide" 826 - version = "0.7.1" 824 + version = "0.7.2" 827 825 source = "registry+https://github.com/rust-lang/crates.io-index" 828 - checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 826 + checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 829 827 dependencies = [ 830 828 "adler", 831 829 ] 832 830 833 831 [[package]] 834 832 name = "mio" 835 - version = "0.8.8" 833 + version = "0.8.11" 836 834 source = "registry+https://github.com/rust-lang/crates.io-index" 837 - checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" 835 + checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 838 836 dependencies = [ 839 837 "libc", 840 838 "wasi", 841 - "windows-sys", 839 + "windows-sys 0.48.0", 842 840 ] 843 841 844 842 [[package]] ··· 876 874 "security-framework-sys", 877 875 "tempfile", 878 876 ] 877 + 878 + [[package]] 879 + name = "num-conv" 880 + version = "0.1.0" 881 + source = "registry+https://github.com/rust-lang/crates.io-index" 882 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 879 883 880 884 [[package]] 881 885 name = "num_cpus" ··· 889 893 890 894 [[package]] 891 895 name = "object" 892 - version = "0.31.1" 896 + version = "0.32.2" 893 897 source = "registry+https://github.com/rust-lang/crates.io-index" 894 - checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" 898 + checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 895 899 dependencies = [ 896 900 "memchr", 897 901 ] 898 902 899 903 [[package]] 900 904 name = "once_cell" 901 - version = "1.18.0" 905 + version = "1.19.0" 902 906 source = "registry+https://github.com/rust-lang/crates.io-index" 903 - checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 907 + checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 904 908 905 909 [[package]] 906 910 name = "openssl" 907 - version = "0.10.56" 911 + version = "0.10.64" 908 912 source = "registry+https://github.com/rust-lang/crates.io-index" 909 - checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" 913 + checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" 910 914 dependencies = [ 911 - "bitflags 1.3.2", 915 + "bitflags 2.5.0", 912 916 "cfg-if", 913 917 "foreign-types", 914 918 "libc", ··· 936 940 937 941 [[package]] 938 942 name = "openssl-sys" 939 - version = "0.9.91" 943 + version = "0.9.101" 940 944 source = "registry+https://github.com/rust-lang/crates.io-index" 941 - checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" 945 + checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" 942 946 dependencies = [ 943 947 "cc", 944 948 "libc", ··· 958 962 959 963 [[package]] 960 964 name = "parking_lot_core" 961 - version = "0.9.8" 965 + version = "0.9.9" 962 966 source = "registry+https://github.com/rust-lang/crates.io-index" 963 - checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" 967 + checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 964 968 dependencies = [ 965 969 "cfg-if", 966 970 "libc", 967 971 "redox_syscall", 968 972 "smallvec", 969 - "windows-targets", 973 + "windows-targets 0.48.5", 974 + ] 975 + 976 + [[package]] 977 + name = "pbkdf2" 978 + version = "0.12.2" 979 + source = "registry+https://github.com/rust-lang/crates.io-index" 980 + checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 981 + dependencies = [ 982 + "digest", 983 + "hmac", 970 984 ] 971 985 972 986 [[package]] 973 987 name = "percent-encoding" 974 - version = "2.3.0" 988 + version = "2.3.1" 975 989 source = "registry+https://github.com/rust-lang/crates.io-index" 976 - checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" 990 + checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 977 991 978 992 [[package]] 979 993 name = "phf" ··· 995 1009 996 1010 [[package]] 997 1011 name = "pin-project" 998 - version = "1.1.3" 1012 + version = "1.1.5" 999 1013 source = "registry+https://github.com/rust-lang/crates.io-index" 1000 - checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" 1014 + checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1001 1015 dependencies = [ 1002 1016 "pin-project-internal", 1003 1017 ] 1004 1018 1005 1019 [[package]] 1006 1020 name = "pin-project-internal" 1007 - version = "1.1.3" 1021 + version = "1.1.5" 1008 1022 source = "registry+https://github.com/rust-lang/crates.io-index" 1009 - checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" 1023 + checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1010 1024 dependencies = [ 1011 1025 "proc-macro2", 1012 1026 "quote", ··· 1015 1029 1016 1030 [[package]] 1017 1031 name = "pin-project-lite" 1018 - version = "0.2.12" 1032 + version = "0.2.13" 1019 1033 source = "registry+https://github.com/rust-lang/crates.io-index" 1020 - checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" 1034 + checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 1021 1035 1022 1036 [[package]] 1023 1037 name = "pin-utils" ··· 1027 1041 1028 1042 [[package]] 1029 1043 name = "pkg-config" 1030 - version = "0.3.27" 1044 + version = "0.3.30" 1031 1045 source = "registry+https://github.com/rust-lang/crates.io-index" 1032 - checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" 1046 + checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1033 1047 1034 1048 [[package]] 1035 1049 name = "postgres-native-tls" ··· 1046 1060 1047 1061 [[package]] 1048 1062 name = "postgres-protocol" 1049 - version = "0.6.5" 1063 + version = "0.6.6" 1050 1064 source = "registry+https://github.com/rust-lang/crates.io-index" 1051 - checksum = "78b7fa9f396f51dffd61546fd8573ee20592287996568e6175ceb0f8699ad75d" 1065 + checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" 1052 1066 dependencies = [ 1053 - "base64 0.21.2", 1067 + "base64", 1054 1068 "byteorder", 1055 1069 "bytes", 1056 1070 "fallible-iterator", ··· 1064 1078 1065 1079 [[package]] 1066 1080 name = "postgres-types" 1067 - version = "0.2.5" 1081 + version = "0.2.6" 1068 1082 source = "registry+https://github.com/rust-lang/crates.io-index" 1069 - checksum = "f028f05971fe20f512bcc679e2c10227e57809a3af86a7606304435bc8896cd6" 1083 + checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" 1070 1084 dependencies = [ 1071 1085 "bytes", 1072 1086 "fallible-iterator", ··· 1074 1088 ] 1075 1089 1076 1090 [[package]] 1091 + name = "powerfmt" 1092 + version = "0.2.0" 1093 + source = "registry+https://github.com/rust-lang/crates.io-index" 1094 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1095 + 1096 + [[package]] 1077 1097 name = "ppv-lite86" 1078 1098 version = "0.2.17" 1079 1099 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1091 1111 1092 1112 [[package]] 1093 1113 name = "proc-macro2" 1094 - version = "1.0.66" 1114 + version = "1.0.79" 1095 1115 source = "registry+https://github.com/rust-lang/crates.io-index" 1096 - checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 1116 + checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 1097 1117 dependencies = [ 1098 1118 "unicode-ident", 1099 1119 ] 1100 1120 1101 1121 [[package]] 1102 1122 name = "prometheus-client" 1103 - version = "0.21.2" 1123 + version = "0.22.2" 1104 1124 source = "registry+https://github.com/rust-lang/crates.io-index" 1105 - checksum = "3c99afa9a01501019ac3a14d71d9f94050346f55ca471ce90c799a15c58f61e2" 1125 + checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa" 1106 1126 dependencies = [ 1107 1127 "dtoa", 1108 1128 "itoa", ··· 1123 1143 1124 1144 [[package]] 1125 1145 name = "quote" 1126 - version = "1.0.33" 1146 + version = "1.0.35" 1127 1147 source = "registry+https://github.com/rust-lang/crates.io-index" 1128 - checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 1148 + checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 1129 1149 dependencies = [ 1130 1150 "proc-macro2", 1131 1151 ] ··· 1162 1182 1163 1183 [[package]] 1164 1184 name = "redox_syscall" 1165 - version = "0.3.5" 1185 + version = "0.4.1" 1166 1186 source = "registry+https://github.com/rust-lang/crates.io-index" 1167 - checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" 1187 + checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1168 1188 dependencies = [ 1169 1189 "bitflags 1.3.2", 1170 1190 ] 1171 1191 1172 1192 [[package]] 1173 1193 name = "regex" 1174 - version = "1.9.3" 1194 + version = "1.10.3" 1175 1195 source = "registry+https://github.com/rust-lang/crates.io-index" 1176 - checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" 1196 + checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 1177 1197 dependencies = [ 1178 1198 "aho-corasick", 1179 1199 "memchr", ··· 1183 1203 1184 1204 [[package]] 1185 1205 name = "regex-automata" 1186 - version = "0.3.6" 1206 + version = "0.4.6" 1187 1207 source = "registry+https://github.com/rust-lang/crates.io-index" 1188 - checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" 1208 + checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 1189 1209 dependencies = [ 1190 1210 "aho-corasick", 1191 1211 "memchr", ··· 1194 1214 1195 1215 [[package]] 1196 1216 name = "regex-syntax" 1197 - version = "0.7.4" 1217 + version = "0.8.2" 1198 1218 source = "registry+https://github.com/rust-lang/crates.io-index" 1199 - checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" 1219 + checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 1220 + 1221 + [[package]] 1222 + name = "rpassword" 1223 + version = "7.3.1" 1224 + source = "registry+https://github.com/rust-lang/crates.io-index" 1225 + checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" 1226 + dependencies = [ 1227 + "libc", 1228 + "rtoolbox", 1229 + "windows-sys 0.48.0", 1230 + ] 1231 + 1232 + [[package]] 1233 + name = "rtoolbox" 1234 + version = "0.0.2" 1235 + source = "registry+https://github.com/rust-lang/crates.io-index" 1236 + checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" 1237 + dependencies = [ 1238 + "libc", 1239 + "windows-sys 0.48.0", 1240 + ] 1200 1241 1201 1242 [[package]] 1202 1243 name = "rustc-demangle" ··· 1206 1247 1207 1248 [[package]] 1208 1249 name = "rustix" 1209 - version = "0.38.8" 1250 + version = "0.38.32" 1210 1251 source = "registry+https://github.com/rust-lang/crates.io-index" 1211 - checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" 1252 + checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 1212 1253 dependencies = [ 1213 - "bitflags 2.4.0", 1254 + "bitflags 2.5.0", 1214 1255 "errno", 1215 1256 "libc", 1216 1257 "linux-raw-sys", 1217 - "windows-sys", 1258 + "windows-sys 0.52.0", 1218 1259 ] 1219 1260 1220 1261 [[package]] 1221 1262 name = "rustls-pemfile" 1222 - version = "1.0.3" 1263 + version = "1.0.4" 1223 1264 source = "registry+https://github.com/rust-lang/crates.io-index" 1224 - checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" 1265 + checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1225 1266 dependencies = [ 1226 - "base64 0.21.2", 1267 + "base64", 1227 1268 ] 1228 1269 1229 1270 [[package]] 1230 1271 name = "ryu" 1231 - version = "1.0.15" 1272 + version = "1.0.17" 1232 1273 source = "registry+https://github.com/rust-lang/crates.io-index" 1233 - checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" 1274 + checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 1234 1275 1235 1276 [[package]] 1236 1277 name = "schannel" 1237 - version = "0.1.22" 1278 + version = "0.1.23" 1238 1279 source = "registry+https://github.com/rust-lang/crates.io-index" 1239 - checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" 1280 + checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 1240 1281 dependencies = [ 1241 - "windows-sys", 1282 + "windows-sys 0.52.0", 1242 1283 ] 1243 1284 1244 1285 [[package]] ··· 1288 1329 1289 1330 [[package]] 1290 1331 name = "serde" 1291 - version = "1.0.183" 1332 + version = "1.0.197" 1292 1333 source = "registry+https://github.com/rust-lang/crates.io-index" 1293 - checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" 1334 + checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 1294 1335 dependencies = [ 1295 1336 "serde_derive", 1296 1337 ] 1297 1338 1298 1339 [[package]] 1299 1340 name = "serde_derive" 1300 - version = "1.0.183" 1341 + version = "1.0.197" 1301 1342 source = "registry+https://github.com/rust-lang/crates.io-index" 1302 - checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" 1343 + checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 1303 1344 dependencies = [ 1304 1345 "proc-macro2", 1305 1346 "quote", ··· 1308 1349 1309 1350 [[package]] 1310 1351 name = "serde_json" 1311 - version = "1.0.105" 1352 + version = "1.0.114" 1312 1353 source = "registry+https://github.com/rust-lang/crates.io-index" 1313 - checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" 1354 + checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" 1314 1355 dependencies = [ 1315 1356 "itoa", 1316 1357 "ryu", ··· 1319 1360 1320 1361 [[package]] 1321 1362 name = "serde_spanned" 1322 - version = "0.6.3" 1363 + version = "0.6.5" 1323 1364 source = "registry+https://github.com/rust-lang/crates.io-index" 1324 - checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" 1365 + checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 1325 1366 dependencies = [ 1326 1367 "serde", 1327 1368 ] ··· 1340 1381 1341 1382 [[package]] 1342 1383 name = "sha1" 1343 - version = "0.10.5" 1384 + version = "0.10.6" 1344 1385 source = "registry+https://github.com/rust-lang/crates.io-index" 1345 - checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1386 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1346 1387 dependencies = [ 1347 1388 "cfg-if", 1348 1389 "cpufeatures", ··· 1351 1392 1352 1393 [[package]] 1353 1394 name = "sha2" 1354 - version = "0.10.7" 1395 + version = "0.10.8" 1355 1396 source = "registry+https://github.com/rust-lang/crates.io-index" 1356 - checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" 1397 + checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1357 1398 dependencies = [ 1358 1399 "cfg-if", 1359 1400 "cpufeatures", ··· 1371 1412 1372 1413 [[package]] 1373 1414 name = "siphasher" 1374 - version = "0.3.10" 1415 + version = "0.3.11" 1375 1416 source = "registry+https://github.com/rust-lang/crates.io-index" 1376 - checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 1417 + checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 1377 1418 1378 1419 [[package]] 1379 1420 name = "slab" 1380 - version = "0.4.8" 1421 + version = "0.4.9" 1381 1422 source = "registry+https://github.com/rust-lang/crates.io-index" 1382 - checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" 1423 + checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1383 1424 dependencies = [ 1384 1425 "autocfg", 1385 1426 ] 1386 1427 1387 1428 [[package]] 1388 1429 name = "smallvec" 1389 - version = "1.11.0" 1430 + version = "1.13.2" 1390 1431 source = "registry+https://github.com/rust-lang/crates.io-index" 1391 - checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" 1432 + checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1392 1433 1393 1434 [[package]] 1394 1435 name = "socket2" 1395 - version = "0.4.9" 1436 + version = "0.5.6" 1396 1437 source = "registry+https://github.com/rust-lang/crates.io-index" 1397 - checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 1438 + checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" 1398 1439 dependencies = [ 1399 1440 "libc", 1400 - "winapi", 1401 - ] 1402 - 1403 - [[package]] 1404 - name = "socket2" 1405 - version = "0.5.3" 1406 - source = "registry+https://github.com/rust-lang/crates.io-index" 1407 - checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" 1408 - dependencies = [ 1409 - "libc", 1410 - "windows-sys", 1441 + "windows-sys 0.52.0", 1411 1442 ] 1412 1443 1413 1444 [[package]] ··· 1418 1449 1419 1450 [[package]] 1420 1451 name = "stringprep" 1421 - version = "0.1.3" 1452 + version = "0.1.4" 1422 1453 source = "registry+https://github.com/rust-lang/crates.io-index" 1423 - checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" 1454 + checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" 1424 1455 dependencies = [ 1456 + "finl_unicode", 1425 1457 "unicode-bidi", 1426 1458 "unicode-normalization", 1427 1459 ] 1428 1460 1429 1461 [[package]] 1430 1462 name = "strsim" 1431 - version = "0.10.0" 1463 + version = "0.11.0" 1432 1464 source = "registry+https://github.com/rust-lang/crates.io-index" 1433 - checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1465 + checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 1434 1466 1435 1467 [[package]] 1436 1468 name = "subtle" ··· 1440 1472 1441 1473 [[package]] 1442 1474 name = "syn" 1443 - version = "2.0.29" 1475 + version = "2.0.53" 1444 1476 source = "registry+https://github.com/rust-lang/crates.io-index" 1445 - checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" 1477 + checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" 1446 1478 dependencies = [ 1447 1479 "proc-macro2", 1448 1480 "quote", ··· 1451 1483 1452 1484 [[package]] 1453 1485 name = "tempfile" 1454 - version = "3.7.1" 1486 + version = "3.10.1" 1455 1487 source = "registry+https://github.com/rust-lang/crates.io-index" 1456 - checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" 1488 + checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 1457 1489 dependencies = [ 1458 1490 "cfg-if", 1459 1491 "fastrand", 1460 - "redox_syscall", 1461 1492 "rustix", 1462 - "windows-sys", 1493 + "windows-sys 0.52.0", 1463 1494 ] 1464 1495 1465 1496 [[package]] 1466 1497 name = "termcolor" 1467 - version = "1.2.0" 1498 + version = "1.4.1" 1468 1499 source = "registry+https://github.com/rust-lang/crates.io-index" 1469 - checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 1500 + checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1470 1501 dependencies = [ 1471 1502 "winapi-util", 1472 1503 ] 1473 1504 1474 1505 [[package]] 1475 1506 name = "thiserror" 1476 - version = "1.0.47" 1507 + version = "1.0.58" 1477 1508 source = "registry+https://github.com/rust-lang/crates.io-index" 1478 - checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" 1509 + checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" 1479 1510 dependencies = [ 1480 1511 "thiserror-impl", 1481 1512 ] 1482 1513 1483 1514 [[package]] 1484 1515 name = "thiserror-impl" 1485 - version = "1.0.47" 1516 + version = "1.0.58" 1486 1517 source = "registry+https://github.com/rust-lang/crates.io-index" 1487 - checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" 1518 + checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" 1488 1519 dependencies = [ 1489 1520 "proc-macro2", 1490 1521 "quote", ··· 1493 1524 1494 1525 [[package]] 1495 1526 name = "time" 1496 - version = "0.3.25" 1527 + version = "0.3.34" 1497 1528 source = "registry+https://github.com/rust-lang/crates.io-index" 1498 - checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" 1529 + checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" 1499 1530 dependencies = [ 1500 1531 "deranged", 1532 + "num-conv", 1533 + "powerfmt", 1501 1534 "serde", 1502 1535 "time-core", 1503 1536 "time-macros", ··· 1505 1538 1506 1539 [[package]] 1507 1540 name = "time-core" 1508 - version = "0.1.1" 1541 + version = "0.1.2" 1509 1542 source = "registry+https://github.com/rust-lang/crates.io-index" 1510 - checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 1543 + checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1511 1544 1512 1545 [[package]] 1513 1546 name = "time-macros" 1514 - version = "0.2.11" 1547 + version = "0.2.17" 1515 1548 source = "registry+https://github.com/rust-lang/crates.io-index" 1516 - checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" 1549 + checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" 1517 1550 dependencies = [ 1551 + "num-conv", 1518 1552 "time-core", 1519 1553 ] 1520 1554 ··· 1535 1569 1536 1570 [[package]] 1537 1571 name = "tokio" 1538 - version = "1.32.0" 1572 + version = "1.36.0" 1539 1573 source = "registry+https://github.com/rust-lang/crates.io-index" 1540 - checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" 1574 + checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" 1541 1575 dependencies = [ 1542 1576 "backtrace", 1543 1577 "bytes", ··· 1547 1581 "parking_lot", 1548 1582 "pin-project-lite", 1549 1583 "signal-hook-registry", 1550 - "socket2 0.5.3", 1584 + "socket2", 1551 1585 "tokio-macros", 1552 - "windows-sys", 1586 + "windows-sys 0.48.0", 1553 1587 ] 1554 1588 1555 1589 [[package]] 1556 1590 name = "tokio-macros" 1557 - version = "2.1.0" 1591 + version = "2.2.0" 1558 1592 source = "registry+https://github.com/rust-lang/crates.io-index" 1559 - checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 1593 + checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1560 1594 dependencies = [ 1561 1595 "proc-macro2", 1562 1596 "quote", ··· 1575 1609 1576 1610 [[package]] 1577 1611 name = "tokio-postgres" 1578 - version = "0.7.8" 1612 + version = "0.7.10" 1579 1613 source = "registry+https://github.com/rust-lang/crates.io-index" 1580 - checksum = "6e89f6234aa8fd43779746012fcf53603cdb91fdd8399aa0de868c2d56b6dde1" 1614 + checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" 1581 1615 dependencies = [ 1582 1616 "async-trait", 1583 1617 "byteorder", ··· 1592 1626 "pin-project-lite", 1593 1627 "postgres-protocol", 1594 1628 "postgres-types", 1595 - "socket2 0.5.3", 1629 + "rand", 1630 + "socket2", 1596 1631 "tokio", 1597 1632 "tokio-util", 1633 + "whoami", 1598 1634 ] 1599 1635 1600 1636 [[package]] 1601 1637 name = "tokio-stream" 1602 - version = "0.1.14" 1638 + version = "0.1.15" 1603 1639 source = "registry+https://github.com/rust-lang/crates.io-index" 1604 - checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 1640 + checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" 1605 1641 dependencies = [ 1606 1642 "futures-core", 1607 1643 "pin-project-lite", ··· 1610 1646 1611 1647 [[package]] 1612 1648 name = "tokio-tungstenite" 1613 - version = "0.18.0" 1649 + version = "0.20.1" 1614 1650 source = "registry+https://github.com/rust-lang/crates.io-index" 1615 - checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" 1651 + checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" 1616 1652 dependencies = [ 1617 1653 "futures-util", 1618 1654 "log", ··· 1622 1658 1623 1659 [[package]] 1624 1660 name = "tokio-util" 1625 - version = "0.7.8" 1661 + version = "0.7.10" 1626 1662 source = "registry+https://github.com/rust-lang/crates.io-index" 1627 - checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 1663 + checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1628 1664 dependencies = [ 1629 1665 "bytes", 1630 1666 "futures-core", ··· 1636 1672 1637 1673 [[package]] 1638 1674 name = "toml" 1639 - version = "0.7.6" 1675 + version = "0.8.12" 1640 1676 source = "registry+https://github.com/rust-lang/crates.io-index" 1641 - checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" 1677 + checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" 1642 1678 dependencies = [ 1643 1679 "serde", 1644 1680 "serde_spanned", ··· 1648 1684 1649 1685 [[package]] 1650 1686 name = "toml_datetime" 1651 - version = "0.6.3" 1687 + version = "0.6.5" 1652 1688 source = "registry+https://github.com/rust-lang/crates.io-index" 1653 - checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" 1689 + checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 1654 1690 dependencies = [ 1655 1691 "serde", 1656 1692 ] 1657 1693 1658 1694 [[package]] 1659 1695 name = "toml_edit" 1660 - version = "0.19.14" 1696 + version = "0.22.9" 1661 1697 source = "registry+https://github.com/rust-lang/crates.io-index" 1662 - checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" 1698 + checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" 1663 1699 dependencies = [ 1664 - "indexmap 2.0.0", 1700 + "indexmap", 1665 1701 "serde", 1666 1702 "serde_spanned", 1667 1703 "toml_datetime", ··· 1676 1712 1677 1713 [[package]] 1678 1714 name = "tracing" 1679 - version = "0.1.37" 1715 + version = "0.1.40" 1680 1716 source = "registry+https://github.com/rust-lang/crates.io-index" 1681 - checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1717 + checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1682 1718 dependencies = [ 1683 - "cfg-if", 1684 1719 "log", 1685 1720 "pin-project-lite", 1686 1721 "tracing-core", ··· 1688 1723 1689 1724 [[package]] 1690 1725 name = "tracing-core" 1691 - version = "0.1.31" 1726 + version = "0.1.32" 1692 1727 source = "registry+https://github.com/rust-lang/crates.io-index" 1693 - checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 1728 + checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1694 1729 dependencies = [ 1695 1730 "once_cell", 1696 1731 ] 1697 1732 1698 1733 [[package]] 1699 1734 name = "try-lock" 1700 - version = "0.2.4" 1735 + version = "0.2.5" 1701 1736 source = "registry+https://github.com/rust-lang/crates.io-index" 1702 - checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1737 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1703 1738 1704 1739 [[package]] 1705 1740 name = "tungstenite" 1706 - version = "0.18.0" 1741 + version = "0.20.1" 1707 1742 source = "registry+https://github.com/rust-lang/crates.io-index" 1708 - checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" 1743 + checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" 1709 1744 dependencies = [ 1710 - "base64 0.13.1", 1711 1745 "byteorder", 1712 1746 "bytes", 1747 + "data-encoding", 1713 1748 "http", 1714 1749 "httparse", 1715 1750 "log", ··· 1722 1757 1723 1758 [[package]] 1724 1759 name = "typenum" 1725 - version = "1.16.0" 1760 + version = "1.17.0" 1726 1761 source = "registry+https://github.com/rust-lang/crates.io-index" 1727 - checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1762 + checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1728 1763 1729 1764 [[package]] 1730 1765 name = "unicase" 1731 - version = "2.6.0" 1766 + version = "2.7.0" 1732 1767 source = "registry+https://github.com/rust-lang/crates.io-index" 1733 - checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1768 + checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1734 1769 dependencies = [ 1735 1770 "version_check", 1736 1771 ] 1737 1772 1738 1773 [[package]] 1739 1774 name = "unicode-bidi" 1740 - version = "0.3.13" 1775 + version = "0.3.15" 1741 1776 source = "registry+https://github.com/rust-lang/crates.io-index" 1742 - checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" 1777 + checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1743 1778 1744 1779 [[package]] 1745 1780 name = "unicode-ident" 1746 - version = "1.0.11" 1781 + version = "1.0.12" 1747 1782 source = "registry+https://github.com/rust-lang/crates.io-index" 1748 - checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 1783 + checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1749 1784 1750 1785 [[package]] 1751 1786 name = "unicode-normalization" 1752 - version = "0.1.22" 1787 + version = "0.1.23" 1753 1788 source = "registry+https://github.com/rust-lang/crates.io-index" 1754 - checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1789 + checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1755 1790 dependencies = [ 1756 1791 "tinyvec", 1757 1792 ] 1758 1793 1759 1794 [[package]] 1760 1795 name = "url" 1761 - version = "2.4.0" 1796 + version = "2.5.0" 1762 1797 source = "registry+https://github.com/rust-lang/crates.io-index" 1763 - checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" 1798 + checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1764 1799 dependencies = [ 1765 1800 "form_urlencoded", 1766 1801 "idna", ··· 1802 1837 1803 1838 [[package]] 1804 1839 name = "warp" 1805 - version = "0.3.5" 1840 + version = "0.3.6" 1806 1841 source = "registry+https://github.com/rust-lang/crates.io-index" 1807 - checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" 1842 + checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169" 1808 1843 dependencies = [ 1809 1844 "bytes", 1810 1845 "futures-channel", ··· 1838 1873 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1839 1874 1840 1875 [[package]] 1876 + name = "wasite" 1877 + version = "0.1.0" 1878 + source = "registry+https://github.com/rust-lang/crates.io-index" 1879 + checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 1880 + 1881 + [[package]] 1882 + name = "wasm-bindgen" 1883 + version = "0.2.92" 1884 + source = "registry+https://github.com/rust-lang/crates.io-index" 1885 + checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 1886 + dependencies = [ 1887 + "cfg-if", 1888 + "wasm-bindgen-macro", 1889 + ] 1890 + 1891 + [[package]] 1892 + name = "wasm-bindgen-backend" 1893 + version = "0.2.92" 1894 + source = "registry+https://github.com/rust-lang/crates.io-index" 1895 + checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 1896 + dependencies = [ 1897 + "bumpalo", 1898 + "log", 1899 + "once_cell", 1900 + "proc-macro2", 1901 + "quote", 1902 + "syn", 1903 + "wasm-bindgen-shared", 1904 + ] 1905 + 1906 + [[package]] 1907 + name = "wasm-bindgen-macro" 1908 + version = "0.2.92" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1911 + dependencies = [ 1912 + "quote", 1913 + "wasm-bindgen-macro-support", 1914 + ] 1915 + 1916 + [[package]] 1917 + name = "wasm-bindgen-macro-support" 1918 + version = "0.2.92" 1919 + source = "registry+https://github.com/rust-lang/crates.io-index" 1920 + checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1921 + dependencies = [ 1922 + "proc-macro2", 1923 + "quote", 1924 + "syn", 1925 + "wasm-bindgen-backend", 1926 + "wasm-bindgen-shared", 1927 + ] 1928 + 1929 + [[package]] 1930 + name = "wasm-bindgen-shared" 1931 + version = "0.2.92" 1932 + source = "registry+https://github.com/rust-lang/crates.io-index" 1933 + checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1934 + 1935 + [[package]] 1936 + name = "web-sys" 1937 + version = "0.3.69" 1938 + source = "registry+https://github.com/rust-lang/crates.io-index" 1939 + checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1940 + dependencies = [ 1941 + "js-sys", 1942 + "wasm-bindgen", 1943 + ] 1944 + 1945 + [[package]] 1946 + name = "whoami" 1947 + version = "1.5.1" 1948 + source = "registry+https://github.com/rust-lang/crates.io-index" 1949 + checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" 1950 + dependencies = [ 1951 + "redox_syscall", 1952 + "wasite", 1953 + "web-sys", 1954 + ] 1955 + 1956 + [[package]] 1841 1957 name = "winapi" 1842 1958 version = "0.3.9" 1843 1959 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1855 1971 1856 1972 [[package]] 1857 1973 name = "winapi-util" 1858 - version = "0.1.5" 1974 + version = "0.1.6" 1859 1975 source = "registry+https://github.com/rust-lang/crates.io-index" 1860 - checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1976 + checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 1861 1977 dependencies = [ 1862 1978 "winapi", 1863 1979 ] ··· 1874 1990 source = "registry+https://github.com/rust-lang/crates.io-index" 1875 1991 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1876 1992 dependencies = [ 1877 - "windows-targets", 1993 + "windows-targets 0.48.5", 1994 + ] 1995 + 1996 + [[package]] 1997 + name = "windows-sys" 1998 + version = "0.52.0" 1999 + source = "registry+https://github.com/rust-lang/crates.io-index" 2000 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2001 + dependencies = [ 2002 + "windows-targets 0.52.4", 1878 2003 ] 1879 2004 1880 2005 [[package]] 1881 2006 name = "windows-targets" 1882 - version = "0.48.3" 2007 + version = "0.48.5" 1883 2008 source = "registry+https://github.com/rust-lang/crates.io-index" 1884 - checksum = "27f51fb4c64f8b770a823c043c7fad036323e1c48f55287b7bbb7987b2fcdf3b" 2009 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1885 2010 dependencies = [ 1886 - "windows_aarch64_gnullvm", 1887 - "windows_aarch64_msvc", 1888 - "windows_i686_gnu", 1889 - "windows_i686_msvc", 1890 - "windows_x86_64_gnu", 1891 - "windows_x86_64_gnullvm", 1892 - "windows_x86_64_msvc", 2011 + "windows_aarch64_gnullvm 0.48.5", 2012 + "windows_aarch64_msvc 0.48.5", 2013 + "windows_i686_gnu 0.48.5", 2014 + "windows_i686_msvc 0.48.5", 2015 + "windows_x86_64_gnu 0.48.5", 2016 + "windows_x86_64_gnullvm 0.48.5", 2017 + "windows_x86_64_msvc 0.48.5", 2018 + ] 2019 + 2020 + [[package]] 2021 + name = "windows-targets" 2022 + version = "0.52.4" 2023 + source = "registry+https://github.com/rust-lang/crates.io-index" 2024 + checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 2025 + dependencies = [ 2026 + "windows_aarch64_gnullvm 0.52.4", 2027 + "windows_aarch64_msvc 0.52.4", 2028 + "windows_i686_gnu 0.52.4", 2029 + "windows_i686_msvc 0.52.4", 2030 + "windows_x86_64_gnu 0.52.4", 2031 + "windows_x86_64_gnullvm 0.52.4", 2032 + "windows_x86_64_msvc 0.52.4", 1893 2033 ] 1894 2034 1895 2035 [[package]] 1896 2036 name = "windows_aarch64_gnullvm" 1897 - version = "0.48.3" 2037 + version = "0.48.5" 2038 + source = "registry+https://github.com/rust-lang/crates.io-index" 2039 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2040 + 2041 + [[package]] 2042 + name = "windows_aarch64_gnullvm" 2043 + version = "0.52.4" 1898 2044 source = "registry+https://github.com/rust-lang/crates.io-index" 1899 - checksum = "fde1bb55ae4ce76a597a8566d82c57432bc69c039449d61572a7a353da28f68c" 2045 + checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 1900 2046 1901 2047 [[package]] 1902 2048 name = "windows_aarch64_msvc" 1903 - version = "0.48.3" 2049 + version = "0.48.5" 1904 2050 source = "registry+https://github.com/rust-lang/crates.io-index" 1905 - checksum = "1513e8d48365a78adad7322fd6b5e4c4e99d92a69db8df2d435b25b1f1f286d4" 2051 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2052 + 2053 + [[package]] 2054 + name = "windows_aarch64_msvc" 2055 + version = "0.52.4" 2056 + source = "registry+https://github.com/rust-lang/crates.io-index" 2057 + checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 1906 2058 1907 2059 [[package]] 1908 2060 name = "windows_i686_gnu" 1909 - version = "0.48.3" 2061 + version = "0.48.5" 1910 2062 source = "registry+https://github.com/rust-lang/crates.io-index" 1911 - checksum = "60587c0265d2b842298f5858e1a5d79d146f9ee0c37be5782e92a6eb5e1d7a83" 2063 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2064 + 2065 + [[package]] 2066 + name = "windows_i686_gnu" 2067 + version = "0.52.4" 2068 + source = "registry+https://github.com/rust-lang/crates.io-index" 2069 + checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 1912 2070 1913 2071 [[package]] 1914 2072 name = "windows_i686_msvc" 1915 - version = "0.48.3" 2073 + version = "0.48.5" 1916 2074 source = "registry+https://github.com/rust-lang/crates.io-index" 1917 - checksum = "224fe0e0ffff5d2ea6a29f82026c8f43870038a0ffc247aa95a52b47df381ac4" 2075 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2076 + 2077 + [[package]] 2078 + name = "windows_i686_msvc" 2079 + version = "0.52.4" 2080 + source = "registry+https://github.com/rust-lang/crates.io-index" 2081 + checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 1918 2082 1919 2083 [[package]] 1920 2084 name = "windows_x86_64_gnu" 1921 - version = "0.48.3" 2085 + version = "0.48.5" 2086 + source = "registry+https://github.com/rust-lang/crates.io-index" 2087 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2088 + 2089 + [[package]] 2090 + name = "windows_x86_64_gnu" 2091 + version = "0.52.4" 1922 2092 source = "registry+https://github.com/rust-lang/crates.io-index" 1923 - checksum = "62fc52a0f50a088de499712cbc012df7ebd94e2d6eb948435449d76a6287e7ad" 2093 + checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 1924 2094 1925 2095 [[package]] 1926 2096 name = "windows_x86_64_gnullvm" 1927 - version = "0.48.3" 2097 + version = "0.48.5" 2098 + source = "registry+https://github.com/rust-lang/crates.io-index" 2099 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2100 + 2101 + [[package]] 2102 + name = "windows_x86_64_gnullvm" 2103 + version = "0.52.4" 2104 + source = "registry+https://github.com/rust-lang/crates.io-index" 2105 + checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 2106 + 2107 + [[package]] 2108 + name = "windows_x86_64_msvc" 2109 + version = "0.48.5" 1928 2110 source = "registry+https://github.com/rust-lang/crates.io-index" 1929 - checksum = "2093925509d91ea3d69bcd20238f4c2ecdb1a29d3c281d026a09705d0dd35f3d" 2111 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1930 2112 1931 2113 [[package]] 1932 2114 name = "windows_x86_64_msvc" 1933 - version = "0.48.3" 2115 + version = "0.52.4" 1934 2116 source = "registry+https://github.com/rust-lang/crates.io-index" 1935 - checksum = "b6ade45bc8bf02ae2aa34a9d54ba660a1a58204da34ba793c00d83ca3730b5f1" 2117 + checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 1936 2118 1937 2119 [[package]] 1938 2120 name = "winnow" 1939 - version = "0.5.12" 2121 + version = "0.6.5" 1940 2122 source = "registry+https://github.com/rust-lang/crates.io-index" 1941 - checksum = "83817bbecf72c73bad717ee86820ebf286203d2e04c3951f3cd538869c897364" 2123 + checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" 1942 2124 dependencies = [ 1943 2125 "memchr", 1944 2126 ]
+1
Cargo.toml
··· 1 1 [workspace] 2 + resolver = "2" 2 3 members = [ 3 4 "common", 4 5 "lumen",
+3 -3
Dockerfile
··· 1 - FROM rust:1.68.2-slim-buster 1 + FROM rust:1.74.1-slim-buster 2 2 ARG DEBIAN_FRONTEND=noninteractive 3 3 RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests ca-certificates pkg-config libssl-dev libpq-dev 4 4 ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse 5 5 6 6 RUN --mount=type=cache,target=$CARGO_HOME/registry \ 7 - cargo install diesel_cli --version 2.0.1 --no-default-features --features postgres 7 + cargo install diesel_cli --version 2.1.1 --no-default-features --features postgres 8 8 9 9 COPY common /lumen/common 10 10 COPY lumen /lumen/lumen ··· 15 15 FROM debian:buster-slim 16 16 ARG DEBIAN_FRONTEND=noninteractive 17 17 RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests openssl libpq5 && \ 18 - sed -i -e 's,\[ v3_req \],\[ v3_req \]\nextendedKeyUsage = serverAuth,' /etc/ssl/openssl.cnf 18 + sed -i -e 's,\[ v3_req \],\[ v3_req \]\nextendedKeyUsage = serverAuth,' /etc/ssl/openssl.cnf 19 19 RUN mkdir /usr/lib/lumen/ 20 20 21 21 COPY --from=0 /usr/local/cargo/bin/diesel /usr/bin/diesel
+125 -100
README.md
··· 1 - # Lumen 2 - A private Lumina server that can be used with IDA Pro 7.2+. 3 - 4 - [lumen.abda.nl](https://lumen.abda.nl/) runs this server. 5 - 6 - You can read about the protocol research [here](https://abda.nl/posts/introducing-lumen/). 7 - 8 - ## Features 9 - - Stores function signatures so you (and your team) can quickly identify functions that you found in the past using IDA's built-in Lumina features. 10 - - Backed by PostgreSQL 11 - - Experimental HTTP API that allows querying the database for comments by file or function hash. 12 - 13 - ## Getting Started 14 - 15 - ### Docker Method (Recommended) 16 - In this method precompiled docker images will be downloaded, All you need is [docker-compose.yml](./docker-compose.yml). 17 - 18 - 1. Install `docker-engine` and `docker-compose`. 19 - 2. If using a custom TLS certificate, copy the private key (`.p12`/`.pfx` extension) to `./dockershare` and set the key password in `.env` as `PKCSPASSWD`. 20 - 3. If using a custom Lumen config, copy it to `./dockershare/config.toml`. 21 - 4. Otherwise, or if you have finished these steps, just run `docker-compose up`. 22 - 5. Regardless, if TLS is enabled in the `config.toml`, a `hexrays.crt` will be generated in `./dockershare` to be copied to the IDA install directory. 23 - 24 - ### Building from source with Rust 25 - 1. `git clone https://github.com/naim94a/lumen.git` 26 - 2. Get a rust toolchain: https://rustup.rs/ 27 - 3. `cd lumen` 28 - 4. Setup a Postgres database and execute src/schema.sql on it 29 - 5. `cargo build --release` 30 - 31 - ### Usage 32 - ``` 33 - ./lumen -c config.toml 34 - ``` 35 - 36 - ### Configuring IDA 37 - 38 - #### IDA Pro >= 8.1 39 - If you used LUMEN in the past, remove the LUMINA settings in the ida.cfg or idauser.cfg files, otherwise you will get a warning about 40 - bad config parameters. 41 - 42 - ##### Setup under Linux : 43 - ``` 44 - #!/bin/sh 45 - export LUMINA_TLS=false 46 - $1 47 - ``` 48 - - save as ida_lumen.sh, "chmod +x ida_lumen.sh", now you can run IDA using "./ida_lumen.sh ./ida" or "./ida_lumen ./ida64" 49 - 50 - ##### Setup under Windows : 51 - ``` 52 - set LUMINA_TLS=false 53 - %1 54 - ``` 55 - - save as ida_lumen.bat, now you can run IDA using "./ida_lumen.bat ida.exe" or "./ida_lumen.bat ida64.exe" 56 - 57 - ##### Setup IDA 58 - - Go to Options, General, Lumina. Select "Use a private server", then set your host and port and "guest" as username and password. Click on ok. 59 - 60 - #### IDA Pro < 8.1 61 - You will need IDA Pro 7.2 or above in order to use _lumen_. 62 - 63 - > The following information may get sent to _lumen_ server: IDA key, Hostname, IDB path, original file path, file MD5, function signature, stack frames & comments. 64 - 65 - - In your IDA's installation directory open "cfg\ida.cfg" with your favorite text editor _(Example: C:\Program Files\IDA Pro 7.5\cfg\ida.cfg)_ 66 - - Locate the commented out `LUMINA_HOST`, `LUMINA_PORT`, and change their values to the address of your _lumen_ server. 67 - - If you didn't configure TLS, Add "LUMINA_TLS = NO" after the line with `LUMINA_PORT`. 68 - 69 - Example: 70 - ```C 71 - LUMINA_HOST = "192.168.1.1"; 72 - LUMINA_PORT = 1234 73 - 74 - // Only if TLS isn't used: 75 - LUMINA_TLS = NO 76 - ``` 77 - 78 - ### Configuring TLS 79 - IDA Pro uses a pinned certificate for Lumina's communcation, so adding a self-signed certificate to your root certificates won't work. 80 - Luckily, we can override the hard-coded public key by writing a DER-base64 encoded certificate to "hexrays.crt" in IDA's install directory. 81 - 82 - You may find the following commands useful: 83 - ```bash 84 - # create a certificate 85 - openssl req -x509 -newkey rsa:4096 -keyout lumen_key.pem -out lumen_crt.pem -days 365 -nodes 86 - 87 - # convert to pkcs12 for lumen; used for `lumen.tls` in config 88 - openssl pkcs12 -export -out lumen.p12 -inkey lumen_key.pem -in lumen_crt.pem 89 - 90 - # export public-key for IDA; Copy hexrays.crt to IDA installation folder 91 - openssl x509 -in lumen_crt.pem -out hexrays.crt 92 - ``` 93 - 94 - No attempt is made to merge function data - this may casuse a situation where metadata is inconsistent. 95 - Instead, the metadata with the highest calculated score is returned to the user. 96 - 97 - 98 - --- 99 - 100 - Developed by [Naim A.](https://github.com/naim94a); License: MIT. 1 + # Lumen 2 + 3 + A private Lumina server that can be used with IDA Pro 7.2+. 4 + 5 + [lumen.abda.nl](https://lumen.abda.nl/) runs this server. 6 + 7 + You can read about the protocol research [here](https://abda.nl/posts/introducing-lumen/). 8 + 9 + ## Features 10 + 11 + - Stores function signatures so you (and your team) can quickly identify functions that you found in the past using IDA's built-in Lumina features. 12 + - Backed by PostgreSQL 13 + - Experimental HTTP API that allows querying the database for comments by file or function hash. 14 + 15 + ## Getting Started 16 + 17 + ### Docker Method (Recommended) 18 + 19 + In this method precompiled docker images will be downloaded, All you need is [docker-compose.yml](./docker-compose.yml). 20 + 21 + 1. Install `docker-engine` and `docker-compose`. 22 + 2. If using a custom TLS certificate, copy the private key (`.p12`/`.pfx` extension) to `./dockershare` and set the key password in `.env` as `PKCSPASSWD`. 23 + 3. If using a custom Lumen config, copy it to `./dockershare/config.toml`. 24 + 4. Otherwise, or if you have finished these steps, just run `docker-compose up`. 25 + 5. Regardless, if TLS is enabled in the `config.toml`, a `hexrays.crt` will be generated in `./dockershare` to be copied to the IDA install directory. 26 + 27 + ### Building from source with Rust 28 + 29 + 1. `git clone https://github.com/naim94a/lumen.git` 30 + 2. Get a rust toolchain: https://rustup.rs/ 31 + 3. `cd lumen` 32 + 4. Setup a the database 33 + 34 + - install postgres 35 + - install diesel-cli and run migrations: 36 + 37 + ```bash 38 + cargo install diesel_cli --no-default-features -Fpostgres 39 + diesel --config-file common/diesel.toml \ 40 + --database-url postgres://postgres:password@localhost/lumen \ 41 + migration run 42 + ``` 43 + 44 + 5. `cargo build --release` 45 + 46 + ### Usage 47 + 48 + ```bash 49 + ./lumen -c config.toml 50 + ``` 51 + 52 + ### Configuring IDA 53 + 54 + #### IDA Pro >= 8.1 55 + 56 + If you used LUMEN in the past, remove the LUMINA settings in the ida.cfg or idauser.cfg files, otherwise you will get a warning about 57 + bad config parameters. 58 + 59 + ##### Setup under Linux : 60 + 61 + ```bash 62 + #!/bin/sh 63 + export LUMINA_TLS=false 64 + $1 65 + ``` 66 + 67 + - save as ida_lumen.sh, "chmod +x ida_lumen.sh", now you can run IDA using "./ida_lumen.sh ./ida" or "./ida_lumen ./ida64" 68 + 69 + ##### Setup under Windows : 70 + 71 + ```batch 72 + set LUMINA_TLS=false 73 + %1 74 + ``` 75 + 76 + - save as ida_lumen.bat, now you can run IDA using "./ida_lumen.bat ida.exe" or "./ida_lumen.bat ida64.exe" 77 + 78 + ##### Setup IDA 79 + 80 + - Go to Options, General, Lumina. Select "Use a private server", then set your host and port and "guest" as username and password. Click on ok. 81 + 82 + #### IDA Pro < 8.1 83 + 84 + You will need IDA Pro 7.2 or above in order to use _lumen_. 85 + 86 + > The following information may get sent to _lumen_ server: IDA key, Hostname, IDB path, original file path, file MD5, function signature, stack frames & comments. 87 + 88 + - In your IDA's installation directory open "cfg\ida.cfg" with your favorite text editor _(Example: C:\Program Files\IDA Pro 7.5\cfg\ida.cfg)_ 89 + - Locate the commented out `LUMINA_HOST`, `LUMINA_PORT`, and change their values to the address of your _lumen_ server. 90 + - If you didn't configure TLS, Add "LUMINA_TLS = NO" after the line with `LUMINA_PORT`. 91 + 92 + Example: 93 + 94 + ```C 95 + LUMINA_HOST = "192.168.1.1"; 96 + LUMINA_PORT = 1234 97 + 98 + // Only if TLS isn't used: 99 + LUMINA_TLS = NO 100 + ``` 101 + 102 + ### Configuring TLS 103 + 104 + IDA Pro uses a pinned certificate for Lumina's communcation, so adding a self-signed certificate to your root certificates won't work. 105 + Luckily, we can override the hard-coded public key by writing a DER-base64 encoded certificate to "hexrays.crt" in IDA's install directory. 106 + 107 + You may find the following commands useful: 108 + 109 + ```bash 110 + # create a certificate 111 + openssl req -x509 -newkey rsa:4096 -keyout lumen_key.pem -out lumen_crt.pem -days 365 -nodes 112 + 113 + # convert to pkcs12 for lumen; used for `lumen.tls` in config 114 + openssl pkcs12 -export -out lumen.p12 -inkey lumen_key.pem -in lumen_crt.pem 115 + 116 + # export public-key for IDA; Copy hexrays.crt to IDA installation folder 117 + openssl x509 -in lumen_crt.pem -out hexrays.crt 118 + ``` 119 + 120 + No attempt is made to merge function data - this may cause a situation where metadata is inconsistent. 121 + Instead, the metadata with the highest calculated score is returned to the user. 122 + 123 + --- 124 + 125 + Developed by [Naim A.](https://github.com/naim94a); License: MIT.
+8 -4
common/Cargo.toml
··· 13 13 postgres-native-tls = {version = "0.5", optional = true} 14 14 native-tls = {version = "0.2", optional = true} 15 15 futures-util = "0.3" 16 - toml = "0.7" 16 + toml = "0.8" 17 17 warp = {version = "0.3", optional = true} 18 18 binascii = "0.1" 19 19 20 20 tokio-postgres = {version = "0.7", default-features = false, optional = true} 21 21 diesel = {version = "2.1", optional = true, default-features = false, features = ["postgres_backend", "time"]} 22 - diesel-async = {version = "0.3", optional = true, features = ["postgres", "bb8"]} 22 + time = {version = "0.3.31", optional = true} 23 + diesel-async = {version = "0.4.1", optional = true, features = ["postgres", "bb8"]} 23 24 anyhow = "1.0" 24 - prometheus-client = "0.21.2" 25 + prometheus-client = "0.22.0" 26 + pbkdf2 = { version = "0.12.2" } 27 + sha2 = "0.10.8" 28 + rand = "0.8.5" 25 29 26 30 [features] 27 31 default = ["web", "db"] 28 32 web = ["warp"] 29 - db = ["tokio", "postgres-native-tls", "native-tls", "diesel", "diesel-async", "tokio-postgres"] 33 + db = ["tokio", "postgres-native-tls", "native-tls", "diesel", "diesel-async", "tokio-postgres", "time"]
+21
common/migrations/2024-01-20-215809_users/down.sql
··· 1 + -- don't allow table to be modified until we're done... 2 + LOCK TABLE users; 3 + 4 + -- delete funcs that belong to users 5 + DELETE FROM funcs USING dbs, users 6 + WHERE dbs.id=funcs.db_id 7 + AND users.id=dbs.user_id 8 + AND users.cred_id IS NOT NULL; 9 + 10 + -- delete dbs that belong to users 11 + DELETE FROM dbs USING users WHERE dbs.user_id=users.id AND users.cred_id IS NOT NULL; 12 + 13 + -- delete all users with creds... 14 + DELETE FROM users WHERE cred_id is NOT NULL; 15 + DROP TABLE creds CASCADE; 16 + 17 + CREATE UNIQUE INDEX IF NOT EXISTS user_rec ON users(lic_id,lic_data,hostname); 18 + CREATE UNIQUE INDEX IF NOT EXISTS user_hn_null ON users (lic_id,lic_data, (hostname IS NULL)) WHERE hostname is NULL; 19 + DROP INDEX user_cred_idx; 20 + 21 + ALTER TABLE users DROP COLUMN cred_id;
+22
common/migrations/2024-01-20-215809_users/up.sql
··· 1 + CREATE TABLE creds ( 2 + id SERIAL PRIMARY KEY, 3 + 4 + username VARCHAR(256) UNIQUE NOT NULL, 5 + email VARCHAR(256) UNIQUE NOT NULL, 6 + 7 + passwd_salt bytea, 8 + passwd_iters INTEGER NOT NULL DEFAULT 10000, 9 + passwd_hash bytea, 10 + 11 + last_active TIMESTAMPTZ, 12 + creation_dt TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, 13 + 14 + is_admin BOOLEAN NOT NULL DEFAULT FALSE, 15 + is_enabled BOOLEAN NOT NULL DEFAULT TRUE 16 + ); 17 + 18 + ALTER TABLE users ADD COLUMN cred_id INTEGER REFERENCES creds(id) ON DELETE CASCADE; 19 + 20 + CREATE UNIQUE INDEX user_cred_idx ON users(lic_id,lic_data,hostname,cred_id) NULLS NOT DISTINCT; 21 + DROP INDEX user_hn_null; 22 + DROP INDEX user_rec;
+3 -6
common/src/async_drop.rs
··· 1 1 use futures_util::{future::BoxFuture, Future}; 2 2 use log::trace; 3 - use tokio::{sync::mpsc::{UnboundedSender, unbounded_channel, WeakUnboundedSender}}; 3 + use tokio::sync::mpsc::{unbounded_channel, UnboundedSender, WeakUnboundedSender}; 4 4 5 5 enum AsyncDropperMsg { 6 6 Future(BoxFuture<'static, ()>), ··· 29 29 AsyncDropperMsg::Termination => { 30 30 trace!("term received for '{orig}'..."); 31 31 break; 32 - } 32 + }, 33 33 } 34 34 } 35 35 trace!("dropper '{orig}' exited."); ··· 41 41 /// Defers execution of a future to when the returned `AsyncDropGuard` is dropped 42 42 pub fn defer<F: Future<Output = ()> + Send + 'static>(&self, fut: F) -> AsyncDropGuard { 43 43 let tx = self.tx.downgrade(); 44 - AsyncDropGuard { 45 - tx, 46 - run: Some(Box::pin(fut)) 47 - } 44 + AsyncDropGuard { tx, run: Some(Box::pin(fut)) } 48 45 } 49 46 } 50 47 impl Drop for AsyncDropper {
+125 -57
common/src/config.rs
··· 1 - use serde::Deserialize; 2 - use toml::from_str; 3 - use std::{net::SocketAddr, path::PathBuf}; 4 - 5 - #[derive(Deserialize)] 6 - pub struct TlsIdentity { 7 - pub server_cert: PathBuf, 8 - } 9 - 10 - #[derive(Deserialize)] 11 - pub struct LuminaServer { 12 - pub bind_addr: SocketAddr, 13 - pub use_tls: Option<bool>, 14 - pub tls: Option<TlsIdentity>, 15 - pub server_name: Option<String>, 16 - pub allow_deletes: Option<bool>, 17 - } 18 - 19 - #[derive(Deserialize)] 20 - pub struct WebServer { 21 - pub bind_addr: SocketAddr, 22 - } 23 - 24 - #[derive(Deserialize)] 25 - pub struct Database { 26 - pub connection_info: String, 27 - 28 - pub use_tls: bool, 29 - pub server_ca: Option<PathBuf>, 30 - pub client_id: Option<PathBuf>, 31 - } 32 - 33 - #[derive(Deserialize)] 34 - pub struct Config { 35 - pub lumina: LuminaServer, 36 - pub api_server: Option<WebServer>, 37 - pub database: Database, 38 - } 39 - 40 - pub trait HasConfig { 41 - fn get_config(&self) -> &Config; 42 - } 43 - 44 - impl HasConfig for Config { 45 - fn get_config(&self) -> &Config { 46 - self 47 - } 48 - } 49 - 50 - pub fn load_config<R: std::io::Read>(mut fd: R) -> Config { 51 - let mut buf = vec![]; 52 - fd.read_to_end(&mut buf).expect("failed to read config"); 53 - 54 - let buf = std::str::from_utf8(&buf).expect("file contains invalid utf-8"); 55 - 56 - from_str(buf).expect("failed to parse configuration") 57 - } 1 + use serde::Deserialize; 2 + use std::num::NonZeroU32; 3 + use std::time::Duration; 4 + use std::{net::SocketAddr, path::PathBuf}; 5 + use toml::from_str; 6 + 7 + #[derive(Deserialize)] 8 + pub struct TlsIdentity { 9 + pub server_cert: PathBuf, 10 + } 11 + 12 + #[derive(Deserialize)] 13 + #[serde(default)] 14 + pub struct LuminaServer { 15 + pub bind_addr: SocketAddr, 16 + pub use_tls: bool, 17 + pub tls: Option<TlsIdentity>, 18 + pub server_name: Option<String>, 19 + pub allow_deletes: bool, 20 + 21 + /// limit of function histories to return per function. 22 + /// `None`, or `Some(0)` will disable the feature on the server. 23 + pub get_history_limit: NonZeroU32, 24 + } 25 + impl Default for LuminaServer { 26 + fn default() -> Self { 27 + Self { 28 + bind_addr: "0.0.0.0:1234".parse().unwrap(), 29 + use_tls: false, 30 + tls: None, 31 + server_name: None, 32 + allow_deletes: false, 33 + get_history_limit: NonZeroU32::new(50).unwrap(), 34 + } 35 + } 36 + } 37 + 38 + #[derive(Deserialize)] 39 + pub struct WebServer { 40 + pub bind_addr: SocketAddr, 41 + } 42 + 43 + #[derive(Deserialize)] 44 + pub struct Database { 45 + pub connection_info: String, 46 + 47 + pub use_tls: bool, 48 + pub server_ca: Option<PathBuf>, 49 + pub client_id: Option<PathBuf>, 50 + } 51 + 52 + #[derive(Deserialize, Debug)] 53 + #[serde(default)] 54 + pub struct Limits { 55 + /// Maximum time to wait on an idle connection between commands. 56 + pub command_timeout: Duration, 57 + 58 + /// Maximum time to all `PULL_MD` queries. 59 + pub pull_md_timeout: Duration, 60 + 61 + /// Maximum time to wait for `HELO` message. 62 + pub hello_timeout: Duration, 63 + 64 + /// Maximum time allowed until TLS handshake completes. 65 + pub tls_handshake_timeout: Duration, 66 + } 67 + 68 + impl Default for Limits { 69 + fn default() -> Self { 70 + Self { 71 + command_timeout: Duration::from_secs(3600), 72 + pull_md_timeout: Duration::from_secs(4 * 60), 73 + hello_timeout: Duration::from_secs(15), 74 + tls_handshake_timeout: Duration::from_secs(10), 75 + } 76 + } 77 + } 78 + 79 + #[derive(Deserialize)] 80 + #[serde(default)] 81 + pub struct Users { 82 + /// Sets if guests are allowed to login. required for IDA<8.1 83 + pub allow_guests: bool, 84 + 85 + /// PBKDF2 iterations for newly set passwords. 86 + pub pbkdf2_iterations: NonZeroU32, 87 + } 88 + 89 + impl Default for Users { 90 + fn default() -> Self { 91 + Self { allow_guests: true, pbkdf2_iterations: NonZeroU32::new(120_000).unwrap() } 92 + } 93 + } 94 + 95 + #[derive(Deserialize)] 96 + pub struct Config { 97 + pub lumina: LuminaServer, 98 + pub api_server: Option<WebServer>, 99 + pub database: Database, 100 + 101 + #[serde(default)] 102 + pub limits: Limits, 103 + 104 + #[serde(default)] 105 + pub users: Users, 106 + } 107 + 108 + pub trait HasConfig { 109 + fn get_config(&self) -> &Config; 110 + } 111 + 112 + impl HasConfig for Config { 113 + fn get_config(&self) -> &Config { 114 + self 115 + } 116 + } 117 + 118 + pub fn load_config<R: std::io::Read>(mut fd: R) -> Config { 119 + let mut buf = vec![]; 120 + fd.read_to_end(&mut buf).expect("failed to read config"); 121 + 122 + let buf = std::str::from_utf8(&buf).expect("file contains invalid utf-8"); 123 + 124 + from_str(buf).expect("failed to parse configuration") 125 + }
+592 -406
common/src/db/mod.rs
··· 1 - use log::*; 2 - use postgres_native_tls::MakeTlsConnector; 3 - use serde::Serialize; 4 - use tokio_postgres::{tls::MakeTlsConnect, Socket, NoTls}; 5 - use std::{collections::HashMap}; 6 - use crate::async_drop::{AsyncDropper, AsyncDropGuard}; 7 - mod schema_auto; 8 - pub mod schema; 9 - 10 - use diesel::{upsert::excluded, ExpressionMethods, QueryDsl, NullableExpressionMethods, sql_types::{Array, Binary, VarChar, Integer}, query_builder::{QueryFragment, Query}}; 11 - use diesel_async::RunQueryDsl; 12 - 13 - pub type DynConfig = dyn crate::config::HasConfig + Send + Sync; 14 - 15 - pub struct Database { 16 - tls_connector: Option<MakeTlsConnector>, 17 - diesel: diesel_async::pooled_connection::bb8::Pool<diesel_async::AsyncPgConnection>, 18 - dropper: AsyncDropper, 19 - } 20 - 21 - pub struct FunctionInfo { 22 - pub name: String, 23 - pub len: u32, 24 - pub data: Vec<u8>, 25 - pub popularity: u32, 26 - } 27 - 28 - #[derive(Debug, Serialize)] 29 - pub struct DbStats { 30 - unique_lics: i32, 31 - unique_hosts_per_lic: i32, 32 - 33 - unique_funcs: i32, 34 - total_funcs: i32, 35 - 36 - dbs: i32, 37 - unique_files: i32, 38 - } 39 - 40 - impl Database { 41 - pub async fn open(config: &crate::config::Database) -> Result<Self, anyhow::Error> { 42 - let connection_string = config.connection_info.as_str(); 43 - let tls_connector = if config.use_tls { 44 - Some(Self::make_tls(config).await) 45 - } else { 46 - None 47 - }; 48 - 49 - let (dropper, worker) = AsyncDropper::new(); 50 - tokio::task::spawn(worker); 51 - 52 - let diesel = Self::make_bb8_pool(connection_string, tls_connector.clone()).await?; 53 - 54 - Ok(Database{ 55 - tls_connector, 56 - dropper, 57 - diesel, 58 - }) 59 - } 60 - 61 - async fn make_pg_client<T>(db_url: &str, tls: T) -> diesel::result::ConnectionResult<diesel_async::AsyncPgConnection> 62 - where T: MakeTlsConnect<Socket>, 63 - T::Stream: Send + 'static { 64 - let (cli, conn) = tokio_postgres::connect(db_url, tls) 65 - .await 66 - .map_err(|e| { 67 - error!("failed to connect db: {e}"); 68 - diesel::result::ConnectionError::BadConnection(format!("{e}")) 69 - })?; 70 - 71 - tokio::spawn(async move { 72 - if let Err(e) = conn.await { 73 - error!("connection task error: {e}"); 74 - } 75 - }); 76 - 77 - diesel_async::AsyncPgConnection::try_from(cli).await 78 - } 79 - 80 - async fn make_bb8_pool(db_url: &str, tls: Option<MakeTlsConnector>) -> Result<diesel_async::pooled_connection::bb8::Pool<diesel_async::AsyncPgConnection>, anyhow::Error> { 81 - let cfg = diesel_async::pooled_connection::AsyncDieselConnectionManager::<diesel_async::AsyncPgConnection>::new_with_setup(db_url, move |db_url| { 82 - let tls = tls.clone(); 83 - Box::pin( async move { 84 - if let Some(tls) = tls { 85 - Self::make_pg_client(db_url, tls).await 86 - } else { 87 - Self::make_pg_client(db_url, NoTls).await 88 - } 89 - }) 90 - }); 91 - 92 - let pool = diesel_async::pooled_connection::bb8::Pool::builder() 93 - .min_idle(Some(1)) 94 - .build(cfg) 95 - .await?; 96 - Ok(pool) 97 - } 98 - 99 - async fn make_tls(database: &crate::config::Database) -> MakeTlsConnector { 100 - use native_tls::{TlsConnector, Certificate, Identity}; 101 - 102 - let mut tls_connector = TlsConnector::builder(); 103 - 104 - if let Some(ref client_identity) = database.client_id { 105 - let client_identity = tokio::fs::read(client_identity).await.expect("failed to read db's client id"); 106 - let client_identity = Identity::from_pkcs12(&client_identity, "").expect("failed to load db's client identity (PKCS12)"); 107 - tls_connector.identity(client_identity); 108 - } 109 - 110 - if let Some(ref server_ca) = database.server_ca { 111 - let server_ca = tokio::fs::read(server_ca).await.expect("failed to read db's server ca"); 112 - let server_ca = Certificate::from_pem(&server_ca).expect("failed to load db's server ca (PEM)"); 113 - tls_connector.add_root_certificate(server_ca); 114 - } 115 - 116 - let tls_connector = tls_connector 117 - .danger_accept_invalid_hostnames(true) 118 - .build() 119 - .expect("failed to build TlsConnector"); 120 - 121 - MakeTlsConnector::new(tls_connector) 122 - } 123 - 124 - pub async fn get_funcs(&self, funcs: &[crate::rpc::PullMetadataFunc<'_>]) -> Result<Vec<Option<FunctionInfo>>, anyhow::Error> { 125 - let chksums: Vec<&[u8]> = funcs.iter().map(|v| v.mb_hash).collect(); 126 - 127 - let rows: Vec<(String, i32, Vec<u8>, Vec<u8>)> = { 128 - let conn = &mut self.diesel.get().await?; 129 - 130 - let ct = self.cancel_guard(&*conn); 131 - 132 - let res: Vec<_> = BestMds(chksums.as_slice()) 133 - .get_results::<_>(conn).await?; 134 - ct.consume(); 135 - res 136 - }; 137 - 138 - let mut partial: HashMap<Vec<u8>, FunctionInfo> = rows 139 - .into_iter() 140 - .map(|row| { 141 - let v = FunctionInfo { 142 - name: row.0, 143 - len: row.1 as u32, 144 - data: row.2, 145 - popularity: 0, 146 - }; 147 - 148 - (row.3, v) 149 - }) 150 - .collect(); 151 - 152 - let results = partial.len(); 153 - 154 - let res: Vec<Option<FunctionInfo>> = chksums.iter().map(|&chksum| { 155 - partial.remove(chksum) 156 - }).collect(); 157 - 158 - trace!("found {}/{} results", results, chksums.len()); 159 - debug_assert_eq!(chksums.len(), res.len()); 160 - Ok(res) 161 - } 162 - 163 - pub async fn get_or_create_user<'a>(&self, user: &'a crate::rpc::RpcHello<'a>, hostname: &str) -> Result<i32, anyhow::Error> { 164 - use schema::users; 165 - 166 - let conn = &mut self.diesel.get().await?; 167 - 168 - let lic_id = &user.lic_number[..]; 169 - let lic_data = user.license_data; 170 - 171 - let get_user = || users::table.select(users::id) 172 - .filter(users::lic_data.eq(lic_data)) 173 - .filter(users::lic_id.eq(lic_id)) 174 - .filter(users::hostname.eq(hostname)); 175 - 176 - match get_user().get_result::<i32>(conn).await { 177 - Ok(v) => return Ok(v), 178 - Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 179 - _ => {}, 180 - }; 181 - 182 - match diesel::insert_into(users::table) 183 - .values(vec![ 184 - ( 185 - users::lic_id.eq(lic_id), 186 - users::lic_data.eq(lic_data), 187 - users::hostname.eq(hostname), 188 - ) 189 - ]) 190 - .returning(users::id) // xmax = 0 if the row is new 191 - .get_result::<i32>(conn) 192 - .await { 193 - Ok(v) => return Ok(v), 194 - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => {}, 195 - Err(e) => return Err(e.into()), 196 - } 197 - 198 - Ok(get_user().get_result::<i32>(conn).await?) 199 - } 200 - 201 - async fn get_or_create_file<'a>(&self, funcs: &'a crate::rpc::PushMetadata<'a>) -> Result<i32, anyhow::Error> { 202 - use schema::files::{table as files, chksum, id}; 203 - 204 - let hash = &funcs.md5[..]; 205 - 206 - let conn = &mut self.diesel.get().await?; 207 - 208 - let get_file = || files.filter(chksum.eq(hash)).select(id); 209 - 210 - match get_file().get_result::<i32>(conn).await { 211 - Ok(v) => return Ok(v), 212 - Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 213 - _ => {}, 214 - } 215 - 216 - match diesel::insert_into(files) 217 - .values(vec![(chksum.eq(hash),)]) 218 - .returning(id) 219 - .get_result::<i32>(conn) 220 - .await { 221 - Ok(v) => return Ok(v), 222 - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => {}, 223 - Err(e) => return Err(e.into()), 224 - } 225 - Ok(get_file().get_result::<i32>(conn).await?) 226 - } 227 - 228 - async fn get_or_create_db<'a>(&self, user: &'a crate::rpc::RpcHello<'a>, funcs: &'a crate::rpc::PushMetadata<'a>) -> Result<i32, anyhow::Error> { 229 - use schema::dbs::{table as dbs, id as db_id, user_id as db_user, file_id as db_file_id, file_path, idb_path}; 230 - 231 - let file_id = self.get_or_create_file(funcs); 232 - let user_id = self.get_or_create_user(user, funcs.hostname); 233 - 234 - let (file_id, user_id): (i32, i32) = futures_util::try_join!(file_id, user_id)?; 235 - 236 - let conn = &mut self.diesel.get().await?; 237 - 238 - let get_db = || { 239 - dbs.select(db_id) 240 - .filter(db_user.eq(user_id)) 241 - .filter(db_file_id.eq(file_id)) 242 - .filter(file_path.eq(funcs.file_path)) 243 - .filter(idb_path.eq(funcs.idb_path)) 244 - }; 245 - 246 - match get_db().get_result::<i32>(conn).await { 247 - Ok(v) => return Ok(v), 248 - Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 249 - _ => {}, 250 - }; 251 - 252 - match diesel::insert_into(dbs) 253 - .values(vec![( 254 - db_user.eq(user_id), 255 - db_file_id.eq(file_id), 256 - file_path.eq(funcs.file_path), 257 - idb_path.eq(funcs.idb_path), 258 - )]) 259 - .returning(db_id) 260 - .get_result::<i32>(conn) 261 - .await { 262 - Ok(id) => return Ok(id), 263 - Err(diesel::result::Error::DatabaseError(diesel::result::DatabaseErrorKind::UniqueViolation, _)) => {}, 264 - Err(e) => return Err(e.into()), 265 - }; 266 - Ok(get_db().get_result::<i32>(conn).await?) 267 - } 268 - 269 - pub async fn push_funcs<'a, 'b>(&'b self, user: &'a crate::rpc::RpcHello<'a>, funcs: &'a crate::rpc::PushMetadata<'a>, scores: &[u32]) -> Result<Vec<bool>, anyhow::Error> { 270 - use futures_util::TryStreamExt; 271 - 272 - // postgres has a limitation of binding per statement (i16::MAX). Split large push requests into smaller chunks. 273 - const PUSH_FUNC_CHUNK_SIZE: usize = 3000; 274 - 275 - let db_id = self.get_or_create_db(user, funcs).await?; 276 - 277 - let mut rows = Vec::with_capacity(funcs.funcs.len().min(PUSH_FUNC_CHUNK_SIZE)); 278 - let mut is_new = Vec::with_capacity(funcs.funcs.len()); 279 - let conn = &mut self.diesel.get().await?; 280 - let f2 = diesel::alias!(schema::funcs as f2); 281 - 282 - for (idx, (func, &score)) in funcs.funcs.iter().zip(scores.iter()).enumerate() { 283 - let name = func.name; 284 - let len = func.func_len as i32; 285 - let chksum = func.hash; 286 - let md = func.func_data; 287 - let score = score as i32; 288 - 289 - rows.push(( 290 - schema::funcs::name.eq(name), 291 - schema::funcs::len.eq(len), 292 - schema::funcs::chksum.eq(chksum), 293 - schema::funcs::metadata.eq(md), 294 - schema::funcs::rank.eq(score), 295 - schema::funcs::db_id.eq(db_id), 296 - )); 297 - 298 - if rows.len() < PUSH_FUNC_CHUNK_SIZE && idx < funcs.funcs.len() - 1 { 299 - continue; 300 - } 301 - 302 - let mut current_rows = Vec::with_capacity((funcs.funcs.len() - (idx + 1)).max(PUSH_FUNC_CHUNK_SIZE)); 303 - std::mem::swap(&mut current_rows, &mut rows); 304 - 305 - diesel::insert_into(schema::funcs::table) 306 - .values(current_rows) 307 - .on_conflict((schema::funcs::chksum, schema::funcs::db_id)) 308 - .do_update() 309 - .set(( 310 - schema::funcs::name.eq(excluded(schema::funcs::name)), 311 - schema::funcs::metadata.eq(excluded(schema::funcs::metadata)), 312 - schema::funcs::rank.eq(excluded(schema::funcs::rank)), 313 - schema::funcs::update_dt.eq(diesel::dsl::now) 314 - )) 315 - .returning(diesel::dsl::not(diesel::dsl::exists(f2.filter(f2.field(schema::funcs::chksum).eq(schema::funcs::chksum))))) // xmax=0 when a new row is created. 316 - .load_stream::<bool>(conn) 317 - .await? 318 - .try_fold(&mut is_new, |acc, item: bool| { 319 - acc.push(item); 320 - futures_util::future::ready(Ok(acc)) 321 - }) 322 - .await?; 323 - } 324 - 325 - Ok(is_new) 326 - } 327 - 328 - pub async fn get_file_funcs(&self, md5: &[u8], offset: i64, limit: i64) -> Result<Vec<(String, i32, Vec<u8>)>, anyhow::Error> { 329 - let conn = &mut self.diesel.get().await?; 330 - let results = schema::funcs::table 331 - .left_join(schema::dbs::table.left_join(schema::files::table)) 332 - .select((schema::funcs::name.assume_not_null(), schema::funcs::len.assume_not_null(), schema::funcs::chksum.assume_not_null())) 333 - .filter(schema::files::chksum.eq(md5)) 334 - .offset(offset) 335 - .limit(limit) 336 - .get_results::<(String, i32, Vec<u8>)>(conn).await?; 337 - Ok(results) 338 - } 339 - 340 - pub async fn get_files_with_func(&self, func: &[u8]) -> Result<Vec<Vec<u8>>, anyhow::Error> { 341 - let conn = &mut self.diesel.get().await?; 342 - 343 - let res = schema::files::table 344 - .left_join(schema::dbs::table.left_join(schema::funcs::table)) 345 - .select(schema::files::chksum.assume_not_null()) 346 - .distinct() 347 - .filter(schema::funcs::chksum.eq(func)) 348 - .get_results::<Vec<u8>>(conn) 349 - .await?; 350 - Ok(res) 351 - } 352 - 353 - fn cancel_guard(&self, conn: &diesel_async::pooled_connection::bb8::PooledConnection<'_, diesel_async::AsyncPgConnection>) -> AsyncDropGuard { 354 - let token = conn.cancel_token(); 355 - let tls_connector = self.tls_connector.clone(); 356 - self.dropper.defer(async move { 357 - debug!("cancelling query..."); 358 - 359 - if let Some(tls) = tls_connector { 360 - let _ = token.cancel_query(tls).await; 361 - } else { 362 - let _ = token.cancel_query(NoTls).await; 363 - } 364 - }) 365 - } 366 - 367 - pub async fn delete_metadata(&self, req: &crate::rpc::DelHistory<'_>) -> Result<(), anyhow::Error> { 368 - use schema::funcs::{table as funcs, chksum}; 369 - 370 - let chksums = req.funcs.iter() 371 - .map(|v| v.as_slice()) 372 - .collect::<Vec<_>>(); 373 - 374 - let conn = &mut self.diesel.get().await?; 375 - let rows_modified = diesel::delete(funcs.filter(chksum.eq_any(&chksums))) 376 - .execute(conn) 377 - .await?; 378 - 379 - debug!("deleted {rows_modified} rows"); 380 - 381 - Ok(()) 382 - } 383 - } 384 - 385 - // This is eww, but it's the fastest. 386 - struct BestMds<'a>(&'a [&'a [u8]]); 387 - impl<'a> QueryFragment<diesel::pg::Pg> for BestMds<'a> { 388 - fn walk_ast<'b>(&'b self, mut pass: diesel::query_builder::AstPass<'_, 'b, diesel::pg::Pg>) -> diesel::QueryResult<()> { 389 - pass.push_sql(r#"WITH best AS ( 390 - select chksum,MAX(rank) as maxrank from funcs f1 391 - WHERE chksum = ANY("#); 392 - pass.push_bind_param::<Array<Binary>, _>(&self.0)?; 393 - pass.push_sql(r#") 394 - GROUP BY chksum 395 - ) 396 - SELECT f2.name,f2.len,f2.metadata,f2.chksum FROM best 397 - LEFT JOIN funcs f2 ON (best.chksum=f2.chksum AND best.maxrank=f2.rank)"#); 398 - Ok(()) 399 - } 400 - } 401 - impl<'a> diesel::query_builder::QueryId for BestMds<'a> { 402 - type QueryId = BestMds<'static>; 403 - } 404 - impl<'a> Query for BestMds<'a> { 405 - type SqlType = (VarChar, Integer, Binary, Binary); 406 - } 1 + use crate::{ 2 + async_drop::{AsyncDropGuard, AsyncDropper}, 3 + db::schema::Creds, 4 + }; 5 + use log::*; 6 + use postgres_native_tls::MakeTlsConnector; 7 + use serde::Serialize; 8 + use std::collections::HashMap; 9 + use time::OffsetDateTime; 10 + use tokio_postgres::{tls::MakeTlsConnect, NoTls, Socket}; 11 + pub mod schema; 12 + mod schema_auto; 13 + 14 + use diesel::{ 15 + query_builder::{Query, QueryFragment}, 16 + sql_types::{Array, Binary, Integer, VarChar}, 17 + upsert::excluded, 18 + ExpressionMethods, NullableExpressionMethods, QueryDsl, SelectableHelper, 19 + }; 20 + use diesel_async::{pooled_connection::ManagerConfig, RunQueryDsl}; 21 + 22 + use self::schema::creds; 23 + 24 + pub type DynConfig = dyn crate::config::HasConfig + Send + Sync; 25 + 26 + pub struct Database { 27 + tls_connector: Option<MakeTlsConnector>, 28 + diesel: diesel_async::pooled_connection::bb8::Pool<diesel_async::AsyncPgConnection>, 29 + dropper: AsyncDropper, 30 + } 31 + 32 + pub struct FunctionInfo { 33 + pub name: String, 34 + pub len: u32, 35 + pub data: Vec<u8>, 36 + pub popularity: u32, 37 + } 38 + 39 + #[derive(Debug, Serialize)] 40 + pub struct DbStats { 41 + unique_lics: i32, 42 + unique_hosts_per_lic: i32, 43 + 44 + unique_funcs: i32, 45 + total_funcs: i32, 46 + 47 + dbs: i32, 48 + unique_files: i32, 49 + } 50 + 51 + impl Database { 52 + pub async fn open(config: &crate::config::Database) -> Result<Self, anyhow::Error> { 53 + let connection_string = config.connection_info.as_str(); 54 + let tls_connector = if config.use_tls { Some(Self::make_tls(config).await) } else { None }; 55 + 56 + let (dropper, worker) = AsyncDropper::new(); 57 + tokio::task::spawn(worker); 58 + 59 + let diesel = Self::make_bb8_pool(connection_string, tls_connector.clone()).await?; 60 + 61 + Ok(Database { tls_connector, dropper, diesel }) 62 + } 63 + 64 + async fn make_pg_client<T>( 65 + db_url: &str, tls: T, 66 + ) -> diesel::result::ConnectionResult<diesel_async::AsyncPgConnection> 67 + where 68 + T: MakeTlsConnect<Socket>, 69 + T::Stream: Send + 'static, 70 + { 71 + let (cli, conn) = tokio_postgres::connect(db_url, tls).await.map_err(|e| { 72 + error!("failed to connect db: {e}"); 73 + diesel::result::ConnectionError::BadConnection(format!("{e}")) 74 + })?; 75 + 76 + tokio::spawn(async move { 77 + if let Err(e) = conn.await { 78 + error!("connection task error: {e}"); 79 + } 80 + }); 81 + 82 + diesel_async::AsyncPgConnection::try_from(cli).await 83 + } 84 + 85 + async fn make_bb8_pool( 86 + db_url: &str, tls: Option<MakeTlsConnector>, 87 + ) -> Result< 88 + diesel_async::pooled_connection::bb8::Pool<diesel_async::AsyncPgConnection>, 89 + anyhow::Error, 90 + > { 91 + let mut config = ManagerConfig::default(); 92 + config.custom_setup = Box::new(move |db_url| { 93 + let tls = tls.clone(); 94 + Box::pin(async move { 95 + if let Some(tls) = tls { 96 + Self::make_pg_client(db_url, tls).await 97 + } else { 98 + Self::make_pg_client(db_url, NoTls).await 99 + } 100 + }) 101 + }); 102 + let cfg = diesel_async::pooled_connection::AsyncDieselConnectionManager::< 103 + diesel_async::AsyncPgConnection, 104 + >::new_with_config(db_url, config); 105 + 106 + let pool = diesel_async::pooled_connection::bb8::Pool::builder() 107 + .min_idle(Some(1)) 108 + .build(cfg) 109 + .await?; 110 + Ok(pool) 111 + } 112 + 113 + async fn make_tls(database: &crate::config::Database) -> MakeTlsConnector { 114 + use native_tls::{Certificate, Identity, TlsConnector}; 115 + 116 + let mut tls_connector = TlsConnector::builder(); 117 + 118 + if let Some(ref client_identity) = database.client_id { 119 + let client_identity = 120 + tokio::fs::read(client_identity).await.expect("failed to read db's client id"); 121 + let client_identity = Identity::from_pkcs12(&client_identity, "") 122 + .expect("failed to load db's client identity (PKCS12)"); 123 + tls_connector.identity(client_identity); 124 + } 125 + 126 + if let Some(ref server_ca) = database.server_ca { 127 + let server_ca = 128 + tokio::fs::read(server_ca).await.expect("failed to read db's server ca"); 129 + let server_ca = 130 + Certificate::from_pem(&server_ca).expect("failed to load db's server ca (PEM)"); 131 + tls_connector.add_root_certificate(server_ca); 132 + } 133 + 134 + let tls_connector = tls_connector 135 + .danger_accept_invalid_hostnames(true) 136 + .build() 137 + .expect("failed to build TlsConnector"); 138 + 139 + MakeTlsConnector::new(tls_connector) 140 + } 141 + 142 + pub async fn get_funcs( 143 + &self, funcs: &[crate::rpc::PatternId<'_>], 144 + ) -> Result<Vec<Option<FunctionInfo>>, anyhow::Error> { 145 + let chksums: Vec<&[u8]> = funcs.iter().map(|v| v.data).collect(); 146 + 147 + let rows: Vec<(String, i32, Vec<u8>, Vec<u8>)> = { 148 + let conn = &mut self.diesel.get().await?; 149 + 150 + let ct = self.cancel_guard(&*conn); 151 + 152 + let res: Vec<_> = BestMds(chksums.as_slice()).get_results::<_>(conn).await?; 153 + ct.consume(); 154 + res 155 + }; 156 + 157 + let mut partial: HashMap<Vec<u8>, FunctionInfo> = rows 158 + .into_iter() 159 + .map(|row| { 160 + let v = FunctionInfo { name: row.0, len: row.1 as u32, data: row.2, popularity: 0 }; 161 + 162 + (row.3, v) 163 + }) 164 + .collect(); 165 + 166 + let results = partial.len(); 167 + 168 + let res: Vec<Option<FunctionInfo>> = 169 + chksums.iter().map(|&chksum| partial.remove(chksum)).collect(); 170 + 171 + trace!("found {}/{} results", results, chksums.len()); 172 + debug_assert_eq!(chksums.len(), res.len()); 173 + Ok(res) 174 + } 175 + 176 + pub async fn get_or_create_user<'a>( 177 + &self, user: &'a crate::rpc::RpcHello<'a>, cred_id: Option<i32>, hostname: &str, 178 + ) -> Result<i32, anyhow::Error> { 179 + use schema::users; 180 + 181 + let conn = &mut self.diesel.get().await?; 182 + 183 + let lic_id = &user.lic_number[..]; 184 + let lic_data = user.license_data; 185 + 186 + let get_user = || { 187 + let query = users::table 188 + .into_boxed() 189 + .select(users::id) 190 + .filter(users::lic_data.eq(lic_data)) 191 + .filter(users::lic_id.eq(lic_id)) 192 + .filter(users::hostname.eq(hostname)); 193 + 194 + if let Some(cred_id) = cred_id { 195 + query.filter(users::cred_id.eq(cred_id)) 196 + } else { 197 + query.filter(users::cred_id.is_null()) 198 + } 199 + }; 200 + 201 + match get_user().get_result::<i32>(conn).await { 202 + Ok(v) => return Ok(v), 203 + Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 204 + _ => {}, 205 + }; 206 + 207 + match diesel::insert_into(users::table) 208 + .values(vec![( 209 + users::lic_id.eq(lic_id), 210 + users::lic_data.eq(lic_data), 211 + users::hostname.eq(hostname), 212 + users::cred_id.eq(cred_id), 213 + )]) 214 + .returning(users::id) // xmax = 0 if the row is new 215 + .get_result::<i32>(conn) 216 + .await 217 + { 218 + Ok(v) => return Ok(v), 219 + Err(diesel::result::Error::DatabaseError( 220 + diesel::result::DatabaseErrorKind::UniqueViolation, 221 + _, 222 + )) => {}, 223 + Err(e) => return Err(e.into()), 224 + } 225 + 226 + Ok(get_user().get_result::<i32>(conn).await?) 227 + } 228 + 229 + async fn get_or_create_file<'a>( 230 + &self, funcs: &'a crate::rpc::PushMetadata<'a>, 231 + ) -> Result<i32, anyhow::Error> { 232 + use schema::files::{chksum, id, table as files}; 233 + 234 + let hash = &funcs.input_md5[..]; 235 + 236 + let conn = &mut self.diesel.get().await?; 237 + 238 + let get_file = || files.filter(chksum.eq(hash)).select(id); 239 + 240 + match get_file().get_result::<i32>(conn).await { 241 + Ok(v) => return Ok(v), 242 + Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 243 + _ => {}, 244 + } 245 + 246 + match diesel::insert_into(files) 247 + .values(vec![(chksum.eq(hash),)]) 248 + .returning(id) 249 + .get_result::<i32>(conn) 250 + .await 251 + { 252 + Ok(v) => return Ok(v), 253 + Err(diesel::result::Error::DatabaseError( 254 + diesel::result::DatabaseErrorKind::UniqueViolation, 255 + _, 256 + )) => {}, 257 + Err(e) => return Err(e.into()), 258 + } 259 + Ok(get_file().get_result::<i32>(conn).await?) 260 + } 261 + 262 + async fn get_or_create_db<'a>( 263 + &self, user: &'a crate::rpc::RpcHello<'a>, cred_id: Option<i32>, 264 + funcs: &'a crate::rpc::PushMetadata<'a>, 265 + ) -> Result<i32, anyhow::Error> { 266 + use schema::dbs::{ 267 + file_id as db_file_id, file_path, id as db_id, idb_path, table as dbs, 268 + user_id as db_user, 269 + }; 270 + 271 + let file_id = self.get_or_create_file(funcs); 272 + let user_id = self.get_or_create_user(user, cred_id, funcs.hostname); 273 + 274 + let (file_id, user_id): (i32, i32) = futures_util::try_join!(file_id, user_id)?; 275 + 276 + let conn = &mut self.diesel.get().await?; 277 + 278 + let get_db = || { 279 + dbs.select(db_id) 280 + .filter(db_user.eq(user_id)) 281 + .filter(db_file_id.eq(file_id)) 282 + .filter(file_path.eq(funcs.input_path)) 283 + .filter(idb_path.eq(funcs.idb_path)) 284 + }; 285 + 286 + match get_db().get_result::<i32>(conn).await { 287 + Ok(v) => return Ok(v), 288 + Err(err) if err != diesel::result::Error::NotFound => return Err(err.into()), 289 + _ => {}, 290 + }; 291 + 292 + match diesel::insert_into(dbs) 293 + .values(vec![( 294 + db_user.eq(user_id), 295 + db_file_id.eq(file_id), 296 + file_path.eq(funcs.input_path), 297 + idb_path.eq(funcs.idb_path), 298 + )]) 299 + .returning(db_id) 300 + .get_result::<i32>(conn) 301 + .await 302 + { 303 + Ok(id) => return Ok(id), 304 + Err(diesel::result::Error::DatabaseError( 305 + diesel::result::DatabaseErrorKind::UniqueViolation, 306 + _, 307 + )) => {}, 308 + Err(e) => return Err(e.into()), 309 + }; 310 + Ok(get_db().get_result::<i32>(conn).await?) 311 + } 312 + 313 + pub async fn push_funcs<'a, 'b>( 314 + &'b self, user: &'a crate::rpc::RpcHello<'a>, cred_id: Option<i32>, 315 + funcs: &'a crate::rpc::PushMetadata<'a>, scores: &[u32], 316 + ) -> Result<Vec<bool>, anyhow::Error> { 317 + use futures_util::TryStreamExt; 318 + 319 + // postgres has a limitation of binding per statement (i16::MAX). Split large push requests into smaller chunks. 320 + const PUSH_FUNC_CHUNK_SIZE: usize = 3000; 321 + 322 + let db_id = self.get_or_create_db(user, cred_id, funcs).await?; 323 + 324 + let mut rows = Vec::with_capacity(funcs.funcs.len().min(PUSH_FUNC_CHUNK_SIZE)); 325 + let mut is_new = Vec::with_capacity(funcs.funcs.len()); 326 + let conn = &mut self.diesel.get().await?; 327 + let f2 = diesel::alias!(schema::funcs as f2); 328 + 329 + for (idx, (func, &score)) in funcs.funcs.iter().zip(scores.iter()).enumerate() { 330 + let name = func.name; 331 + let len = func.func_len as i32; 332 + let chksum = func.pattern_id.data; 333 + let md = func.func_data; 334 + let score = score as i32; 335 + 336 + rows.push(( 337 + schema::funcs::name.eq(name), 338 + schema::funcs::len.eq(len), 339 + schema::funcs::chksum.eq(chksum), 340 + schema::funcs::metadata.eq(md), 341 + schema::funcs::rank.eq(score), 342 + schema::funcs::db_id.eq(db_id), 343 + )); 344 + 345 + if rows.len() < PUSH_FUNC_CHUNK_SIZE && idx < funcs.funcs.len() - 1 { 346 + continue; 347 + } 348 + 349 + let mut current_rows = 350 + Vec::with_capacity((funcs.funcs.len() - (idx + 1)).max(PUSH_FUNC_CHUNK_SIZE)); 351 + std::mem::swap(&mut current_rows, &mut rows); 352 + 353 + diesel::insert_into(schema::funcs::table) 354 + .values(current_rows) 355 + .on_conflict((schema::funcs::chksum, schema::funcs::db_id)) 356 + .do_update() 357 + .set(( 358 + schema::funcs::name.eq(excluded(schema::funcs::name)), 359 + schema::funcs::metadata.eq(excluded(schema::funcs::metadata)), 360 + schema::funcs::rank.eq(excluded(schema::funcs::rank)), 361 + schema::funcs::update_dt.eq(diesel::dsl::now), 362 + )) 363 + .returning(diesel::dsl::not(diesel::dsl::exists( 364 + f2.filter(f2.field(schema::funcs::chksum).eq(schema::funcs::chksum)), 365 + ))) // xmax=0 when a new row is created. 366 + .load_stream::<bool>(conn) 367 + .await? 368 + .try_fold(&mut is_new, |acc, item: bool| { 369 + acc.push(item); 370 + futures_util::future::ready(Ok(acc)) 371 + }) 372 + .await?; 373 + } 374 + 375 + Ok(is_new) 376 + } 377 + 378 + pub async fn get_file_funcs( 379 + &self, md5: &[u8], offset: i64, limit: i64, 380 + ) -> Result<Vec<(String, i32, Vec<u8>)>, anyhow::Error> { 381 + let conn = &mut self.diesel.get().await?; 382 + let results = schema::funcs::table 383 + .left_join(schema::dbs::table.left_join(schema::files::table)) 384 + .select(( 385 + schema::funcs::name.assume_not_null(), 386 + schema::funcs::len.assume_not_null(), 387 + schema::funcs::chksum.assume_not_null(), 388 + )) 389 + .filter(schema::files::chksum.eq(md5)) 390 + .offset(offset) 391 + .limit(limit) 392 + .get_results::<(String, i32, Vec<u8>)>(conn) 393 + .await?; 394 + Ok(results) 395 + } 396 + 397 + pub async fn get_files_with_func(&self, func: &[u8]) -> Result<Vec<Vec<u8>>, anyhow::Error> { 398 + let conn = &mut self.diesel.get().await?; 399 + 400 + let res = schema::files::table 401 + .left_join(schema::dbs::table.left_join(schema::funcs::table)) 402 + .select(schema::files::chksum.assume_not_null()) 403 + .distinct() 404 + .filter(schema::funcs::chksum.eq(func)) 405 + .get_results::<Vec<u8>>(conn) 406 + .await?; 407 + Ok(res) 408 + } 409 + 410 + fn cancel_guard( 411 + &self, 412 + conn: &diesel_async::pooled_connection::bb8::PooledConnection< 413 + '_, 414 + diesel_async::AsyncPgConnection, 415 + >, 416 + ) -> AsyncDropGuard { 417 + let token = conn.cancel_token(); 418 + let tls_connector = self.tls_connector.clone(); 419 + self.dropper.defer(async move { 420 + debug!("cancelling query..."); 421 + 422 + if let Some(tls) = tls_connector { 423 + let _ = token.cancel_query(tls).await; 424 + } else { 425 + let _ = token.cancel_query(NoTls).await; 426 + } 427 + }) 428 + } 429 + 430 + pub async fn delete_metadata( 431 + &self, req: &crate::rpc::DelHistory<'_>, 432 + ) -> Result<(), anyhow::Error> { 433 + use schema::funcs::{chksum, table as funcs}; 434 + 435 + let chksums = req.calcrel_hashes.iter().map(|v| v.as_slice()).collect::<Vec<_>>(); 436 + 437 + let conn = &mut self.diesel.get().await?; 438 + let rows_modified = 439 + diesel::delete(funcs.filter(chksum.eq_any(&chksums))).execute(conn).await?; 440 + 441 + debug!("deleted {rows_modified} rows"); 442 + 443 + Ok(()) 444 + } 445 + 446 + pub async fn get_func_histories( 447 + &self, chksum: &[u8], limit: u32, 448 + ) -> Result<Vec<(OffsetDateTime, String, Vec<u8>)>, anyhow::Error> { 449 + let conn = &mut self.diesel.get().await?; 450 + let rows = &schema::funcs::table.select(( 451 + schema::funcs::update_dt.assume_not_null(), 452 + schema::funcs::name, 453 + schema::funcs::metadata.assume_not_null(), 454 + )); 455 + let rows = rows 456 + .limit(limit as i64) 457 + .order_by(schema::funcs::update_dt.desc()) 458 + .filter(schema::funcs::chksum.eq(chksum)) 459 + .get_results::<(time::OffsetDateTime, String, Vec<u8>)>(conn) 460 + .await?; 461 + Ok(rows) 462 + } 463 + 464 + pub async fn get_users(&self) -> Result<Vec<(i32, String, String, bool, bool)>, anyhow::Error> { 465 + let db = &mut self.diesel.get().await?; 466 + 467 + creds::table 468 + .select(( 469 + creds::id, 470 + creds::username, 471 + creds::email, 472 + creds::is_admin, 473 + creds::passwd_hash.is_not_null(), 474 + )) 475 + .get_results::<(i32, String, String, bool, bool)>(db) 476 + .await 477 + .map_err(|v| v.into()) 478 + } 479 + 480 + pub async fn delete_user(&self, username: &str) -> Result<(), anyhow::Error> { 481 + let db = &mut self.diesel.get().await?; 482 + 483 + let changes = 484 + diesel::delete(creds::table.filter(creds::username.eq(username))).execute(db).await?; 485 + if changes != 1 { 486 + return Err(anyhow::anyhow!("expected a single row deletion, got {changes}")); 487 + } 488 + Ok(()) 489 + } 490 + 491 + pub async fn set_password( 492 + &self, username: &str, password: String, iters: u32, 493 + ) -> Result<(), anyhow::Error> { 494 + let (salt, hash) = 495 + tokio::task::spawn_blocking(move || Creds::generate_creds(&password, iters)).await?; 496 + 497 + let db = &mut self.diesel.get().await?; 498 + let rows = diesel::update(creds::table) 499 + .filter(creds::username.eq(username)) 500 + .set(( 501 + creds::passwd_iters.eq(iters as i32), 502 + creds::passwd_salt.eq(&salt[..]), 503 + creds::passwd_hash.eq(&hash[..]), 504 + )) 505 + .execute(db) 506 + .await?; 507 + if rows != 1 { 508 + return Err(anyhow::anyhow!("updated {rows} rows, expected to update 1")); 509 + } 510 + Ok(()) 511 + } 512 + 513 + pub async fn add_user( 514 + &self, username: &str, email: &str, is_admin: bool, 515 + ) -> Result<u32, anyhow::Error> { 516 + let db = &mut self.diesel.get().await?; 517 + 518 + diesel::insert_into(creds::table) 519 + .values(&Creds { 520 + username: username.into(), 521 + email: email.into(), 522 + passwd_salt: None, 523 + passwd_iters: 10_000, 524 + passwd_hash: None, 525 + last_active: None, 526 + is_enabled: true, 527 + is_admin, 528 + }) 529 + .returning(creds::id) 530 + .get_result::<i32>(db) 531 + .await 532 + .map_err(|v| v.into()) 533 + .map(|v| v as u32) 534 + } 535 + 536 + pub async fn get_user_by_username( 537 + &self, username: &str, 538 + ) -> Result<(i32, Creds), anyhow::Error> { 539 + let db = &mut self.diesel.get().await?; 540 + 541 + Ok(creds::table 542 + .select((creds::id, Creds::as_select())) 543 + .filter(creds::username.eq(username)) 544 + .get_result::<(i32, Creds)>(db) 545 + .await?) 546 + } 547 + 548 + pub async fn get_user_by_id(&self, id: i32) -> Result<Creds, anyhow::Error> { 549 + let db = &mut self.diesel.get().await?; 550 + 551 + Ok(creds::table.select(Creds::as_select()).filter(creds::id.eq(id)).get_result(db).await?) 552 + } 553 + 554 + pub async fn update_last_active(&self, user_id: i32) -> Result<(), anyhow::Error> { 555 + let mut db = self.diesel.get().await?; 556 + diesel::update(schema::creds::table) 557 + .filter(schema::creds::id.eq(user_id)) 558 + .set((schema::creds::last_active.eq(time::OffsetDateTime::now_utc()),)) 559 + .execute(&mut db) 560 + .await?; 561 + Ok(()) 562 + } 563 + } 564 + 565 + // This is eww, but it's the fastest. 566 + struct BestMds<'a>(&'a [&'a [u8]]); 567 + impl<'a> QueryFragment<diesel::pg::Pg> for BestMds<'a> { 568 + fn walk_ast<'b>( 569 + &'b self, mut pass: diesel::query_builder::AstPass<'_, 'b, diesel::pg::Pg>, 570 + ) -> diesel::QueryResult<()> { 571 + pass.push_sql( 572 + r#"WITH best AS ( 573 + select chksum,MAX(rank) as maxrank from funcs f1 574 + WHERE chksum = ANY("#, 575 + ); 576 + pass.push_bind_param::<Array<Binary>, _>(&self.0)?; 577 + pass.push_sql( 578 + r#") 579 + GROUP BY chksum 580 + ) 581 + SELECT f2.name,f2.len,f2.metadata,f2.chksum FROM best 582 + LEFT JOIN funcs f2 ON (best.chksum=f2.chksum AND best.maxrank=f2.rank)"#, 583 + ); 584 + Ok(()) 585 + } 586 + } 587 + impl<'a> diesel::query_builder::QueryId for BestMds<'a> { 588 + type QueryId = BestMds<'static>; 589 + } 590 + impl<'a> Query for BestMds<'a> { 591 + type SqlType = (VarChar, Integer, Binary, Binary); 592 + }
+78
common/src/db/schema.rs
··· 1 + use std::borrow::Cow; 2 + 3 + use diesel::{Insertable, Queryable, Selectable}; 4 + use log::error; 5 + use pbkdf2::{hmac::Hmac, pbkdf2}; 6 + use sha2::Sha256; 7 + 1 8 pub use super::schema_auto::*; 2 9 3 10 diesel::table! { ··· 15 22 } 16 23 17 24 diesel::joinable!(func_ranks -> dbs (id)); 25 + 26 + #[derive(Insertable, Queryable, Selectable, Default)] 27 + #[diesel(table_name = creds)] 28 + pub struct Creds<'a> { 29 + pub username: Cow<'a, str>, 30 + pub email: Cow<'a, str>, 31 + 32 + pub passwd_salt: Option<Cow<'a, [u8]>>, 33 + pub passwd_iters: i32, 34 + pub passwd_hash: Option<Cow<'a, [u8]>>, 35 + 36 + pub last_active: Option<time::OffsetDateTime>, 37 + 38 + pub is_admin: bool, 39 + pub is_enabled: bool, 40 + } 41 + 42 + impl<'a> Creds<'a> { 43 + pub fn verify_password(&self, password: &str) -> bool { 44 + let salt = if let Some(v) = self.passwd_salt.as_ref() { 45 + v 46 + } else { 47 + return false; 48 + }; 49 + let current_hash = if let Some(v) = self.passwd_hash.as_ref() { 50 + v 51 + } else { 52 + return false; 53 + }; 54 + if self.passwd_iters <= 0 { 55 + return false; 56 + } 57 + 58 + let mut hash = vec![0u8; 32]; 59 + if pbkdf2::<Hmac<Sha256>>(password.as_bytes(), salt, self.passwd_iters as u32, &mut hash) 60 + .is_err() 61 + { 62 + error!("invalid output digest length"); 63 + return false; 64 + } 65 + 66 + hash == current_hash.as_ref() 67 + } 68 + 69 + pub(crate) fn generate_creds(password: &str, iters: u32) -> ([u8; 4], [u8; 32]) { 70 + let salt: [u8; 4] = rand::random(); 71 + let mut res = [0u8; 32]; 72 + pbkdf2::pbkdf2::<Hmac<Sha256>>(password.as_bytes(), &salt, iters, &mut res) 73 + .expect("failed to perform pbkdf2_hmac_sha256"); 74 + (salt, res) 75 + } 76 + } 77 + 78 + #[cfg(test)] 79 + mod tests { 80 + use super::*; 81 + 82 + #[test] 83 + fn generate_and_verify_password() { 84 + let password = "MyPassword1$"; 85 + let iters = 10_000; 86 + let (salt, hash) = Creds::generate_creds(password, iters); 87 + let creds = Creds { 88 + passwd_hash: Some((&hash[..]).into()), 89 + passwd_salt: Some((&salt[..]).into()), 90 + passwd_iters: iters as i32, 91 + ..Default::default() 92 + }; 93 + assert!(creds.verify_password(password), "failed to verify password"); 94 + } 95 + }
+23
common/src/db/schema_auto.rs
··· 1 1 // @generated automatically by Diesel CLI. 2 2 3 3 diesel::table! { 4 + creds (id) { 5 + id -> Int4, 6 + #[max_length = 256] 7 + username -> Varchar, 8 + #[max_length = 256] 9 + email -> Varchar, 10 + passwd_salt -> Nullable<Bytea>, 11 + passwd_iters -> Int4, 12 + passwd_hash -> Nullable<Bytea>, 13 + last_active -> Nullable<Timestamptz>, 14 + creation_dt -> Timestamptz, 15 + is_admin -> Bool, 16 + is_enabled -> Bool, 17 + } 18 + } 19 + 20 + diesel::table! { 4 21 dbs (id) { 5 22 id -> Int4, 23 + #[max_length = 260] 6 24 file_path -> Nullable<Varchar>, 25 + #[max_length = 260] 7 26 idb_path -> Nullable<Varchar>, 8 27 file_id -> Nullable<Int4>, 9 28 user_id -> Nullable<Int4>, ··· 36 55 id -> Int4, 37 56 lic_id -> Nullable<Bytea>, 38 57 lic_data -> Nullable<Bytea>, 58 + #[max_length = 260] 39 59 hostname -> Nullable<Varchar>, 40 60 first_seen -> Nullable<Timestamptz>, 61 + cred_id -> Nullable<Int4>, 41 62 } 42 63 } 43 64 44 65 diesel::joinable!(dbs -> files (file_id)); 45 66 diesel::joinable!(dbs -> users (user_id)); 46 67 diesel::joinable!(funcs -> dbs (db_id)); 68 + diesel::joinable!(users -> creds (cred_id)); 47 69 48 70 diesel::allow_tables_to_appear_in_same_query!( 71 + creds, 49 72 dbs, 50 73 files, 51 74 funcs,
+8 -4
common/src/lib.rs
··· 3 3 4 4 use std::fmt::Write; 5 5 6 + pub mod async_drop; 7 + pub mod config; 6 8 pub mod db; 7 - pub mod config; 8 9 pub mod md; 10 + pub mod metrics; 9 11 pub mod rpc; 10 12 pub mod web; 11 - pub mod async_drop; 12 - pub mod metrics; 13 13 14 14 pub struct SharedState_ { 15 15 pub db: db::Database, ··· 33 33 } 34 34 35 35 let _ = write!(&mut output, " | "); 36 - for ch in chunk.iter().chain(std::iter::repeat(&b' ').take(padding)).map(|&v| std::char::from_u32(v as u32).unwrap_or('.')) { 36 + for ch in chunk 37 + .iter() 38 + .chain(std::iter::repeat(&b' ').take(padding)) 39 + .map(|&v| std::char::from_u32(v as u32).unwrap_or('.')) 40 + { 37 41 if !ch.is_ascii_graphic() { 38 42 output.push('.'); 39 43 } else {
+218 -214
common/src/md.rs
··· 1 - use std::collections::HashSet; 2 - 3 - use serde::{Serialize, Deserialize}; 4 - use crate::rpc::de::from_slice; 5 - 6 - #[derive(Serialize, Deserialize)] 7 - pub struct MetadataChunk<'a> { 8 - code: u32, 9 - data: &'a [u8], 10 - } 11 - 12 - #[derive(Debug)] 13 - pub enum FunctionMetadata<'a> { 14 - FunctionComment(FunctionComment<'a>), 15 - ByteComment(ByteComment<'a>), 16 - ExtraComment(ExtraComment<'a>), 17 - } 18 - 19 - #[derive(Debug)] 20 - pub struct FunctionComment<'a> { 21 - pub is_repeatable: bool, 22 - pub comment: &'a str, 23 - } 24 - 25 - #[derive(Debug)] 26 - pub struct ByteComment<'a> { 27 - pub is_repeatable: bool, 28 - pub offset: u32, 29 - pub comment: &'a str, 30 - } 31 - 32 - #[derive(Debug)] 33 - pub struct ExtraComment<'a> { 34 - pub offset: u32, 35 - pub anterior: &'a str, 36 - pub posterior: &'a str, 37 - } 38 - 39 - impl<'a> FunctionMetadata<'a> { 40 - fn is_useful(&self) -> bool { 41 - // TODO: rewrite using regex with configurable library names 42 - match self { 43 - FunctionMetadata::ExtraComment(cmt) => { 44 - if cmt.anterior.starts_with("; Exported entry ") // offset=0 45 - { 46 - return false; 47 - } 48 - if cmt.anterior.is_empty() && cmt.posterior.is_empty() { 49 - return false; 50 - } 51 - }, 52 - FunctionMetadata::FunctionComment(cmt) => { 53 - if cmt.comment == "Microsoft VisualC v14 64bit runtime" 54 - || cmt.comment == "Microsoft VisualC 64bit universal runtime" 55 - { 56 - return false; 57 - } 58 - if cmt.comment.is_empty() { 59 - return false; 60 - } 61 - }, 62 - FunctionMetadata::ByteComment(cmt) => { 63 - if cmt.comment == "Trap to Debugger" 64 - || (cmt.comment.starts_with("jumptable ") && cmt.comment.contains(" case")) // repeatable=true 65 - || cmt.comment == "switch jump" 66 - || (cmt.comment.starts_with("switch ") && cmt.comment.ends_with(" cases ")) 67 - || cmt.comment == "jump table for switch statement" 68 - || cmt.comment == "indirect table for switch statement" 69 - || cmt.comment == "Microsoft VisualC v7/14 64bit runtime" 70 - || cmt.comment == "Microsoft VisualC v7/14 64bit runtime\nMicrosoft VisualC v14 64bit runtime" 71 - || cmt.comment == "Microsoft VisualC v14 64bit runtime" { 72 - return false; 73 - } 74 - if cmt.comment.is_empty() { 75 - return false; 76 - } 77 - }, 78 - } 79 - true 80 - } 81 - } 82 - 83 - fn deserialize_seq<'de, T: Deserialize<'de>>(mut data: &'de [u8]) -> Result<Vec<(u32, T)>, crate::rpc::Error> { 84 - let mut res = vec![]; 85 - let mut reset = true; 86 - let (mut offset, used): (u32, usize) = from_slice(data)?; 87 - data = &data[used..]; 88 - if data.is_empty() { 89 - return Err(crate::rpc::Error::UnexpectedEof); 90 - } 91 - 92 - loop { 93 - let (offset_diff, used): (u32, usize) = from_slice(data)?; 94 - data = &data[used..]; 95 - if data.is_empty() { 96 - return Err(crate::rpc::Error::UnexpectedEof); 97 - } 98 - 99 - if (offset_diff > 0) || reset { 100 - offset += offset_diff; 101 - let (e, used): (T, usize) = from_slice(data)?; 102 - data = &data[used..]; 103 - 104 - res.push((offset, e)); 105 - 106 - reset = false; 107 - } else { 108 - let (offset_diff, used): (u32, usize) = from_slice(data)?; 109 - data = &data[used..]; 110 - 111 - offset = offset_diff; 112 - reset = true; 113 - } 114 - 115 - if data.is_empty() { 116 - break; 117 - } 118 - } 119 - 120 - Ok(res) 121 - } 122 - 123 - pub fn parse_metadata(mut data: &[u8]) -> Result<Vec<FunctionMetadata<'_>>, crate::rpc::Error> { 124 - let mut res = vec![]; 125 - let mut bad_codes = HashSet::new(); 126 - 127 - while !data.is_empty() { 128 - let (chunk, used) :(MetadataChunk, _) = from_slice(data)?; 129 - data = &data[used..]; 130 - 131 - let data = chunk.data; 132 - 133 - if data.is_empty() { 134 - continue; 135 - } 136 - 137 - match chunk.code { 138 - 1 => {}, // TODO: parse typeinfo 139 - 2 => {}, // nop 140 - 3 | 4 => { // function comments 141 - let is_repeatable = chunk.code == 4; 142 - let cmt = std::str::from_utf8(data)?; 143 - res.push(FunctionMetadata::FunctionComment(FunctionComment{ 144 - is_repeatable, 145 - comment: cmt, 146 - })); 147 - }, 148 - 5 | 6 => { // comments 149 - let is_repeatable = chunk.code == 6; 150 - let byte_comments: Vec<(_, &[u8])> = match deserialize_seq(data) { 151 - Ok(v) => v, 152 - Err(err) => { 153 - log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 154 - return Err(err); 155 - }, 156 - }; 157 - 158 - for comment in byte_comments { 159 - let cmt = std::str::from_utf8(comment.1)?; 160 - res.push(FunctionMetadata::ByteComment( 161 - ByteComment{ 162 - is_repeatable, 163 - offset: comment.0, 164 - comment: cmt, 165 - } 166 - )); 167 - } 168 - }, 169 - 7 => { // extra comments 170 - let byte_comments: Vec<(_, (&[u8], &[u8]))> = match deserialize_seq(data) { 171 - Ok(v) => v, 172 - Err(err) => { 173 - log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 174 - return Err(err); 175 - }, 176 - }; 177 - 178 - for comment in byte_comments { 179 - res.push(FunctionMetadata::ExtraComment(ExtraComment{ 180 - offset: comment.0, 181 - anterior: std::str::from_utf8(comment.1.0)?, 182 - posterior: std::str::from_utf8(comment.1.1)?, 183 - })); 184 - } 185 - }, 186 - 9 | 10 => { /* TODO! */ }, 187 - _ => { 188 - bad_codes.insert(chunk.code); 189 - }, 190 - } 191 - } 192 - 193 - Ok(res) 194 - } 195 - 196 - pub fn get_score(md: &crate::rpc::PushMetadataFunc) -> u32 { 197 - let mut score = 0; 198 - 199 - let md = match parse_metadata(md.func_data) { 200 - Ok(v) => v, 201 - Err(e) => { 202 - log::warn!("failed to parse metadata: {}", e); 203 - return 0; 204 - } 205 - }; 206 - 207 - for md in md { 208 - if md.is_useful() { 209 - score += 10; 210 - } 211 - } 212 - 213 - score 214 - } 1 + use std::collections::HashSet; 2 + 3 + use crate::rpc::de::from_slice; 4 + use serde::{Deserialize, Serialize}; 5 + 6 + #[derive(Serialize, Deserialize)] 7 + pub struct MetadataChunk<'a> { 8 + code: u32, 9 + data: &'a [u8], 10 + } 11 + 12 + #[derive(Debug)] 13 + pub enum FunctionMetadata<'a> { 14 + FunctionComment(FunctionComment<'a>), 15 + ByteComment(ByteComment<'a>), 16 + ExtraComment(ExtraComment<'a>), 17 + } 18 + 19 + #[derive(Debug)] 20 + pub struct FunctionComment<'a> { 21 + pub is_repeatable: bool, 22 + pub comment: &'a str, 23 + } 24 + 25 + #[derive(Debug)] 26 + pub struct ByteComment<'a> { 27 + pub is_repeatable: bool, 28 + pub offset: u32, 29 + pub comment: &'a str, 30 + } 31 + 32 + #[derive(Debug)] 33 + pub struct ExtraComment<'a> { 34 + pub offset: u32, 35 + pub anterior: &'a str, 36 + pub posterior: &'a str, 37 + } 38 + 39 + impl<'a> FunctionMetadata<'a> { 40 + fn is_useful(&self) -> bool { 41 + // TODO: rewrite using regex with configurable library names 42 + match self { 43 + FunctionMetadata::ExtraComment(cmt) => { 44 + if cmt.anterior.starts_with("; Exported entry ") 45 + // offset=0 46 + { 47 + return false; 48 + } 49 + if cmt.anterior.is_empty() && cmt.posterior.is_empty() { 50 + return false; 51 + } 52 + }, 53 + FunctionMetadata::FunctionComment(cmt) => { 54 + if cmt.comment == "Microsoft VisualC v14 64bit runtime" 55 + || cmt.comment == "Microsoft VisualC 64bit universal runtime" 56 + { 57 + return false; 58 + } 59 + if cmt.comment.is_empty() { 60 + return false; 61 + } 62 + }, 63 + FunctionMetadata::ByteComment(cmt) => { 64 + if cmt.comment == "Trap to Debugger" 65 + || (cmt.comment.starts_with("jumptable ") && cmt.comment.contains(" case")) // repeatable=true 66 + || cmt.comment == "switch jump" 67 + || (cmt.comment.starts_with("switch ") && cmt.comment.ends_with(" cases ")) 68 + || cmt.comment == "jump table for switch statement" 69 + || cmt.comment == "indirect table for switch statement" 70 + || cmt.comment == "Microsoft VisualC v7/14 64bit runtime" 71 + || cmt.comment == "Microsoft VisualC v7/14 64bit runtime\nMicrosoft VisualC v14 64bit runtime" 72 + || cmt.comment == "Microsoft VisualC v14 64bit runtime" { 73 + return false; 74 + } 75 + if cmt.comment.is_empty() { 76 + return false; 77 + } 78 + }, 79 + } 80 + true 81 + } 82 + } 83 + 84 + fn deserialize_seq<'de, T: Deserialize<'de>>( 85 + mut data: &'de [u8], 86 + ) -> Result<Vec<(u32, T)>, crate::rpc::Error> { 87 + let mut res = vec![]; 88 + let mut reset = true; 89 + let (mut offset, used): (u32, usize) = from_slice(data)?; 90 + data = &data[used..]; 91 + if data.is_empty() { 92 + return Err(crate::rpc::Error::UnexpectedEof); 93 + } 94 + 95 + loop { 96 + let (offset_diff, used): (u32, usize) = from_slice(data)?; 97 + data = &data[used..]; 98 + if data.is_empty() { 99 + return Err(crate::rpc::Error::UnexpectedEof); 100 + } 101 + 102 + if (offset_diff > 0) || reset { 103 + offset += offset_diff; 104 + let (e, used): (T, usize) = from_slice(data)?; 105 + data = &data[used..]; 106 + 107 + res.push((offset, e)); 108 + 109 + reset = false; 110 + } else { 111 + let (offset_diff, used): (u32, usize) = from_slice(data)?; 112 + data = &data[used..]; 113 + 114 + offset = offset_diff; 115 + reset = true; 116 + } 117 + 118 + if data.is_empty() { 119 + break; 120 + } 121 + } 122 + 123 + Ok(res) 124 + } 125 + 126 + pub fn parse_metadata(mut data: &[u8]) -> Result<Vec<FunctionMetadata<'_>>, crate::rpc::Error> { 127 + let mut res = vec![]; 128 + let mut bad_codes = HashSet::new(); 129 + 130 + while !data.is_empty() { 131 + let (chunk, used): (MetadataChunk, _) = from_slice(data)?; 132 + data = &data[used..]; 133 + 134 + let data = chunk.data; 135 + 136 + if data.is_empty() { 137 + continue; 138 + } 139 + 140 + match chunk.code { 141 + 1 => {}, // TODO: parse typeinfo 142 + 2 => {}, // nop 143 + 3 | 4 => { 144 + // function comments 145 + let is_repeatable = chunk.code == 4; 146 + let cmt = std::str::from_utf8(data)?; 147 + res.push(FunctionMetadata::FunctionComment(FunctionComment { 148 + is_repeatable, 149 + comment: cmt, 150 + })); 151 + }, 152 + 5 | 6 => { 153 + // comments 154 + let is_repeatable = chunk.code == 6; 155 + let byte_comments: Vec<(_, &[u8])> = match deserialize_seq(data) { 156 + Ok(v) => v, 157 + Err(err) => { 158 + log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 159 + return Err(err); 160 + }, 161 + }; 162 + 163 + for comment in byte_comments { 164 + let cmt = std::str::from_utf8(comment.1)?; 165 + res.push(FunctionMetadata::ByteComment(ByteComment { 166 + is_repeatable, 167 + offset: comment.0, 168 + comment: cmt, 169 + })); 170 + } 171 + }, 172 + 7 => { 173 + // extra comments 174 + let byte_comments: Vec<(_, (&[u8], &[u8]))> = match deserialize_seq(data) { 175 + Ok(v) => v, 176 + Err(err) => { 177 + log::error!("err: {}\n{}", err, super::make_pretty_hex(data)); 178 + return Err(err); 179 + }, 180 + }; 181 + 182 + for comment in byte_comments { 183 + res.push(FunctionMetadata::ExtraComment(ExtraComment { 184 + offset: comment.0, 185 + anterior: std::str::from_utf8(comment.1 .0)?, 186 + posterior: std::str::from_utf8(comment.1 .1)?, 187 + })); 188 + } 189 + }, 190 + 9 | 10 => { /* TODO! */ }, 191 + _ => { 192 + bad_codes.insert(chunk.code); 193 + }, 194 + } 195 + } 196 + 197 + Ok(res) 198 + } 199 + 200 + pub fn get_score(md: &crate::rpc::PushMetadataFunc) -> u32 { 201 + let mut score = 0; 202 + 203 + let md = match parse_metadata(md.func_data) { 204 + Ok(v) => v, 205 + Err(e) => { 206 + log::warn!("failed to parse metadata: {}", e); 207 + return 0; 208 + }, 209 + }; 210 + 211 + for md in md { 212 + if md.is_useful() { 213 + score += 10; 214 + } 215 + } 216 + 217 + score 218 + }
+20 -4
common/src/metrics.rs
··· 1 1 use std::sync::atomic::AtomicI64; 2 2 3 - use prometheus_client::{registry::Registry, metrics::{gauge::Gauge, family::Family, counter::Counter}, encoding::EncodeLabelSet}; 3 + use prometheus_client::{ 4 + encoding::EncodeLabelSet, 5 + metrics::{counter::Counter, family::Family, gauge::Gauge}, 6 + registry::Registry, 7 + }; 4 8 5 9 pub struct Metrics { 6 10 pub registry: Registry, ··· 34 38 let mut registry = Registry::default(); 35 39 36 40 let active_connections = Gauge::default(); 37 - registry.register("lumen_active_connections", "Active Lumina connections", active_connections.clone()); 41 + registry.register( 42 + "lumen_active_connections", 43 + "Active Lumina connections", 44 + active_connections.clone(), 45 + ); 38 46 39 47 let lumina_version = Family::<LuminaVersion, Gauge>::default(); 40 - registry.register("lumen_protocol_version", "Version of Lumina protocol being used", lumina_version.clone()); 48 + registry.register( 49 + "lumen_protocol_version", 50 + "Version of Lumina protocol being used", 51 + lumina_version.clone(), 52 + ); 41 53 42 54 let new_funcs = Counter::default(); 43 - registry.register("lumen_new_funcs", "Pushes previously unknown functions", new_funcs.clone()); 55 + registry.register( 56 + "lumen_new_funcs", 57 + "Pushes previously unknown functions", 58 + new_funcs.clone(), 59 + ); 44 60 45 61 let pushes = Counter::default(); 46 62 registry.register("lumen_pushes_total", "Total pushes functions", pushes.clone());
+163 -159
common/src/rpc/de.rs
··· 1 - use serde::Deserialize; 2 - use serde::de::{self, DeserializeSeed, SeqAccess, Visitor}; 3 - use super::Error; 4 - 5 - struct Deserializer<'de> { 6 - input: &'de [u8], 7 - } 8 - 9 - impl<'de> Deserializer<'de> { 10 - fn from_bytes(b: &'de [u8]) -> Self { 11 - Self { 12 - input: b, 13 - } 14 - } 15 - 16 - fn unpack_dd(&mut self) -> Result<u32, Error> { 17 - let (v, len) = super::packing::unpack_dd(self.input); 18 - if len == 0 { 19 - Err(Error::UnexpectedEof) 20 - } else { 21 - self.input = &self.input[len..]; 22 - Ok(v) 23 - } 24 - } 25 - 26 - fn unpack_dq(&mut self) -> Result<u64, Error> { 27 - let a = self.unpack_dd()? as u64; 28 - let b = self.unpack_dd()? as u64; 29 - Ok((a << 32) | b) 30 - } 31 - 32 - fn unpack_var_bytes(&mut self) -> Result<&'de [u8], Error> { 33 - let bytes = self.unpack_dd()? as usize; 34 - if bytes > self.input.len() { 35 - return Err(Error::UnexpectedEof); 36 - } 37 - 38 - let payload = &self.input[..bytes]; 39 - self.input = &self.input[bytes..]; 40 - assert_eq!(payload.len(), bytes); 41 - 42 - Ok(payload) 43 - } 44 - 45 - fn unpack_cstr(&mut self) -> Result<&'de str, Error> { 46 - let len = self.input.iter().enumerate().find_map(|(idx, &v)| if v == 0 { Some(idx) } else { None }); 47 - let len = match len { 48 - Some(v) => v, 49 - None => return Err(Error::UnexpectedEof), 50 - }; 51 - let res = match std::str::from_utf8(&self.input[..len]) { 52 - Ok(v) => v, 53 - Err(err) => { 54 - return Err(err.into()); 55 - } 56 - }; 57 - self.input = &self.input[len + 1..]; 58 - Ok(res) 59 - } 60 - 61 - fn take_byte(&mut self) -> Result<u8, Error> { 62 - if self.input.is_empty() { 63 - return Err(Error::UnexpectedEof); 64 - } 65 - 66 - let v = self.input[0]; 67 - self.input = &self.input[1..]; 68 - Ok(v) 69 - } 70 - } 71 - 72 - /// Returns: a tuple containing the deserialized struct and the bytes used 73 - pub fn from_slice<'a, T: Deserialize<'a>>(b: &'a [u8]) -> Result<(T, usize), Error> { 74 - let mut de = Deserializer::from_bytes(b); 75 - let v = T::deserialize(&mut de)?; 76 - Ok((v, b.len() - de.input.len())) 77 - } 78 - 79 - impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { 80 - type Error = Error; 81 - 82 - fn deserialize_any<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Self::Error> { 83 - unimplemented!() 84 - } 85 - 86 - fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 87 - visitor.visit_u8(self.take_byte()?) 88 - } 89 - 90 - fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 91 - visitor.visit_u32(self.unpack_dd()?) 92 - } 93 - 94 - fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 95 - visitor.visit_u64(self.unpack_dq()?) 96 - } 97 - 98 - fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 99 - let len = self.unpack_dd()?; 100 - 101 - visitor.visit_seq(Access { 102 - len: len as usize, 103 - de: &mut *self, 104 - }) 105 - } 106 - 107 - fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 108 - visitor.visit_borrowed_str(self.unpack_cstr()?) 109 - } 110 - 111 - fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 112 - let v = self.unpack_var_bytes()?; 113 - visitor.visit_borrowed_bytes(v) 114 - } 115 - 116 - fn deserialize_tuple<V: Visitor<'de>>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error> { 117 - visitor.visit_seq(Access { 118 - len, 119 - de: &mut *self, 120 - }) 121 - } 122 - 123 - fn deserialize_tuple_struct<V: Visitor<'de>>(self, _name: &'static str, len: usize, visitor: V) -> Result<V::Value, Self::Error> { 124 - self.deserialize_tuple(len, visitor) 125 - } 126 - 127 - fn deserialize_struct<V: Visitor<'de>>(self, name: &'static str, fields: &'static [&'static str], visitor: V) -> Result<V::Value, Self::Error> { 128 - self.deserialize_tuple_struct(name, fields.len(), visitor) 129 - } 130 - 131 - serde::forward_to_deserialize_any! { 132 - i8 i16 i32 i64 char u16 bool 133 - f32 f64 string byte_buf option unit unit_struct newtype_struct map enum identifier ignored_any 134 - } 135 - } 136 - 137 - struct Access<'a, 'de> { 138 - de: &'a mut Deserializer<'de>, 139 - len: usize, 140 - } 141 - 142 - impl<'de, 'a> SeqAccess<'a> for Access<'de, 'a> { 143 - type Error = Error; 144 - 145 - fn next_element_seed<T: DeserializeSeed<'a>>(&mut self, seed: T) -> Result<Option<T::Value>, Self::Error> { 146 - if self.len > 0 { 147 - self.len -= 1; 148 - 149 - let v = serde::de::DeserializeSeed::deserialize(seed, &mut *self.de)?; 150 - Ok(Some(v)) 151 - } else { 152 - Ok(None) 153 - } 154 - } 155 - 156 - fn size_hint(&self) -> Option<usize> { 157 - Some(self.len) 158 - } 159 - } 1 + use super::Error; 2 + use serde::de::{self, DeserializeSeed, SeqAccess, Visitor}; 3 + use serde::Deserialize; 4 + 5 + struct Deserializer<'de> { 6 + input: &'de [u8], 7 + } 8 + 9 + impl<'de> Deserializer<'de> { 10 + fn from_bytes(b: &'de [u8]) -> Self { 11 + Self { input: b } 12 + } 13 + 14 + fn unpack_dd(&mut self) -> Result<u32, Error> { 15 + let (v, len) = super::packing::unpack_dd(self.input); 16 + if len == 0 { 17 + Err(Error::UnexpectedEof) 18 + } else { 19 + self.input = &self.input[len..]; 20 + Ok(v) 21 + } 22 + } 23 + 24 + fn unpack_dq(&mut self) -> Result<u64, Error> { 25 + let a = self.unpack_dd()? as u64; 26 + let b = self.unpack_dd()? as u64; 27 + Ok((a << 32) | b) 28 + } 29 + 30 + fn unpack_var_bytes(&mut self) -> Result<&'de [u8], Error> { 31 + let bytes = self.unpack_dd()? as usize; 32 + if bytes > self.input.len() { 33 + return Err(Error::UnexpectedEof); 34 + } 35 + 36 + let payload = &self.input[..bytes]; 37 + self.input = &self.input[bytes..]; 38 + assert_eq!(payload.len(), bytes); 39 + 40 + Ok(payload) 41 + } 42 + 43 + fn unpack_cstr(&mut self) -> Result<&'de str, Error> { 44 + let len = self 45 + .input 46 + .iter() 47 + .enumerate() 48 + .find_map(|(idx, &v)| if v == 0 { Some(idx) } else { None }); 49 + let len = match len { 50 + Some(v) => v, 51 + None => return Err(Error::UnexpectedEof), 52 + }; 53 + let res = match std::str::from_utf8(&self.input[..len]) { 54 + Ok(v) => v, 55 + Err(err) => { 56 + return Err(err.into()); 57 + }, 58 + }; 59 + self.input = &self.input[len + 1..]; 60 + Ok(res) 61 + } 62 + 63 + fn take_byte(&mut self) -> Result<u8, Error> { 64 + if self.input.is_empty() { 65 + return Err(Error::UnexpectedEof); 66 + } 67 + 68 + let v = self.input[0]; 69 + self.input = &self.input[1..]; 70 + Ok(v) 71 + } 72 + } 73 + 74 + /// Returns: a tuple containing the deserialized struct and the bytes used 75 + pub fn from_slice<'a, T: Deserialize<'a>>(b: &'a [u8]) -> Result<(T, usize), Error> { 76 + let mut de = Deserializer::from_bytes(b); 77 + let v = T::deserialize(&mut de)?; 78 + Ok((v, b.len() - de.input.len())) 79 + } 80 + 81 + impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { 82 + type Error = Error; 83 + 84 + fn deserialize_any<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Self::Error> { 85 + unimplemented!() 86 + } 87 + 88 + fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 89 + visitor.visit_u8(self.take_byte()?) 90 + } 91 + 92 + fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 93 + visitor.visit_u32(self.unpack_dd()?) 94 + } 95 + 96 + fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 97 + visitor.visit_u64(self.unpack_dq()?) 98 + } 99 + 100 + fn deserialize_seq<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 101 + let len = self.unpack_dd()?; 102 + 103 + visitor.visit_seq(Access { len: len as usize, de: &mut *self }) 104 + } 105 + 106 + fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 107 + visitor.visit_borrowed_str(self.unpack_cstr()?) 108 + } 109 + 110 + fn deserialize_bytes<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> { 111 + let v = self.unpack_var_bytes()?; 112 + visitor.visit_borrowed_bytes(v) 113 + } 114 + 115 + fn deserialize_tuple<V: Visitor<'de>>( 116 + self, len: usize, visitor: V, 117 + ) -> Result<V::Value, Self::Error> { 118 + visitor.visit_seq(Access { len, de: &mut *self }) 119 + } 120 + 121 + fn deserialize_tuple_struct<V: Visitor<'de>>( 122 + self, _name: &'static str, len: usize, visitor: V, 123 + ) -> Result<V::Value, Self::Error> { 124 + self.deserialize_tuple(len, visitor) 125 + } 126 + 127 + fn deserialize_struct<V: Visitor<'de>>( 128 + self, name: &'static str, fields: &'static [&'static str], visitor: V, 129 + ) -> Result<V::Value, Self::Error> { 130 + self.deserialize_tuple_struct(name, fields.len(), visitor) 131 + } 132 + 133 + serde::forward_to_deserialize_any! { 134 + i8 i16 i32 i64 char u16 bool 135 + f32 f64 string byte_buf option unit unit_struct newtype_struct map enum identifier ignored_any 136 + } 137 + } 138 + 139 + struct Access<'a, 'de> { 140 + de: &'a mut Deserializer<'de>, 141 + len: usize, 142 + } 143 + 144 + impl<'de, 'a> SeqAccess<'a> for Access<'de, 'a> { 145 + type Error = Error; 146 + 147 + fn next_element_seed<T: DeserializeSeed<'a>>( 148 + &mut self, seed: T, 149 + ) -> Result<Option<T::Value>, Self::Error> { 150 + if self.len > 0 { 151 + self.len -= 1; 152 + 153 + let v = serde::de::DeserializeSeed::deserialize(seed, &mut *self.de)?; 154 + Ok(Some(v)) 155 + } else { 156 + Ok(None) 157 + } 158 + } 159 + 160 + fn size_hint(&self) -> Option<usize> { 161 + Some(self.len) 162 + } 163 + }
+153 -118
common/src/rpc/messages.rs
··· 1 - use serde::{Serialize, Deserialize}; 2 - use std::borrow::Cow; 3 - 4 - #[derive(Deserialize, Serialize)] 5 - pub struct RpcFail<'a> { 6 - pub code: u32, 7 - pub message: &'a str, 8 - } 9 - 10 - #[derive(Serialize, Deserialize)] 11 - pub struct RpcNotify<'a> { 12 - pub code: u32, 13 - pub msg: &'a str, 14 - } 15 - 16 - #[derive(Serialize, Deserialize, Debug)] 17 - pub struct Creds<'a> { 18 - pub username: &'a str, 19 - pub password: &'a str, 20 - } 21 - 22 - #[derive(Serialize, Deserialize)] 23 - pub struct RpcHello<'a> { 24 - pub protocol_version: u32, 25 - pub license_data: &'a [u8], 26 - pub lic_number: [u8; 6], 27 - pub unk2: u32, 28 - } 29 - 30 - #[derive(Deserialize, Serialize, Clone)] 31 - pub struct PullMetadataFunc<'a> { 32 - pub unk0: u32, 33 - pub mb_hash: &'a [u8], 34 - } 35 - 36 - #[derive(Deserialize, Serialize)] 37 - pub struct PullMetadata<'a> { 38 - pub unk0: u32, 39 - pub unk1: Cow<'a, [u32]>, 40 - 41 - #[serde(borrow)] 42 - pub funcs: Cow<'a, [PullMetadataFunc<'a>]>, 43 - } 44 - 45 - #[derive(Deserialize, Serialize, Clone)] 46 - pub struct PullMetadataResultFunc<'a> { 47 - pub name: Cow<'a, str>, 48 - pub len: u32, 49 - pub mb_data: Cow<'a, [u8]>, 50 - pub popularity: u32, 51 - } 52 - 53 - #[derive(Deserialize, Serialize)] 54 - pub struct PullMetadataResult<'a> { 55 - pub unk0: Cow<'a, [u32]>, 56 - #[serde(borrow)] 57 - pub funcs: Cow<'a, [PullMetadataResultFunc<'a>]>, 58 - } 59 - 60 - #[derive(Clone, Deserialize, Serialize)] 61 - pub struct PushMetadataFunc<'a> { 62 - pub name: &'a str, 63 - pub func_len: u32, 64 - pub func_data: &'a [u8], 65 - 66 - // PullMetadata's fields (tuple 'unk2') are similar to these two 67 - pub unk2: u32, 68 - pub hash: &'a [u8], 69 - } 70 - 71 - #[derive(Deserialize, Serialize)] 72 - pub struct PushMetadata<'a> { 73 - pub unk0: u32, 74 - pub idb_path: &'a str, 75 - pub file_path: &'a str, 76 - pub md5: [u8; 16], 77 - pub hostname: &'a str, 78 - pub funcs: Cow<'a, [PushMetadataFunc<'a>]>, 79 - pub unk1: Cow<'a, [u64]>, 80 - } 81 - 82 - #[derive(Deserialize, Serialize)] 83 - pub struct PushMetadataResult<'a> { 84 - // array of 0=exists, 1=NEW 85 - pub status: Cow<'a, [u32]>, 86 - } 87 - 88 - #[derive(Debug, Deserialize, Serialize)] 89 - pub struct DelHistory<'a> { 90 - pub unk0: u32, // =0x08 91 - pub unk1: Cow<'a, [Cow<'a, str>]>, 92 - pub unk2: Cow<'a, [[u64; 2]]>, 93 - pub unk3: Cow<'a, [[u64; 2]]>, 94 - pub unk4: Cow<'a, [Cow<'a, str>]>, 95 - pub unk5: Cow<'a, [Cow<'a, str>]>, 96 - pub unk6: Cow<'a, [Cow<'a, str>]>, 97 - pub unk7: Cow<'a, [Cow<'a, str>]>, 98 - pub unk8: Cow<'a, [Cow<'a, [u8; 16]>]>, 99 - pub funcs: Cow<'a, [Cow<'a, [u8; 16]>]>, 100 - pub unk10: Cow<'a, [[u64; 2]]>, 101 - pub unk11: u64, 102 - } 103 - 104 - #[derive(Deserialize, Serialize)] 105 - pub struct DelHistoryResult { 106 - pub deleted_mds: u32, 107 - } 108 - 109 - #[derive(Debug, Deserialize, Serialize)] 110 - pub struct HelloResult<'a> { 111 - pub unk0: Cow<'a, str>, 112 - pub unk1: Cow<'a, str>, 113 - pub unk2: Cow<'a, str>, 114 - pub unk3: Cow<'a, str>, 115 - pub unk4: u32, 116 - pub unk5: u64, 117 - pub unk6: u32, 118 - } 1 + use serde::{Deserialize, Serialize}; 2 + use std::borrow::Cow; 3 + 4 + #[derive(Deserialize, Serialize)] 5 + pub struct RpcFail<'a> { 6 + pub result: u32, 7 + pub error: &'a str, 8 + } 9 + 10 + #[derive(Serialize, Deserialize)] 11 + pub struct RpcNotify<'a> { 12 + pub ty: u32, 13 + pub text: &'a str, 14 + } 15 + 16 + #[derive(Serialize, Deserialize, Debug)] 17 + pub struct Creds<'a> { 18 + pub username: &'a str, 19 + pub password: &'a str, 20 + } 21 + 22 + #[derive(Serialize, Deserialize)] 23 + pub struct RpcHello<'a> { 24 + pub client_version: u32, 25 + pub license_data: &'a [u8], 26 + pub lic_number: [u8; 6], 27 + pub record_conv: u32, 28 + } 29 + 30 + #[derive(Debug, Deserialize, Serialize, Clone)] 31 + pub struct PatternId<'a> { 32 + pub ty: u32, 33 + pub data: &'a [u8], 34 + } 35 + 36 + #[derive(Deserialize, Serialize)] 37 + pub struct PullMetadata<'a> { 38 + pub flags: u32, 39 + pub keys: Cow<'a, [u32]>, 40 + 41 + #[serde(borrow)] 42 + pub pattern_ids: Cow<'a, [PatternId<'a>]>, 43 + } 44 + 45 + #[derive(Deserialize, Serialize, Clone)] 46 + pub struct PullMetadataResultFunc<'a> { 47 + pub name: Cow<'a, str>, 48 + pub len: u32, 49 + pub mb_data: Cow<'a, [u8]>, 50 + pub popularity: u32, 51 + } 52 + 53 + #[derive(Deserialize, Serialize)] 54 + pub struct PullMetadataResult<'a> { 55 + pub codes: Cow<'a, [u32]>, 56 + #[serde(borrow)] 57 + pub funcs: Cow<'a, [PullMetadataResultFunc<'a>]>, 58 + } 59 + 60 + #[derive(Clone, Deserialize, Serialize)] 61 + pub struct PushMetadataFunc<'a> { 62 + pub name: &'a str, 63 + pub func_len: u32, 64 + pub func_data: &'a [u8], 65 + pub pattern_id: PatternId<'a>, 66 + } 67 + 68 + #[derive(Deserialize, Serialize)] 69 + pub struct PushMetadata<'a> { 70 + pub flags: u32, 71 + pub idb_path: &'a str, 72 + pub input_path: &'a str, 73 + pub input_md5: [u8; 16], 74 + pub hostname: &'a str, 75 + pub funcs: Cow<'a, [PushMetadataFunc<'a>]>, 76 + pub ea64s: Cow<'a, [u64]>, 77 + } 78 + 79 + #[derive(Deserialize, Serialize)] 80 + pub struct PushMetadataResult<'a> { 81 + // array of 0=exists, 1=NEW 82 + pub status: Cow<'a, [u32]>, 83 + } 84 + 85 + #[derive(Debug, Deserialize, Serialize)] 86 + pub struct DelHistory<'a> { 87 + pub flags: u32, // =0x08 88 + pub license_ids: Cow<'a, [Cow<'a, str>]>, 89 + pub time_ranges: Cow<'a, [[u64; 2]]>, 90 + pub history_id_ranges: Cow<'a, [[u64; 2]]>, 91 + pub idbs: Cow<'a, [Cow<'a, str>]>, 92 + pub inputs: Cow<'a, [Cow<'a, str>]>, 93 + pub funcs: Cow<'a, [Cow<'a, str>]>, // funcs 94 + pub usernames: Cow<'a, [Cow<'a, str>]>, 95 + pub input_hashes: Cow<'a, [Cow<'a, [u8; 16]>]>, 96 + pub calcrel_hashes: Cow<'a, [Cow<'a, [u8; 16]>]>, 97 + pub push_id_ranges: Cow<'a, [[u64; 2]]>, 98 + pub max_entries: u64, 99 + } 100 + 101 + #[derive(Deserialize, Serialize)] 102 + pub struct DelHistoryResult { 103 + pub ndeleted: u32, 104 + } 105 + 106 + #[derive(Debug, Deserialize, Serialize, Default)] 107 + pub struct LicenseInfo<'a> { 108 + pub id: Cow<'a, str>, 109 + pub name: Cow<'a, str>, 110 + pub email: Cow<'a, str>, 111 + } 112 + 113 + #[derive(Debug, Deserialize, Serialize, Default)] 114 + pub struct HelloResult<'a> { 115 + pub license_info: LicenseInfo<'a>, 116 + pub username: Cow<'a, str>, 117 + pub karma: u32, 118 + pub last_active: u64, 119 + pub features: u32, 120 + } 121 + 122 + #[derive(Debug, Deserialize, Serialize)] 123 + pub struct GetFuncHistories<'a> { 124 + #[serde(borrow)] 125 + pub funcs: Cow<'a, [PatternId<'a>]>, 126 + pub flags: u32, 127 + } 128 + 129 + #[derive(Debug, Deserialize, Serialize, Clone)] 130 + pub struct FunctionHistory<'a> { 131 + pub unk0: u64, 132 + pub unk1: u64, 133 + pub name: Cow<'a, str>, 134 + pub metadata: Cow<'a, [u8]>, 135 + pub timestamp: u64, 136 + pub author_idx: u32, 137 + pub idb_path_idx: u32, 138 + } 139 + 140 + #[derive(Debug, Deserialize, Serialize, Clone)] 141 + pub struct FunctionHistories<'a> { 142 + #[serde(borrow)] 143 + pub log: Cow<'a, [FunctionHistory<'a>]>, 144 + } 145 + 146 + #[derive(Debug, Deserialize, Serialize)] 147 + pub struct GetFuncHistoriesResult<'a> { 148 + pub status: Cow<'a, [u32]>, 149 + #[serde(borrow)] 150 + pub funcs: Cow<'a, [FunctionHistories<'a>]>, 151 + pub authors: Cow<'a, [Cow<'a, str>]>, 152 + pub idb_paths: Cow<'a, [Cow<'a, str>]>, 153 + }
+281 -234
common/src/rpc/mod.rs
··· 1 - use tokio::io::{AsyncWriteExt, AsyncWrite, AsyncRead, AsyncReadExt}; 2 - use log::*; 3 - mod ser; 4 - pub(crate) mod de; 5 - mod messages; 6 - mod packing; 7 - use serde::Serializer; 8 - 9 - pub use messages::*; 10 - 11 - #[derive(Debug)] 12 - pub enum Error { 13 - UnexpectedEof, 14 - Utf8Error(std::str::Utf8Error), 15 - IOError(std::io::Error), 16 - Serde(String), 17 - InvalidData, 18 - OutOfMemory, 19 - Todo, 20 - Timeout, 21 - Eof, 22 - } 23 - 24 - impl std::fmt::Display for Error { 25 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 - std::fmt::Debug::fmt(self, f) 27 - } 28 - } 29 - 30 - impl std::error::Error for Error {} 31 - impl serde::ser::Error for Error { 32 - fn custom<T: std::fmt::Display>(msg: T) -> Self { 33 - Error::Serde(msg.to_string()) 34 - } 35 - } 36 - impl serde::de::Error for Error { 37 - fn custom<T: std::fmt::Display>(msg: T) -> Self { 38 - Error::Serde(msg.to_string()) 39 - } 40 - } 41 - impl From<std::io::Error> for Error { 42 - fn from(v: std::io::Error) -> Self { 43 - Error::IOError(v) 44 - } 45 - } 46 - impl From<std::str::Utf8Error> for Error { 47 - fn from(v: std::str::Utf8Error) -> Self { 48 - Error::Utf8Error(v) 49 - } 50 - } 51 - impl From<std::collections::TryReserveError> for Error { 52 - fn from(v: std::collections::TryReserveError) -> Self { 53 - error!("failed to allocate {} bytes", v); 54 - Error::OutOfMemory 55 - } 56 - } 57 - 58 - fn get_code_maxlen(code: u8) -> usize { 59 - match code { 60 - 0x0e => 50 * 1024 * 1024, // PullMD: 50 MiB 61 - 0x10 => 200 * 1024 * 1024, // PushMD: 200 MiB 62 - _ => 1024 * 50, // otherwise 50K 63 - } 64 - } 65 - 66 - pub async fn read_packet<R: AsyncRead + Unpin>(mut reader: R) -> Result<Vec<u8>, Error> { 67 - let mut head = [0u8; 5]; 68 - match reader.read_exact(&mut head).await { 69 - Ok(_) => {}, 70 - Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Err(Error::Eof), // client decided to disconnect... 71 - Err(e) => return Err(e.into()), 72 - } 73 - let code = head[4]; 74 - let mut buf_len = [0u8; 4]; 75 - buf_len.copy_from_slice(&head[..4]); 76 - 77 - let buf_len = u32::from_be_bytes(buf_len) as usize; 78 - if buf_len < 4 { 79 - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "payload size is too small").into()); 80 - } 81 - 82 - let max_len = get_code_maxlen(code); 83 - 84 - if buf_len > max_len { 85 - info!("maxium size exceeded: code={}: max={}; req={}", code, max_len, buf_len); 86 - return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "request length exceeded maximum limit").into()); 87 - } 88 - 89 - // the additional byte is for the RPC code 90 - trace!("expecting {} bytes...", buf_len); 91 - let buf_len = buf_len + 1; 92 - 93 - let mut data = Vec::new(); 94 - data.try_reserve_exact(buf_len)?; 95 - data.resize(buf_len, 0); 96 - data[0] = code; 97 - reader.read_exact(&mut data[1..]).await?; 98 - 99 - Ok(data) 100 - } 101 - 102 - async fn write_packet<W: AsyncWrite + Unpin>(mut w: W, data: &[u8]) -> Result<(), std::io::Error> { 103 - let buf_len: u32 = (data.len() - 1) as u32; 104 - let buf_len = buf_len.to_be_bytes(); 105 - w.write_all(&buf_len).await?; 106 - w.write_all(data).await?; 107 - Ok(()) 108 - } 109 - 110 - pub enum RpcMessage<'a> { 111 - Ok(()), 112 - Fail(RpcFail<'a>), 113 - Notify(RpcNotify<'a>), 114 - Hello(RpcHello<'a>, Option<Creds<'a>>), 115 - PullMetadata(PullMetadata<'a>), 116 - PullMetadataResult(PullMetadataResult<'a>), 117 - PushMetadata(PushMetadata<'a>), 118 - PushMetadataResult(PushMetadataResult<'a>), 119 - DelHistory(DelHistory<'a>), 120 - DelHistoryResult(DelHistoryResult), 121 - HelloResult(HelloResult<'a>), 122 - } 123 - 124 - impl<'a> serde::Serialize for RpcMessage<'a> { 125 - fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 126 - use serde::ser::SerializeTuple; 127 - 128 - let code = self.get_code(); 129 - let mut tuple = serializer.serialize_tuple(2)?; 130 - 131 - // u8 is pushed without further encoding... 132 - tuple.serialize_element(&code)?; 133 - 134 - match self { 135 - RpcMessage::Ok(msg) => tuple.serialize_element(msg)?, 136 - RpcMessage::Fail(msg) => tuple.serialize_element(msg)?, 137 - RpcMessage::Notify(msg) => tuple.serialize_element(msg)?, 138 - RpcMessage::Hello(msg, _) => tuple.serialize_element(msg)?, 139 - RpcMessage::PullMetadata(msg) => tuple.serialize_element(msg)?, 140 - RpcMessage::PullMetadataResult(msg) => tuple.serialize_element(msg)?, 141 - RpcMessage::PushMetadata(msg) => tuple.serialize_element(msg)?, 142 - RpcMessage::PushMetadataResult(msg) => tuple.serialize_element(msg)?, 143 - RpcMessage::DelHistory(msg) => tuple.serialize_element(msg)?, 144 - RpcMessage::DelHistoryResult(msg) => tuple.serialize_element(msg)?, 145 - RpcMessage::HelloResult(msg) => tuple.serialize_element(msg)?, 146 - } 147 - 148 - tuple.end() 149 - } 150 - } 151 - 152 - impl<'a> RpcMessage<'a> { 153 - fn deserialize_check<T: serde::Deserialize<'a>>(payload: &'a [u8]) -> Result<T, Error> { 154 - let v = de::from_slice(payload)?; 155 - if v.1 != payload.len() { 156 - let bytes_remaining = crate::make_pretty_hex(&payload[v.1..]); 157 - trace!("{} remaining bytes after deserializing {}\n{bytes_remaining}", payload.len() - v.1, std::any::type_name::<T>()); 158 - } 159 - Ok(v.0) 160 - } 161 - 162 - pub fn deserialize(payload: &'a [u8]) -> Result<RpcMessage<'a>, Error> { 163 - let msg_type = payload[0]; 164 - let payload = &payload[1..]; 165 - 166 - let res = match msg_type { 167 - 0x0a => { 168 - if !payload.is_empty() { 169 - trace!("Ok message with additional data: {} bytes: {payload:02x?}", payload.len()); 170 - } 171 - RpcMessage::Ok(()) 172 - }, 173 - 0x0b => RpcMessage::Fail(Self::deserialize_check(payload)?), 174 - 0x0c => RpcMessage::Notify(Self::deserialize_check(payload)?), 175 - 0x0d => { 176 - let (hello, consumed) = de::from_slice::<messages::RpcHello>(payload)?; 177 - let creds = if payload.len() > consumed && hello.protocol_version > 2 { 178 - let payload = &payload[consumed..]; 179 - let (creds, consumed) = de::from_slice::<Creds>(payload)?; 180 - if payload.len() != consumed { 181 - trace!("bytes remaining after HelloV2: {payload:02x?}"); 182 - } 183 - Some(creds) 184 - } else { 185 - if hello.protocol_version > 2 || payload.len() != consumed { 186 - trace!("Unexpected Hello msg: {payload:02x?}"); 187 - } 188 - None 189 - }; 190 - RpcMessage::Hello(hello, creds) 191 - }, 192 - 0x0e => RpcMessage::PullMetadata(Self::deserialize_check(payload)?), 193 - 0x0f => RpcMessage::PullMetadataResult(Self::deserialize_check(payload)?), 194 - 0x10 => RpcMessage::PushMetadata(Self::deserialize_check(payload)?), 195 - 0x11 => RpcMessage::PushMetadataResult(Self::deserialize_check(payload)?), 196 - 0x18 => RpcMessage::DelHistory(Self::deserialize_check(payload)?), 197 - 0x19 => RpcMessage::DelHistoryResult(Self::deserialize_check(payload)?), 198 - 0x31 => RpcMessage::HelloResult(Self::deserialize_check(payload)?), 199 - _ => { 200 - trace!("got invalid message type '{:02x}'", msg_type); 201 - return Err(Error::InvalidData); 202 - }, 203 - }; 204 - 205 - Ok(res) 206 - } 207 - 208 - pub async fn async_write<W: AsyncWrite + Unpin>(&self, w: W) -> Result<(), Error> { 209 - let mut output = Vec::with_capacity(32); 210 - ser::to_writer(self, &mut output)?; 211 - 212 - write_packet(w, &output).await?; 213 - 214 - Ok(()) 215 - } 216 - 217 - fn get_code(&self) -> u8 { 218 - use RpcMessage::*; 219 - 220 - match self { 221 - Ok(_) => 0x0a, 222 - Fail(_) => 0x0b, 223 - Notify(_) => 0x0c, 224 - Hello(..) => 0x0d, 225 - PullMetadata(_) => 0x0e, 226 - PullMetadataResult(_) => 0x0f, 227 - PushMetadata(_) => 0x10, 228 - PushMetadataResult(_) => 0x11, 229 - DelHistory(_) => 0x18, 230 - DelHistoryResult(_) => 0x19, 231 - HelloResult(_) => 0x31, 232 - } 233 - } 234 - } 1 + use log::*; 2 + use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 3 + pub(crate) mod de; 4 + mod messages; 5 + mod packing; 6 + mod ser; 7 + use serde::Serializer; 8 + 9 + pub use messages::*; 10 + 11 + #[derive(Debug)] 12 + pub enum Error { 13 + UnexpectedEof, 14 + Utf8Error(std::str::Utf8Error), 15 + IOError(std::io::Error), 16 + Serde(String), 17 + InvalidData, 18 + OutOfMemory, 19 + Todo, 20 + Timeout, 21 + Eof, 22 + } 23 + 24 + impl std::fmt::Display for Error { 25 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 + std::fmt::Debug::fmt(self, f) 27 + } 28 + } 29 + 30 + impl std::error::Error for Error {} 31 + impl serde::ser::Error for Error { 32 + fn custom<T: std::fmt::Display>(msg: T) -> Self { 33 + Error::Serde(msg.to_string()) 34 + } 35 + } 36 + impl serde::de::Error for Error { 37 + fn custom<T: std::fmt::Display>(msg: T) -> Self { 38 + Error::Serde(msg.to_string()) 39 + } 40 + } 41 + impl From<std::io::Error> for Error { 42 + fn from(v: std::io::Error) -> Self { 43 + Error::IOError(v) 44 + } 45 + } 46 + impl From<std::str::Utf8Error> for Error { 47 + fn from(v: std::str::Utf8Error) -> Self { 48 + Error::Utf8Error(v) 49 + } 50 + } 51 + impl From<std::collections::TryReserveError> for Error { 52 + fn from(v: std::collections::TryReserveError) -> Self { 53 + error!("failed to allocate {} bytes", v); 54 + Error::OutOfMemory 55 + } 56 + } 57 + 58 + fn get_code_maxlen(code: u8) -> usize { 59 + match code { 60 + 0x0e => 50 * 1024 * 1024, // PullMD: 50 MiB 61 + 0x10 => 200 * 1024 * 1024, // PushMD: 200 MiB 62 + _ => 1024 * 50, // otherwise 50K 63 + } 64 + } 65 + 66 + pub struct PacketHeader { 67 + code: u8, 68 + size: usize, 69 + } 70 + 71 + pub async fn read_packet_header<R: AsyncRead + Unpin>( 72 + mut reader: R, 73 + ) -> Result<PacketHeader, Error> { 74 + let mut head = [0u8; 5]; 75 + match reader.read_exact(&mut head).await { 76 + Ok(_) => {}, 77 + Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Err(Error::Eof), // client decided to disconnect... 78 + Err(e) => return Err(e.into()), 79 + } 80 + let code = head[4]; 81 + let mut buf_len = [0u8; 4]; 82 + buf_len.copy_from_slice(&head[..4]); 83 + 84 + let buf_len = u32::from_be_bytes(buf_len) as usize; 85 + if buf_len < 4 { 86 + return Err(std::io::Error::new( 87 + std::io::ErrorKind::InvalidData, 88 + "payload size is too small", 89 + ) 90 + .into()); 91 + } 92 + 93 + Ok(PacketHeader { code, size: buf_len }) 94 + } 95 + 96 + impl PacketHeader { 97 + pub async fn read<R: AsyncRead + Unpin>(self, mut reader: R) -> Result<Vec<u8>, Error> { 98 + trace!("expecting {} bytes...", self.size); 99 + let buf_len = self.size + 1; 100 + 101 + let max_len = get_code_maxlen(self.code); 102 + if self.size > max_len { 103 + info!("maxium size exceeded: code={}: max={}; req={}", self.code, max_len, self.size); 104 + return Err(std::io::Error::new( 105 + std::io::ErrorKind::InvalidData, 106 + "request length exceeded maximum limit", 107 + ) 108 + .into()); 109 + } 110 + 111 + let mut data = Vec::new(); 112 + data.try_reserve_exact(buf_len)?; 113 + data.resize(buf_len, 0); 114 + data[0] = self.code; 115 + reader.read_exact(&mut data[1..]).await?; 116 + 117 + Ok(data) 118 + } 119 + 120 + // returns true if this could be an http request. 121 + pub fn is_http(&self) -> bool { 122 + let mut sz = (self.size as u32).to_be_bytes(); 123 + sz.make_ascii_uppercase(); 124 + let sz = &sz[..]; 125 + 126 + match sz { 127 + b"GET " | b"PUT " if self.code == b'/' => true, 128 + b"POST" | b"HEAD" if self.code == b' ' => true, 129 + _ => false, 130 + } 131 + } 132 + } 133 + 134 + async fn write_packet<W: AsyncWrite + Unpin>(mut w: W, data: &[u8]) -> Result<(), std::io::Error> { 135 + let buf_len: u32 = (data.len() - 1) as u32; 136 + let buf_len = buf_len.to_be_bytes(); 137 + w.write_all(&buf_len).await?; 138 + w.write_all(data).await?; 139 + Ok(()) 140 + } 141 + 142 + pub enum RpcMessage<'a> { 143 + Ok(()), 144 + Fail(RpcFail<'a>), 145 + Notify(RpcNotify<'a>), 146 + Hello(RpcHello<'a>, Option<Creds<'a>>), 147 + PullMetadata(PullMetadata<'a>), 148 + PullMetadataResult(PullMetadataResult<'a>), 149 + PushMetadata(PushMetadata<'a>), 150 + PushMetadataResult(PushMetadataResult<'a>), 151 + DelHistory(DelHistory<'a>), 152 + DelHistoryResult(DelHistoryResult), 153 + GetFuncHistories(GetFuncHistories<'a>), 154 + GetFuncHistoriesResult(GetFuncHistoriesResult<'a>), 155 + HelloResult(HelloResult<'a>), 156 + } 157 + 158 + impl<'a> serde::Serialize for RpcMessage<'a> { 159 + fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 160 + use serde::ser::SerializeTuple; 161 + 162 + let code = self.get_code(); 163 + let mut tuple = serializer.serialize_tuple(2)?; 164 + 165 + // u8 is pushed without further encoding... 166 + tuple.serialize_element(&code)?; 167 + 168 + match self { 169 + RpcMessage::Ok(msg) => tuple.serialize_element(msg)?, 170 + RpcMessage::Fail(msg) => tuple.serialize_element(msg)?, 171 + RpcMessage::Notify(msg) => tuple.serialize_element(msg)?, 172 + RpcMessage::Hello(msg, _) => tuple.serialize_element(msg)?, 173 + RpcMessage::PullMetadata(msg) => tuple.serialize_element(msg)?, 174 + RpcMessage::PullMetadataResult(msg) => tuple.serialize_element(msg)?, 175 + RpcMessage::PushMetadata(msg) => tuple.serialize_element(msg)?, 176 + RpcMessage::PushMetadataResult(msg) => tuple.serialize_element(msg)?, 177 + RpcMessage::DelHistory(msg) => tuple.serialize_element(msg)?, 178 + RpcMessage::DelHistoryResult(msg) => tuple.serialize_element(msg)?, 179 + RpcMessage::GetFuncHistories(msg) => tuple.serialize_element(msg)?, 180 + RpcMessage::GetFuncHistoriesResult(msg) => tuple.serialize_element(msg)?, 181 + RpcMessage::HelloResult(msg) => tuple.serialize_element(msg)?, 182 + } 183 + 184 + tuple.end() 185 + } 186 + } 187 + 188 + impl<'a> RpcMessage<'a> { 189 + fn deserialize_check<T: serde::Deserialize<'a>>(payload: &'a [u8]) -> Result<T, Error> { 190 + let v = de::from_slice(payload)?; 191 + if v.1 != payload.len() { 192 + let bytes_remaining = crate::make_pretty_hex(&payload[v.1..]); 193 + trace!( 194 + "{} remaining bytes after deserializing {}\n{bytes_remaining}", 195 + payload.len() - v.1, 196 + std::any::type_name::<T>() 197 + ); 198 + } 199 + Ok(v.0) 200 + } 201 + 202 + pub fn deserialize(payload: &'a [u8]) -> Result<RpcMessage<'a>, Error> { 203 + let msg_type = payload[0]; 204 + let payload = &payload[1..]; 205 + 206 + let res = match msg_type { 207 + 0x0a => { 208 + if !payload.is_empty() { 209 + trace!( 210 + "Ok message with additional data: {} bytes: {payload:02x?}", 211 + payload.len() 212 + ); 213 + } 214 + RpcMessage::Ok(()) 215 + }, 216 + 0x0b => RpcMessage::Fail(Self::deserialize_check(payload)?), 217 + 0x0c => RpcMessage::Notify(Self::deserialize_check(payload)?), 218 + 0x0d => { 219 + let (hello, consumed) = de::from_slice::<messages::RpcHello>(payload)?; 220 + let creds = if payload.len() > consumed && hello.client_version > 2 { 221 + let payload = &payload[consumed..]; 222 + let (creds, consumed) = de::from_slice::<Creds>(payload)?; 223 + if payload.len() != consumed { 224 + trace!("bytes remaining after HelloV2: {payload:02x?}"); 225 + } 226 + Some(creds) 227 + } else { 228 + if hello.client_version > 2 || payload.len() != consumed { 229 + trace!("Unexpected Hello msg: {payload:02x?}"); 230 + } 231 + None 232 + }; 233 + RpcMessage::Hello(hello, creds) 234 + }, 235 + 0x0e => RpcMessage::PullMetadata(Self::deserialize_check(payload)?), 236 + 0x0f => RpcMessage::PullMetadataResult(Self::deserialize_check(payload)?), 237 + 0x10 => RpcMessage::PushMetadata(Self::deserialize_check(payload)?), 238 + 0x11 => RpcMessage::PushMetadataResult(Self::deserialize_check(payload)?), 239 + 0x18 => RpcMessage::DelHistory(Self::deserialize_check(payload)?), 240 + 0x19 => RpcMessage::DelHistoryResult(Self::deserialize_check(payload)?), 241 + 0x2f => RpcMessage::GetFuncHistories(Self::deserialize_check(payload)?), 242 + 0x30 => RpcMessage::GetFuncHistoriesResult(Self::deserialize_check(payload)?), 243 + 0x31 => RpcMessage::HelloResult(Self::deserialize_check(payload)?), 244 + _ => { 245 + trace!("got invalid message type '{:02x}'", msg_type); 246 + return Err(Error::InvalidData); 247 + }, 248 + }; 249 + 250 + Ok(res) 251 + } 252 + 253 + pub async fn async_write<W: AsyncWrite + Unpin>(&self, w: W) -> Result<(), Error> { 254 + let mut output = Vec::with_capacity(32); 255 + ser::to_writer(self, &mut output)?; 256 + 257 + write_packet(w, &output).await?; 258 + 259 + Ok(()) 260 + } 261 + 262 + fn get_code(&self) -> u8 { 263 + use RpcMessage::*; 264 + 265 + match self { 266 + Ok(_) => 0x0a, 267 + Fail(_) => 0x0b, 268 + Notify(_) => 0x0c, 269 + Hello(..) => 0x0d, 270 + PullMetadata(_) => 0x0e, 271 + PullMetadataResult(_) => 0x0f, 272 + PushMetadata(_) => 0x10, 273 + PushMetadataResult(_) => 0x11, 274 + DelHistory(_) => 0x18, 275 + DelHistoryResult(_) => 0x19, 276 + GetFuncHistories(_) => 0x2f, 277 + GetFuncHistoriesResult(_) => 0x30, 278 + HelloResult(_) => 0x31, 279 + } 280 + } 281 + }
+111 -104
common/src/rpc/packing.rs
··· 1 - /// packs a dd into `buf` returning amout of bytes written. 2 - /// Returns 0 if buffer is too small 3 - pub fn pack_dd(v: u32, buf: &mut [u8]) -> usize { 4 - let bytes = v.to_le_bytes(); 5 - match v { 6 - 0..=0x7f => { // 0..0XXXXXXX (7 bits) 7 - if buf.is_empty() { 8 - return 0; 9 - } 10 - buf[0] = bytes[0]; 11 - 1 12 - }, 13 - 0x80..=0x3fff => { // 10AAAAAA..BBBBBBBB (14 bits) 14 - if buf.len() < 2 { 15 - return 0; 16 - } 17 - buf[0] = 0x80 | bytes[1]; 18 - buf[1] = bytes[0]; 19 - 2 20 - }, 21 - 0x4000..=0x1fffff => { // 11000000_AAAAAAAA_BBBBBBBB_CCCCCCCC (24 bits) 22 - if buf.len() < 3 { 23 - return 0; 24 - } 25 - buf[0] = 0xc0; 26 - buf[1] = bytes[2]; 27 - buf[2] = bytes[1]; 28 - buf[3] = bytes[0]; 29 - 4 30 - }, 31 - 0x200000..=u32::MAX => { // 11111111_AAAAAAAA_BBBBBBBB_CCCCCCCC_DDDDDDDD (32 bits) 32 - if buf.len() < 5 { 33 - return 0; 34 - } 35 - buf[0] = 0xff; 36 - buf[1] = bytes[3]; 37 - buf[2] = bytes[2]; 38 - buf[3] = bytes[1]; 39 - buf[4] = bytes[0]; 40 - 5 41 - } 42 - } 43 - } 44 - 45 - /// unpacks a dd from `buf`, returning the amount (value, byte consumed) 46 - pub fn unpack_dd(buf: &[u8]) -> (u32, usize) { 47 - if buf.is_empty() { 48 - return (0, 0); 49 - } 50 - 51 - let msb = buf[0]; 52 - let mut val = [0u8; 4]; 53 - 54 - if msb & 0x80 == 0 { // 0...... 55 - val[0] = msb; 56 - return (u32::from_le_bytes(val), 1); 57 - } 58 - if msb & 0x40 == 0 { // 10....../0x80 59 - if buf.len() < 2 { 60 - return (0, 0); 61 - } 62 - val[1] = msb & 0x3f; 63 - val[0] = buf[1]; 64 - return (u32::from_le_bytes(val), 2); 65 - } 66 - if msb & 0x20 == 0 { // 110...../0xC0 67 - if buf.len() < 4 { 68 - return (0, 0); 69 - } 70 - val[3] = msb & 0x1f; 71 - val[2] = buf[1]; 72 - val[1] = buf[2]; 73 - val[0] = buf[3]; 74 - return (u32::from_le_bytes(val), 4); 75 - } 76 - 77 - if buf.len() < 5 { 78 - return (0, 0); 79 - } 80 - 81 - val[3] = buf[1]; 82 - val[2] = buf[2]; 83 - val[1] = buf[3]; 84 - val[0] = buf[4]; 85 - 86 - (u32::from_le_bytes(val), 5) 87 - } 88 - 89 - #[cfg(test)] 90 - mod tests { 91 - #[test] 92 - #[ignore = "this is a very time consuming test, it should be run in release/profiling mode"] 93 - fn pack_all_nums() { 94 - for num in 0..=u32::MAX { 95 - let mut buf = [0u8; 5]; 96 - let rlen = super::pack_dd(num, &mut buf); 97 - assert!(rlen > 0); 98 - 99 - let unpacked = super::unpack_dd(&buf[..rlen]); 100 - assert_eq!(unpacked.1, rlen, "bad unpack size"); 101 - assert_eq!(unpacked.0, num, "values don't match"); 102 - } 103 - } 104 - } 1 + /// packs a dd into `buf` returning amout of bytes written. 2 + /// Returns 0 if buffer is too small 3 + pub fn pack_dd(v: u32, buf: &mut [u8]) -> usize { 4 + let bytes = v.to_le_bytes(); 5 + match v { 6 + 0..=0x7f => { 7 + // 0..0XXXXXXX (7 bits) 8 + if buf.is_empty() { 9 + return 0; 10 + } 11 + buf[0] = bytes[0]; 12 + 1 13 + }, 14 + 0x80..=0x3fff => { 15 + // 10AAAAAA..BBBBBBBB (14 bits) 16 + if buf.len() < 2 { 17 + return 0; 18 + } 19 + buf[0] = 0x80 | bytes[1]; 20 + buf[1] = bytes[0]; 21 + 2 22 + }, 23 + 0x4000..=0x1fffff => { 24 + // 11000000_AAAAAAAA_BBBBBBBB_CCCCCCCC (24 bits) 25 + if buf.len() < 3 { 26 + return 0; 27 + } 28 + buf[0] = 0xc0; 29 + buf[1] = bytes[2]; 30 + buf[2] = bytes[1]; 31 + buf[3] = bytes[0]; 32 + 4 33 + }, 34 + 0x200000..=u32::MAX => { 35 + // 11111111_AAAAAAAA_BBBBBBBB_CCCCCCCC_DDDDDDDD (32 bits) 36 + if buf.len() < 5 { 37 + return 0; 38 + } 39 + buf[0] = 0xff; 40 + buf[1] = bytes[3]; 41 + buf[2] = bytes[2]; 42 + buf[3] = bytes[1]; 43 + buf[4] = bytes[0]; 44 + 5 45 + }, 46 + } 47 + } 48 + 49 + /// unpacks a dd from `buf`, returning the amount (value, byte consumed) 50 + pub fn unpack_dd(buf: &[u8]) -> (u32, usize) { 51 + if buf.is_empty() { 52 + return (0, 0); 53 + } 54 + 55 + let msb = buf[0]; 56 + let mut val = [0u8; 4]; 57 + 58 + if msb & 0x80 == 0 { 59 + // 0...... 60 + val[0] = msb; 61 + return (u32::from_le_bytes(val), 1); 62 + } 63 + if msb & 0x40 == 0 { 64 + // 10....../0x80 65 + if buf.len() < 2 { 66 + return (0, 0); 67 + } 68 + val[1] = msb & 0x3f; 69 + val[0] = buf[1]; 70 + return (u32::from_le_bytes(val), 2); 71 + } 72 + if msb & 0x20 == 0 { 73 + // 110...../0xC0 74 + if buf.len() < 4 { 75 + return (0, 0); 76 + } 77 + val[3] = msb & 0x1f; 78 + val[2] = buf[1]; 79 + val[1] = buf[2]; 80 + val[0] = buf[3]; 81 + return (u32::from_le_bytes(val), 4); 82 + } 83 + 84 + if buf.len() < 5 { 85 + return (0, 0); 86 + } 87 + 88 + val[3] = buf[1]; 89 + val[2] = buf[2]; 90 + val[1] = buf[3]; 91 + val[0] = buf[4]; 92 + 93 + (u32::from_le_bytes(val), 5) 94 + } 95 + 96 + #[cfg(test)] 97 + mod tests { 98 + #[test] 99 + #[ignore = "this is a very time consuming test, it should be run in release/profiling mode"] 100 + fn pack_all_nums() { 101 + for num in 0..=u32::MAX { 102 + let mut buf = [0u8; 5]; 103 + let rlen = super::pack_dd(num, &mut buf); 104 + assert!(rlen > 0); 105 + 106 + let unpacked = super::unpack_dd(&buf[..rlen]); 107 + assert_eq!(unpacked.1, rlen, "bad unpack size"); 108 + assert_eq!(unpacked.0, num, "values don't match"); 109 + } 110 + } 111 + }
+255 -241
common/src/rpc/ser.rs
··· 1 - use std::io::Write; 2 - use serde::{ser, Serialize, ser::Impossible}; 3 - use super::Error; 4 - 5 - struct Serializer<W: Write> { 6 - output: W, 7 - } 8 - 9 - pub fn to_writer<T: Serialize, W: Write>(v: &T, w: W) -> Result<(), Error> { 10 - let mut serializer = Serializer { 11 - output: w, 12 - }; 13 - v.serialize(&mut serializer)?; 14 - Ok(()) 15 - } 16 - 17 - #[allow(dead_code)] 18 - pub fn to_vec<T: Serialize>(v: &T) -> Result<Vec<u8>, Error> { 19 - let mut buf = vec![]; 20 - to_writer(v, &mut buf)?; 21 - Ok(buf) 22 - } 23 - 24 - impl<W: Write> Serializer<W> { 25 - fn pack_dd(&mut self, num: u32) -> Result<(), Error> { 26 - let mut buf = [0u8; 5]; 27 - let bytes = super::packing::pack_dd(num, &mut buf); 28 - self.output.write_all(&buf[..bytes])?; 29 - Ok(()) 30 - } 31 - 32 - fn pack_str(&mut self, s: &str) -> Result<(), Error> { 33 - self.output.write_all(s.as_bytes())?; 34 - self.output.write_all(&[0])?; 35 - Ok(()) 36 - } 37 - 38 - fn pack_bytes(&mut self, b: &[u8]) -> Result<(), Error> { 39 - self.pack_dd(b.len() as u32)?; 40 - self.output.write_all(b)?; 41 - Ok(()) 42 - } 43 - } 44 - 45 - impl<'a, W: Write> ser::Serializer for &'a mut Serializer<W> { 46 - type Ok = (); 47 - type Error = Error; 48 - 49 - type SerializeSeq = Self; 50 - type SerializeTuple = Self; 51 - type SerializeTupleStruct = Impossible<(), Self::Error>; 52 - type SerializeTupleVariant = Impossible<(), Self::Error>; 53 - type SerializeMap = Impossible<(), Self::Error>; 54 - type SerializeStruct = Self; 55 - type SerializeStructVariant = Impossible<(), Self::Error>; 56 - 57 - fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> { 58 - unreachable!() 59 - } 60 - 61 - fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> { 62 - unreachable!() 63 - } 64 - 65 - fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> { 66 - unreachable!() 67 - } 68 - 69 - fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> { 70 - unreachable!() 71 - } 72 - 73 - fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> { 74 - unreachable!() 75 - } 76 - 77 - fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> { 78 - self.output.write_all(&[v])?; 79 - Ok(()) 80 - } 81 - 82 - fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> { 83 - unreachable!() 84 - } 85 - 86 - fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> { 87 - self.pack_dd(v) 88 - } 89 - 90 - fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> { 91 - let high = (v >> 32) & 0xffffffff; 92 - let low = v & 0xffffffff; 93 - self.pack_dd(high as u32)?; 94 - self.pack_dd(low as u32)?; 95 - Ok(()) 96 - } 97 - 98 - fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> { 99 - unreachable!() 100 - } 101 - 102 - fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> { 103 - unreachable!() 104 - } 105 - 106 - fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> { 107 - unreachable!() 108 - } 109 - 110 - fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> { 111 - self.pack_str(v) 112 - } 113 - 114 - fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> { 115 - self.pack_bytes(v) 116 - } 117 - 118 - fn serialize_none(self) -> Result<Self::Ok, Self::Error> { 119 - unreachable!() 120 - } 121 - 122 - fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Self::Ok, Self::Error> { 123 - unreachable!() 124 - } 125 - 126 - fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { 127 - // unit contains no information... 128 - Ok(()) 129 - } 130 - 131 - fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> { 132 - unreachable!() 133 - } 134 - 135 - fn serialize_unit_variant(self, _name: &'static str, _variant_index: u32, _variant: &'static str) -> Result<Self::Ok, Self::Error> { 136 - unreachable!() 137 - } 138 - 139 - fn serialize_newtype_struct<T: ?Sized + Serialize>(self, _name: &'static str, _value: &T) -> Result<Self::Ok, Self::Error> { 140 - unreachable!() 141 - } 142 - 143 - fn serialize_newtype_variant<T: ?Sized + Serialize>(self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T) -> Result<Self::Ok, Self::Error> { 144 - unreachable!() 145 - } 146 - 147 - fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { 148 - let len = len.unwrap(); 149 - self.pack_dd(len as u32)?; 150 - Ok(self) 151 - } 152 - 153 - fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> { 154 - Ok(self) 155 - } 156 - 157 - fn serialize_tuple_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeTupleStruct, Self::Error> { 158 - unreachable!(); 159 - } 160 - 161 - fn serialize_tuple_variant(self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Result<Self::SerializeTupleVariant, Self::Error> { 162 - unreachable!() 163 - } 164 - 165 - fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { 166 - unreachable!() 167 - } 168 - 169 - fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct, Self::Error> { 170 - // structs will simply be flattened 171 - Ok(self) 172 - } 173 - 174 - fn serialize_struct_variant(self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize) -> Result<Self::SerializeStructVariant, Self::Error> { 175 - unreachable!() 176 - } 177 - } 178 - 179 - impl<'a, W: Write> ser::SerializeSeq for &'a mut Serializer<W> { 180 - type Ok = (); 181 - type Error = Error; 182 - 183 - fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> { 184 - value.serialize(&mut **self) 185 - } 186 - 187 - fn end(self) -> Result<Self::Ok, Self::Error> { 188 - Ok(()) 189 - } 190 - } 191 - 192 - impl<'a, W: Write> ser::SerializeTuple for &'a mut Serializer<W> { 193 - type Ok = (); 194 - type Error = Error; 195 - 196 - fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> { 197 - value.serialize(&mut **self) 198 - } 199 - 200 - fn end(self) -> Result<Self::Ok, Self::Error> { 201 - Ok(()) 202 - } 203 - } 204 - 205 - impl<'a, W: Write> ser::SerializeStruct for &'a mut Serializer<W> { 206 - type Ok = (); 207 - type Error = Error; 208 - 209 - fn serialize_field<T: ?Sized + Serialize>(&mut self, _key: &'static str, value: &T) -> Result<(), Self::Error> { 210 - // struct names have no meaning 211 - value.serialize(&mut **self) 212 - } 213 - 214 - fn end(self) -> Result<Self::Ok, Self::Error> { 215 - Ok(()) 216 - } 217 - } 218 - 219 - #[cfg(test)] 220 - mod tests { 221 - #[test] 222 - fn ser_hello() { 223 - #[derive(serde::Serialize)] 224 - struct Test<'a> { 225 - arr: [u8; 16], 226 - s: &'a str, 227 - b: &'a [u8], 228 - i: u32, 229 - q: u64, 230 - } 231 - let v = Test { 232 - arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 233 - s: "somestring", 234 - b: b"bytes", 235 - i: 0x20, 236 - q: 0x20, 237 - }; 238 - let v = super::to_vec(&v).expect("failed to serialize dummy"); 239 - assert_eq!(v, b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10somestring\x00\x05bytes\x20\x00\x20"); 240 - } 241 - } 1 + use super::Error; 2 + use serde::{ser, ser::Impossible, Serialize}; 3 + use std::io::Write; 4 + 5 + struct Serializer<W: Write> { 6 + output: W, 7 + } 8 + 9 + pub fn to_writer<T: Serialize, W: Write>(v: &T, w: W) -> Result<(), Error> { 10 + let mut serializer = Serializer { output: w }; 11 + v.serialize(&mut serializer)?; 12 + Ok(()) 13 + } 14 + 15 + #[allow(dead_code)] 16 + pub fn to_vec<T: Serialize>(v: &T) -> Result<Vec<u8>, Error> { 17 + let mut buf = vec![]; 18 + to_writer(v, &mut buf)?; 19 + Ok(buf) 20 + } 21 + 22 + impl<W: Write> Serializer<W> { 23 + fn pack_dd(&mut self, num: u32) -> Result<(), Error> { 24 + let mut buf = [0u8; 5]; 25 + let bytes = super::packing::pack_dd(num, &mut buf); 26 + self.output.write_all(&buf[..bytes])?; 27 + Ok(()) 28 + } 29 + 30 + fn pack_str(&mut self, s: &str) -> Result<(), Error> { 31 + self.output.write_all(s.as_bytes())?; 32 + self.output.write_all(&[0])?; 33 + Ok(()) 34 + } 35 + 36 + fn pack_bytes(&mut self, b: &[u8]) -> Result<(), Error> { 37 + self.pack_dd(b.len() as u32)?; 38 + self.output.write_all(b)?; 39 + Ok(()) 40 + } 41 + } 42 + 43 + impl<'a, W: Write> ser::Serializer for &'a mut Serializer<W> { 44 + type Ok = (); 45 + type Error = Error; 46 + 47 + type SerializeSeq = Self; 48 + type SerializeTuple = Self; 49 + type SerializeTupleStruct = Impossible<(), Self::Error>; 50 + type SerializeTupleVariant = Impossible<(), Self::Error>; 51 + type SerializeMap = Impossible<(), Self::Error>; 52 + type SerializeStruct = Self; 53 + type SerializeStructVariant = Impossible<(), Self::Error>; 54 + 55 + fn serialize_bool(self, _v: bool) -> Result<Self::Ok, Self::Error> { 56 + unreachable!() 57 + } 58 + 59 + fn serialize_i8(self, _v: i8) -> Result<Self::Ok, Self::Error> { 60 + unreachable!() 61 + } 62 + 63 + fn serialize_i16(self, _v: i16) -> Result<Self::Ok, Self::Error> { 64 + unreachable!() 65 + } 66 + 67 + fn serialize_i32(self, _v: i32) -> Result<Self::Ok, Self::Error> { 68 + unreachable!() 69 + } 70 + 71 + fn serialize_i64(self, _v: i64) -> Result<Self::Ok, Self::Error> { 72 + unreachable!() 73 + } 74 + 75 + fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> { 76 + self.output.write_all(&[v])?; 77 + Ok(()) 78 + } 79 + 80 + fn serialize_u16(self, _v: u16) -> Result<Self::Ok, Self::Error> { 81 + unreachable!() 82 + } 83 + 84 + fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> { 85 + self.pack_dd(v) 86 + } 87 + 88 + fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> { 89 + let high = (v >> 32) & 0xffffffff; 90 + let low = v & 0xffffffff; 91 + self.pack_dd(high as u32)?; 92 + self.pack_dd(low as u32)?; 93 + Ok(()) 94 + } 95 + 96 + fn serialize_f32(self, _v: f32) -> Result<Self::Ok, Self::Error> { 97 + unreachable!() 98 + } 99 + 100 + fn serialize_f64(self, _v: f64) -> Result<Self::Ok, Self::Error> { 101 + unreachable!() 102 + } 103 + 104 + fn serialize_char(self, _v: char) -> Result<Self::Ok, Self::Error> { 105 + unreachable!() 106 + } 107 + 108 + fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> { 109 + self.pack_str(v) 110 + } 111 + 112 + fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> { 113 + self.pack_bytes(v) 114 + } 115 + 116 + fn serialize_none(self) -> Result<Self::Ok, Self::Error> { 117 + unreachable!() 118 + } 119 + 120 + fn serialize_some<T: ?Sized + Serialize>(self, _value: &T) -> Result<Self::Ok, Self::Error> { 121 + unreachable!() 122 + } 123 + 124 + fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { 125 + // unit contains no information... 126 + Ok(()) 127 + } 128 + 129 + fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> { 130 + unreachable!() 131 + } 132 + 133 + fn serialize_unit_variant( 134 + self, _name: &'static str, _variant_index: u32, _variant: &'static str, 135 + ) -> Result<Self::Ok, Self::Error> { 136 + unreachable!() 137 + } 138 + 139 + fn serialize_newtype_struct<T: ?Sized + Serialize>( 140 + self, _name: &'static str, _value: &T, 141 + ) -> Result<Self::Ok, Self::Error> { 142 + unreachable!() 143 + } 144 + 145 + fn serialize_newtype_variant<T: ?Sized + Serialize>( 146 + self, _name: &'static str, _variant_index: u32, _variant: &'static str, _value: &T, 147 + ) -> Result<Self::Ok, Self::Error> { 148 + unreachable!() 149 + } 150 + 151 + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { 152 + let len = len.unwrap(); 153 + self.pack_dd(len as u32)?; 154 + Ok(self) 155 + } 156 + 157 + fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple, Self::Error> { 158 + Ok(self) 159 + } 160 + 161 + fn serialize_tuple_struct( 162 + self, _name: &'static str, _len: usize, 163 + ) -> Result<Self::SerializeTupleStruct, Self::Error> { 164 + unreachable!(); 165 + } 166 + 167 + fn serialize_tuple_variant( 168 + self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, 169 + ) -> Result<Self::SerializeTupleVariant, Self::Error> { 170 + unreachable!() 171 + } 172 + 173 + fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { 174 + unreachable!() 175 + } 176 + 177 + fn serialize_struct( 178 + self, _name: &'static str, _len: usize, 179 + ) -> Result<Self::SerializeStruct, Self::Error> { 180 + // structs will simply be flattened 181 + Ok(self) 182 + } 183 + 184 + fn serialize_struct_variant( 185 + self, _name: &'static str, _variant_index: u32, _variant: &'static str, _len: usize, 186 + ) -> Result<Self::SerializeStructVariant, Self::Error> { 187 + unreachable!() 188 + } 189 + } 190 + 191 + impl<'a, W: Write> ser::SerializeSeq for &'a mut Serializer<W> { 192 + type Ok = (); 193 + type Error = Error; 194 + 195 + fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> { 196 + value.serialize(&mut **self) 197 + } 198 + 199 + fn end(self) -> Result<Self::Ok, Self::Error> { 200 + Ok(()) 201 + } 202 + } 203 + 204 + impl<'a, W: Write> ser::SerializeTuple for &'a mut Serializer<W> { 205 + type Ok = (); 206 + type Error = Error; 207 + 208 + fn serialize_element<T: ?Sized + Serialize>(&mut self, value: &T) -> Result<(), Self::Error> { 209 + value.serialize(&mut **self) 210 + } 211 + 212 + fn end(self) -> Result<Self::Ok, Self::Error> { 213 + Ok(()) 214 + } 215 + } 216 + 217 + impl<'a, W: Write> ser::SerializeStruct for &'a mut Serializer<W> { 218 + type Ok = (); 219 + type Error = Error; 220 + 221 + fn serialize_field<T: ?Sized + Serialize>( 222 + &mut self, _key: &'static str, value: &T, 223 + ) -> Result<(), Self::Error> { 224 + // struct names have no meaning 225 + value.serialize(&mut **self) 226 + } 227 + 228 + fn end(self) -> Result<Self::Ok, Self::Error> { 229 + Ok(()) 230 + } 231 + } 232 + 233 + #[cfg(test)] 234 + mod tests { 235 + #[test] 236 + fn ser_hello() { 237 + #[derive(serde::Serialize)] 238 + struct Test<'a> { 239 + arr: [u8; 16], 240 + s: &'a str, 241 + b: &'a [u8], 242 + i: u32, 243 + q: u64, 244 + } 245 + let v = Test { 246 + arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 247 + s: "somestring", 248 + b: b"bytes", 249 + i: 0x20, 250 + q: 0x20, 251 + }; 252 + let v = super::to_vec(&v).expect("failed to serialize dummy"); 253 + assert_eq!(v, b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10somestring\x00\x05bytes\x20\x00\x20"); 254 + } 255 + }
+192 -204
common/src/web/api.rs
··· 1 - use std::borrow::Cow; 2 - use log::*; 3 - use warp::{Filter, Reply, Rejection}; 4 - use serde::Serialize; 5 - 6 - use super::SharedState; 7 - 8 - struct Md5([u8; 16]); 9 - impl std::str::FromStr for Md5 { 10 - type Err = &'static str; 11 - fn from_str(s: &str) -> Result<Md5, Self::Err> { 12 - let mut res = [0u8; 16]; 13 - let s = s.trim(); 14 - if s.len() != 32 { 15 - return Err("bad md5 length"); 16 - } 17 - binascii::hex2bin(s.as_bytes(), &mut res) 18 - .map_err(|_| "bad md5")?; 19 - Ok(Md5(res)) 20 - } 21 - } 22 - 23 - #[derive(Serialize)] 24 - struct Error<'a> { 25 - error: &'a str, 26 - } 27 - 28 - impl std::fmt::Display for Md5 { 29 - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 - let mut out = [0u8; 32]; 31 - binascii::bin2hex(&self.0, &mut out).unwrap(); 32 - let out = std::str::from_utf8(&out).unwrap(); 33 - write!(f, "{}", &out) 34 - } 35 - } 36 - 37 - impl Serialize for Md5 { 38 - fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 39 - serializer.serialize_str(&format!("{}", &self)) 40 - } 41 - } 42 - 43 - pub fn api_root(state: SharedState) -> impl Filter<Extract = (impl Reply + 'static, ), Error=Rejection> + Clone { 44 - let view_file = warp::get() 45 - .and(warp::path("files")) 46 - .and(super::with_state(state.clone())) 47 - .and(warp::filters::path::param::<Md5>()) 48 - .and_then(view_file_by_hash); 49 - let view_func = warp::get() 50 - .and(warp::path("funcs")) 51 - .and(super::with_state(state)) 52 - .and(warp::filters::path::param::<Md5>()) 53 - .and_then(view_func_by_hash); 54 - 55 - view_file 56 - .or(view_func) 57 - } 58 - 59 - // GET server/api/files/:md5 60 - async fn view_file_by_hash(state: SharedState, md5: Md5) -> Result<impl Reply, Rejection> { 61 - #[derive(Serialize)] 62 - struct FileFunc { 63 - hash: Md5, 64 - len: u32, 65 - name: String, 66 - } 67 - 68 - let v = match state.db.get_file_funcs(&md5.0[..], 0, 10_000).await { 69 - Ok(v) => v, 70 - Err(err) => { 71 - error!("failed to get file's funcs {}: {}", &md5, err); 72 - return Ok(warp::reply::json(&Error{error: "internal server error"})); 73 - }, 74 - }; 75 - let v: Vec<_> = v.into_iter() 76 - .map(|v| { 77 - let mut hash = [0u8; 16]; 78 - hash.copy_from_slice(&v.2); 79 - FileFunc { 80 - name: v.0, 81 - len: v.1 as u32, 82 - hash: Md5(hash), 83 - } 84 - }) 85 - .collect(); 86 - 87 - Result::<_, Rejection>::Ok(warp::reply::json(&v)) 88 - } 89 - 90 - // GET server/api/funcs/:md5 91 - async fn view_func_by_hash(state: SharedState, md5: Md5) -> Result<impl Reply, Rejection> { 92 - #[derive(Serialize)] 93 - enum CommentType { 94 - Posterior, 95 - Anterior, 96 - Function{ repeatable: bool }, 97 - Byte { repeatable: bool }, 98 - } 99 - 100 - #[derive(Serialize)] 101 - struct Comment<'a> { 102 - #[serde(skip_serializing_if = "Option::is_none")] 103 - offset: Option<u32>, 104 - #[serde(rename = "type")] 105 - type_: CommentType, 106 - comment: Cow<'a, str>, 107 - } 108 - 109 - #[derive(Serialize)] 110 - struct FuncInfo<'a> { 111 - name: &'a str, 112 - comments: Vec<Comment<'a>>, 113 - length: u32, 114 - in_files: &'a [Md5], 115 - } 116 - 117 - let funcs = [crate::rpc::PullMetadataFunc { 118 - unk0: 1, 119 - mb_hash: &md5.0 120 - }]; 121 - 122 - let files_with = state.db.get_files_with_func(&md5.0[..]); 123 - let files_info = state.db.get_funcs(&funcs); 124 - 125 - let (files_with, files_info) = match futures_util::try_join!(files_with, files_info) { 126 - Ok(v) => v, 127 - Err(err) => { 128 - error!("failed to execute db queries: {}", err); 129 - return Ok(warp::reply::json(&Error {error: "internal server error"})); 130 - } 131 - }; 132 - 133 - let files_with: Vec<Md5> = files_with.into_iter().map(|v| { 134 - let mut md5 = [0u8; 16]; 135 - md5.copy_from_slice(&v); 136 - Md5(md5) 137 - }).collect(); 138 - 139 - let v = files_info; 140 - let v: Vec<FuncInfo> = v 141 - .iter() 142 - .take(1) 143 - .filter_map(|v| v.as_ref()) 144 - .filter_map(|v| { 145 - let md = match crate::md::parse_metadata(&v.data) { 146 - Ok(v) => v, 147 - Err(e) => { 148 - error!("error parsing metadata for {}: {}", &md5, e); 149 - return None; 150 - } 151 - }; 152 - let comments: Vec<Comment> = md.into_iter() 153 - .filter_map(|md| { 154 - match md { 155 - crate::md::FunctionMetadata::ByteComment(c) => { 156 - Some(vec![Comment { 157 - offset: Some(c.offset), 158 - type_: CommentType::Byte{ repeatable: c.is_repeatable }, 159 - comment: c.comment.into(), 160 - }]) 161 - }, 162 - crate::md::FunctionMetadata::FunctionComment(c) => { 163 - Some(vec![Comment { 164 - offset: None, 165 - type_: CommentType::Function{ repeatable: c.is_repeatable }, 166 - comment: c.comment.into(), 167 - }]) 168 - }, 169 - crate::md::FunctionMetadata::ExtraComment(c) => { 170 - let mut res = vec![]; 171 - if !c.anterior.is_empty() { 172 - res.push(Comment { 173 - offset: Some(c.offset), 174 - type_: CommentType::Anterior, 175 - comment: c.anterior.into(), 176 - }); 177 - } 178 - if !c.posterior.is_empty() { 179 - res.push(Comment { 180 - offset: Some(c.offset), 181 - type_: CommentType::Posterior, 182 - comment: c.posterior.into(), 183 - }); 184 - } 185 - if !res.is_empty() { 186 - Some(res) 187 - } else { 188 - None 189 - } 190 - }, 191 - } 192 - }) 193 - .flatten() 194 - .collect(); 195 - Some(FuncInfo { 196 - name: &v.name, 197 - length: v.len, 198 - comments, 199 - in_files: &files_with, 200 - }) 201 - }).collect(); 202 - 203 - Result::<_, Rejection>::Ok(warp::reply::json(&v)) 204 - } 1 + use log::*; 2 + use serde::Serialize; 3 + use std::borrow::Cow; 4 + use warp::{Filter, Rejection, Reply}; 5 + 6 + use super::SharedState; 7 + 8 + struct Md5([u8; 16]); 9 + impl std::str::FromStr for Md5 { 10 + type Err = &'static str; 11 + fn from_str(s: &str) -> Result<Md5, Self::Err> { 12 + let mut res = [0u8; 16]; 13 + let s = s.trim(); 14 + if s.len() != 32 { 15 + return Err("bad md5 length"); 16 + } 17 + binascii::hex2bin(s.as_bytes(), &mut res).map_err(|_| "bad md5")?; 18 + Ok(Md5(res)) 19 + } 20 + } 21 + 22 + #[derive(Serialize)] 23 + struct Error<'a> { 24 + error: &'a str, 25 + } 26 + 27 + impl std::fmt::Display for Md5 { 28 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 + let mut out = [0u8; 32]; 30 + binascii::bin2hex(&self.0, &mut out).unwrap(); 31 + let out = std::str::from_utf8(&out).unwrap(); 32 + write!(f, "{}", &out) 33 + } 34 + } 35 + 36 + impl Serialize for Md5 { 37 + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { 38 + serializer.serialize_str(&format!("{}", &self)) 39 + } 40 + } 41 + 42 + pub fn api_root( 43 + state: SharedState, 44 + ) -> impl Filter<Extract = (impl Reply + 'static,), Error = Rejection> + Clone { 45 + let view_file = warp::get() 46 + .and(warp::path("files")) 47 + .and(super::with_state(state.clone())) 48 + .and(warp::filters::path::param::<Md5>()) 49 + .and_then(view_file_by_hash); 50 + let view_func = warp::get() 51 + .and(warp::path("funcs")) 52 + .and(super::with_state(state)) 53 + .and(warp::filters::path::param::<Md5>()) 54 + .and_then(view_func_by_hash); 55 + 56 + view_file.or(view_func) 57 + } 58 + 59 + // GET server/api/files/:md5 60 + async fn view_file_by_hash(state: SharedState, md5: Md5) -> Result<impl Reply, Rejection> { 61 + #[derive(Serialize)] 62 + struct FileFunc { 63 + hash: Md5, 64 + len: u32, 65 + name: String, 66 + } 67 + 68 + let v = match state.db.get_file_funcs(&md5.0[..], 0, 10_000).await { 69 + Ok(v) => v, 70 + Err(err) => { 71 + error!("failed to get file's funcs {}: {}", &md5, err); 72 + return Ok(warp::reply::json(&Error { error: "internal server error" })); 73 + }, 74 + }; 75 + let v: Vec<_> = v 76 + .into_iter() 77 + .map(|v| { 78 + let mut hash = [0u8; 16]; 79 + hash.copy_from_slice(&v.2); 80 + FileFunc { name: v.0, len: v.1 as u32, hash: Md5(hash) } 81 + }) 82 + .collect(); 83 + 84 + Result::<_, Rejection>::Ok(warp::reply::json(&v)) 85 + } 86 + 87 + // GET server/api/funcs/:md5 88 + async fn view_func_by_hash(state: SharedState, md5: Md5) -> Result<impl Reply, Rejection> { 89 + #[derive(Serialize)] 90 + enum CommentType { 91 + Posterior, 92 + Anterior, 93 + Function { repeatable: bool }, 94 + Byte { repeatable: bool }, 95 + } 96 + 97 + #[derive(Serialize)] 98 + struct Comment<'a> { 99 + #[serde(skip_serializing_if = "Option::is_none")] 100 + offset: Option<u32>, 101 + #[serde(rename = "type")] 102 + type_: CommentType, 103 + comment: Cow<'a, str>, 104 + } 105 + 106 + #[derive(Serialize)] 107 + struct FuncInfo<'a> { 108 + name: &'a str, 109 + comments: Vec<Comment<'a>>, 110 + length: u32, 111 + in_files: &'a [Md5], 112 + } 113 + 114 + let funcs = [crate::rpc::PatternId { ty: 1, data: &md5.0 }]; 115 + 116 + let files_with = state.db.get_files_with_func(&md5.0[..]); 117 + let files_info = state.db.get_funcs(&funcs); 118 + 119 + let (files_with, files_info) = match futures_util::try_join!(files_with, files_info) { 120 + Ok(v) => v, 121 + Err(err) => { 122 + error!("failed to execute db queries: {}", err); 123 + return Ok(warp::reply::json(&Error { error: "internal server error" })); 124 + }, 125 + }; 126 + 127 + let files_with: Vec<Md5> = files_with 128 + .into_iter() 129 + .map(|v| { 130 + let mut md5 = [0u8; 16]; 131 + md5.copy_from_slice(&v); 132 + Md5(md5) 133 + }) 134 + .collect(); 135 + 136 + let v = files_info; 137 + let v: Vec<FuncInfo> = v 138 + .iter() 139 + .take(1) 140 + .filter_map(|v| v.as_ref()) 141 + .filter_map(|v| { 142 + let md = match crate::md::parse_metadata(&v.data) { 143 + Ok(v) => v, 144 + Err(e) => { 145 + error!("error parsing metadata for {}: {}", &md5, e); 146 + return None; 147 + }, 148 + }; 149 + let comments: Vec<Comment> = md 150 + .into_iter() 151 + .filter_map(|md| match md { 152 + crate::md::FunctionMetadata::ByteComment(c) => Some(vec![Comment { 153 + offset: Some(c.offset), 154 + type_: CommentType::Byte { repeatable: c.is_repeatable }, 155 + comment: c.comment.into(), 156 + }]), 157 + crate::md::FunctionMetadata::FunctionComment(c) => Some(vec![Comment { 158 + offset: None, 159 + type_: CommentType::Function { repeatable: c.is_repeatable }, 160 + comment: c.comment.into(), 161 + }]), 162 + crate::md::FunctionMetadata::ExtraComment(c) => { 163 + let mut res = vec![]; 164 + if !c.anterior.is_empty() { 165 + res.push(Comment { 166 + offset: Some(c.offset), 167 + type_: CommentType::Anterior, 168 + comment: c.anterior.into(), 169 + }); 170 + } 171 + if !c.posterior.is_empty() { 172 + res.push(Comment { 173 + offset: Some(c.offset), 174 + type_: CommentType::Posterior, 175 + comment: c.posterior.into(), 176 + }); 177 + } 178 + if !res.is_empty() { 179 + Some(res) 180 + } else { 181 + None 182 + } 183 + }, 184 + }) 185 + .flatten() 186 + .collect(); 187 + Some(FuncInfo { name: &v.name, length: v.len, comments, in_files: &files_with }) 188 + }) 189 + .collect(); 190 + 191 + Result::<_, Rejection>::Ok(warp::reply::json(&v)) 192 + }
+10 -8
common/src/web.rs
··· 1 - use crate::SharedState; 2 - use warp::Filter; 3 - 4 - pub mod api; 5 - 6 - pub fn with_state(state: SharedState) -> impl Filter<Extract=(SharedState,), Error= std::convert::Infallible> + Clone { 7 - warp::any().map(move || state.clone()) 8 - } 1 + use crate::SharedState; 2 + use warp::Filter; 3 + 4 + pub mod api; 5 + 6 + pub fn with_state( 7 + state: SharedState, 8 + ) -> impl Filter<Extract = (SharedState,), Error = std::convert::Infallible> + Clone { 9 + warp::any().map(move || state.clone()) 10 + }
+10 -2
config-example.toml
··· 7 7 server_name = "lumen" 8 8 9 9 # Allow clients to delete metadata from the database? 10 - allow_deletes = false 10 + allow_deletes = true 11 + # How many function histories should we return? 0=Disabled. 12 + get_history_limit = 50 13 + 14 + [users] 15 + # Enable guest accounts? disabling this will only allow IDA 8.1+ to connect. 16 + allow_guests = true 17 + # sets the amount of PBKDF2 iterations for storing passwords. 18 + pbkdf2_iterations = 120000 11 19 12 20 # only required when `use_tls` is set to true. 13 21 [lumina.tls] 14 - # Specify the server's certificate. 22 + # Specify the server's certificate. 15 23 # Clients connecting to the server must match this certificate. 16 24 # If the certificate is password protected, the password can be specified in the `PKCSPASSWD` environment variable. 17 25 server_cert = "path/to/server_crt"
+6 -5
lumen/Cargo.toml
··· 7 7 publish = false 8 8 9 9 [dependencies] 10 - common = {path = "../common"} 11 - tokio = {version = "1.32", features = ["full"]} 12 - log = {version = "0.4", features = ["release_max_level_debug"]} 10 + common = { path = "../common" } 11 + tokio = { version = "1.32", features = ["full"] } 12 + log = { version = "0.4", features = ["release_max_level_debug"] } 13 13 pretty_env_logger = "0.5" 14 14 clap = "4.3" 15 15 tokio-native-tls = "0.3" 16 - native-tls = {version = "0.2"} 16 + native-tls = { version = "0.2" } 17 17 warp = "0.3" 18 - prometheus-client = "0.21.2" 18 + prometheus-client = "0.22.0" 19 + rpassword = "7.3.1"
+68 -410
lumen/src/main.rs
··· 4 4 #![warn(unused_crate_dependencies)] 5 5 #![deny(clippy::all)] 6 6 7 - use common::async_drop::AsyncDropper; 8 - use common::metrics::LuminaVersion; 9 - use common::rpc::{RpcHello, RpcFail, HelloResult}; 10 - use native_tls::Identity; 11 - use clap::Arg; 7 + use clap::{builder::BoolishValueParser, Arg, Command}; 12 8 use log::*; 13 - use tokio::time::timeout; 14 - use std::collections::HashMap; 15 - use std::mem::discriminant; 16 - use std::time::{Duration, Instant}; 17 - use std::{borrow::Cow, sync::Arc}; 18 - use tokio::{net::TcpListener, io::AsyncWrite, io::AsyncRead}; 19 - use std::process::exit; 20 - use common::{SharedState, SharedState_}; 9 + use server::do_lumen; 10 + use std::sync::Arc; 11 + use users::UserMgmt; 21 12 13 + mod server; 14 + mod users; 22 15 mod web; 23 - 24 - use common::{config, make_pretty_hex, md, rpc::{self, Error}}; 25 - use common::db::Database; 26 - use rpc::RpcMessage; 27 16 28 17 fn setup_logger() { 29 18 if std::env::var("RUST_LOG").is_err() { ··· 32 21 pretty_env_logger::init_timed(); 33 22 } 34 23 35 - async fn handle_transaction<'a, S: AsyncRead + AsyncWrite + Unpin>(state: &SharedState, user: &'a RpcHello<'a>, mut stream: S) -> Result<(), Error> { 36 - let db = &state.db; 37 - let server_name = state.server_name.as_str(); 38 - 39 - trace!("waiting for command.."); 40 - let req = match timeout(Duration::from_secs(3600), rpc::read_packet(&mut stream)).await { 41 - Ok(res) => match res { 42 - Ok(v) => v, 43 - Err(e) => return Err(e), 44 - }, 45 - Err(_) => { 46 - _ = RpcMessage::Fail(RpcFail { 47 - code: 0, 48 - message: &format!("{server_name} client idle for too long.\n"), 49 - }).async_write(&mut stream).await; 50 - return Err(Error::Timeout); 51 - }, 52 - }; 53 - trace!("got command!"); 54 - let req = match RpcMessage::deserialize(&req) { 55 - Ok(v) => v, 56 - Err(err) => { 57 - warn!("bad message: \n{}\n", make_pretty_hex(&req)); 58 - error!("failed to process rpc message: {}", err); 59 - let resp = rpc::RpcFail{ code: 0, message: &format!("{server_name}: error: invalid data.\n")}; 60 - let resp = RpcMessage::Fail(resp); 61 - resp.async_write(&mut stream).await?; 62 - 63 - return Ok(()); 64 - }, 65 - }; 66 - match req { 67 - RpcMessage::PullMetadata(md) => { 68 - let start = Instant::now(); 69 - let funcs = match timeout(Duration::from_secs(4 * 60), db.get_funcs(&md.funcs)).await { 70 - Ok(r) => match r { 71 - Ok(v) => v, 72 - Err(e) => { 73 - error!("pull failed, db: {}", e); 74 - rpc::RpcMessage::Fail(rpc::RpcFail { 75 - code: 0, 76 - message: &format!("{server_name}: db error; please try again later..\n") 77 - }).async_write(&mut stream).await?; 78 - return Ok(()); 79 - }, 80 - }, 81 - Err(_) => { 82 - RpcMessage::Fail(RpcFail { 83 - code: 0, 84 - message: &format!("{server_name}: query took too long to execute.\n"), 85 - }).async_write(&mut stream).await?; 86 - debug!("pull query timeout"); 87 - return Err(Error::Timeout); 88 - } 89 - }; 90 - let pulled_funcs = funcs.iter().filter(|v| v.is_some()).count(); 91 - state.metrics.pulls.inc_by(pulled_funcs as _); 92 - state.metrics.queried_funcs.inc_by(md.funcs.len() as _); 93 - debug!("pull {pulled_funcs}/{} funcs ended after {:?}", md.funcs.len(), start.elapsed()); 94 - 95 - let statuses: Vec<u32> = funcs.iter().map(|v| u32::from(v.is_none())).collect(); 96 - let found = funcs 97 - .into_iter() 98 - .flatten() 99 - .map(|v| { 100 - rpc::PullMetadataResultFunc { 101 - popularity: v.popularity, 102 - len: v.len, 103 - name: Cow::Owned(v.name), 104 - mb_data: Cow::Owned(v.data), 105 - } 106 - }).collect(); 107 - 108 - RpcMessage::PullMetadataResult(rpc::PullMetadataResult{ 109 - unk0: Cow::Owned(statuses), 110 - funcs: Cow::Owned(found), 111 - }).async_write(&mut stream).await?; 112 - }, 113 - RpcMessage::PushMetadata(mds) => { 114 - // parse the function's metadata 115 - let start = Instant::now(); 116 - let scores: Vec<u32> = mds.funcs.iter() 117 - .map(md::get_score) 118 - .collect(); 119 - 120 - let status = match db.push_funcs(user, &mds, &scores).await { 121 - Ok(v) => { 122 - v.into_iter().map(u32::from).collect::<Vec<u32>>() 123 - }, 124 - Err(err) => { 125 - log::error!("push failed, db: {}", err); 126 - rpc::RpcMessage::Fail(rpc::RpcFail { 127 - code: 0, 128 - message: &format!("{server_name}: db error; please try again later.\n") 129 - }).async_write(&mut stream).await?; 130 - return Ok(()); 131 - } 132 - }; 133 - state.metrics.pushes.inc_by(status.len() as _); 134 - let new_funcs = status 135 - .iter() 136 - .fold(0u64, |counter, &v| if v > 0 { counter + 1 } else {counter}); 137 - state.metrics.new_funcs.inc_by(new_funcs); 138 - debug!("push {} funcs ended after {:?} ({new_funcs} new)", status.len(), start.elapsed()); 139 - 140 - RpcMessage::PushMetadataResult(rpc::PushMetadataResult { 141 - status: Cow::Owned(status), 142 - }).async_write(&mut stream).await?; 143 - }, 144 - RpcMessage::DelHistory(req) => { 145 - let is_delete_allowed = state.config.lumina.allow_deletes.unwrap_or(false); 146 - if !is_delete_allowed { 147 - RpcMessage::Fail(rpc::RpcFail { 148 - code: 2, 149 - message: &format!("{server_name}: Delete command is disabled on this server.") 150 - }).async_write(&mut stream).await?; 151 - } else { 152 - if let Err(err) = db.delete_metadata(&req).await { 153 - error!("delete failed. db: {err}"); 154 - RpcMessage::Fail(rpc::RpcFail { 155 - code: 3, 156 - message: &format!("{server_name}: db error, please try again later.") 157 - }).async_write(&mut stream).await?; 158 - return Ok(()); 159 - } 160 - RpcMessage::DelHistoryResult(rpc::DelHistoryResult { 161 - deleted_mds: req.funcs.len() as u32, 162 - }).async_write(&mut stream).await?; 163 - } 164 - }, 165 - _ => { 166 - RpcMessage::Fail(rpc::RpcFail{code: 0, message: &format!("{server_name}: invalid data.\n")}).async_write(&mut stream).await?; 167 - } 168 - } 169 - Ok(()) 170 - } 171 - 172 - async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>(state: &SharedState, mut stream: S) -> Result<(), rpc::Error> { 173 - let server_name = &state.server_name; 174 - let hello = match timeout(Duration::from_secs(15), rpc::read_packet(&mut stream)).await { 175 - Ok(v) => v?, 176 - Err(_) => { 177 - debug!("didn't get hello in time."); 178 - return Ok(()); 179 - }, 180 - }; 181 - 182 - let (hello, creds) = match RpcMessage::deserialize(&hello) { 183 - Ok(RpcMessage::Hello(v, creds)) => { 184 - debug!("hello protocol={}, login creds: {creds:?}", v.protocol_version); 185 - (v, creds) 186 - }, 187 - _ => { 188 - // send error 189 - error!("got bad hello message"); 190 - 191 - let resp = rpc::RpcFail{ code: 0, message: &format!("{server_name}: bad sequence.") }; 192 - let resp = rpc::RpcMessage::Fail(resp); 193 - resp.async_write(&mut stream).await?; 194 - 195 - return Ok(()); 196 - } 197 - }; 198 - state.metrics.lumina_version.get_or_create(&LuminaVersion { 199 - protocol_version: hello.protocol_version, 200 - }).inc(); 201 - 202 - if let Some(ref creds) = creds { 203 - if creds.username != "guest" { 204 - // Only allow "guest" to connect for now. 205 - rpc::RpcMessage::Fail(rpc::RpcFail { 206 - code: 1, 207 - message: &format!("{server_name}: invalid username or password. Try logging in with `guest` instead."), 208 - }).async_write(&mut stream).await?; 209 - return Ok(()); 210 - } 211 - } 212 - 213 - let resp = match hello.protocol_version { 214 - 0..=4 => rpc::RpcMessage::Ok(()), 215 - 216 - // starting IDA 8.3 217 - 5.. => rpc::RpcMessage::HelloResult(HelloResult { 218 - unk0: "".into(), 219 - unk1: "".into(), 220 - unk2: "".into(), 221 - unk3: "".into(), 222 - unk4: 0, 223 - unk5: 0, 224 - unk6: 0, 225 - }) 226 - }; 227 - resp.async_write(&mut stream).await?; 228 - 229 - loop { 230 - handle_transaction(state, &hello, &mut stream).await?; 231 - } 232 - } 233 - 234 - async fn handle_connection<S: AsyncRead + AsyncWrite + Unpin>(state: &SharedState, s: S) { 235 - if let Err(err) = handle_client(state, s).await { 236 - if discriminant(&err) != discriminant(&Error::Eof) { 237 - warn!("err: {}", err); 238 - } 239 - } 240 - } 241 - 242 - async fn serve(listener: TcpListener, accpt: Option<tokio_native_tls::TlsAcceptor>, state: SharedState, mut shutdown_signal: tokio::sync::oneshot::Receiver<()>) { 243 - let accpt = accpt.map(Arc::new); 244 - 245 - let (async_drop, worker) = AsyncDropper::new(); 246 - tokio::task::spawn(worker); 247 - 248 - let connections = Arc::new(tokio::sync::Mutex::new(HashMap::<std::net::SocketAddr, tokio::task::JoinHandle<()>>::new())); 249 - 250 - loop { 251 - let (client, addr) = tokio::select! { 252 - _ = &mut shutdown_signal => { 253 - drop(state); 254 - info!("shutting down..."); 255 - let m = connections.lock().await; 256 - m.iter().for_each(|(k, v)| { 257 - debug!("aborting task for {k}..."); 258 - v.abort(); 259 - }); 260 - return; 261 - }, 262 - res = listener.accept() => match res { 263 - Ok(v) => v, 264 - Err(err) => { 265 - warn!("failed to accept(): {}", err); 266 - continue; 267 - } 268 - }, 269 - }; 270 - 271 - let start = Instant::now(); 272 - 273 - let state = state.clone(); 274 - let accpt = accpt.clone(); 275 - 276 - let conns2 = connections.clone(); 277 - let counter = state.metrics.active_connections.clone(); 278 - let guard = async_drop.defer(async move { 279 - let count = counter.dec() - 1; 280 - debug!("connection with {:?} ended after {:?}; {} active connections", addr, start.elapsed(), count); 281 - 282 - let mut guard = conns2.lock().await; 283 - if guard.remove(&addr).is_none() { 284 - error!("Couldn't remove connection from set {addr}"); 285 - } 286 - }); 287 - 288 - let counter = state.metrics.active_connections.clone(); 289 - let handle = tokio::spawn(async move { 290 - let _guard = guard; 291 - let count = { 292 - counter.inc() + 1 293 - }; 294 - let protocol = if accpt.is_some() {" [TLS]"} else {""}; 295 - debug!("Connection from {:?}{}: {} active connections", &addr, protocol, count); 296 - match accpt { 297 - Some(accpt) => { 298 - match timeout(Duration::from_secs(10), accpt.accept(client)).await { 299 - Ok(r) => match r { 300 - Ok(s) => { 301 - handle_connection(&state, s).await; 302 - }, 303 - Err(err) => debug!("tls accept ({}): {}", &addr, err), 304 - }, 305 - Err(_) => { 306 - debug!("client {} didn't complete ssl handshake in time.", &addr); 307 - }, 308 - }; 309 - }, 310 - None => handle_connection(&state, client).await, 311 - } 312 - }); 313 - 314 - let mut guard = connections.lock().await; 315 - guard.insert(addr, handle); 316 - } 317 - } 318 - 319 - fn main() { 320 - setup_logger(); 24 + #[tokio::main] 25 + async fn main() { 321 26 let matches = clap::Command::new("lumen") 322 27 .version(env!("CARGO_PKG_VERSION")) 323 28 .about("lumen is a private Lumina server for IDA.\nVisit https://github.com/naim94a/lumen/ for updates.") ··· 325 30 .arg( 326 31 Arg::new("config") 327 32 .short('c') 328 - .required(true) 329 33 .default_value("config.toml") 330 34 .help("Configuration file path") 331 35 ) 36 + .subcommand( 37 + Command::new("users") 38 + .about("User Management") 39 + .subcommand( 40 + Command::new("add") 41 + .about("Adds a user") 42 + .arg( 43 + Arg::new("username") 44 + .required(true) 45 + ) 46 + .arg( 47 + Arg::new("email") 48 + .required(true) 49 + ) 50 + .arg( 51 + Arg::new("is_admin") 52 + .required(false) 53 + .default_value("no") 54 + .value_parser(BoolishValueParser::new()) 55 + ) 56 + ) 57 + .subcommand( 58 + Command::new("del") 59 + .about("Deletes a user") 60 + .arg(Arg::new("username")) 61 + ) 62 + ) 63 + .subcommand(Command::new("passwd").about("Set user password").arg(Arg::new("username").required(true))) 332 64 .get_matches(); 333 65 334 66 let config = { 335 - config::load_config(std::fs::File::open(matches.get_one::<String>("config").unwrap()).expect("failed to read config")) 67 + common::config::load_config( 68 + std::fs::File::open(matches.get_one::<String>("config").unwrap()) 69 + .expect("failed to read config"), 70 + ) 336 71 }; 337 72 let config = Arc::new(config); 338 73 339 - info!("starting private lumen server..."); 340 - 341 - let rt = match tokio::runtime::Builder::new_multi_thread() 342 - .enable_all() 343 - .build() { 344 - Ok(v) => v, 345 - Err(err) => { 346 - error!("failed to create tokio runtime: {}", err); 347 - exit(1); 74 + match matches.subcommand() { 75 + Some(("users", m)) => { 76 + let users = UserMgmt::new(&config).await; 77 + match m.subcommand() { 78 + None => users.list_users().await, 79 + Some(("add", m)) => { 80 + let username = m.get_one::<String>("username").unwrap(); 81 + let email = m.get_one::<String>("email").unwrap(); 82 + let is_admin = *m.get_one::<bool>("is_admin").unwrap_or(&false); 83 + users.add_user(username, email, is_admin).await; 84 + }, 85 + Some(("del", m)) => { 86 + let username = m.get_one::<String>("username").unwrap(); 87 + users.delete_user(username).await; 88 + }, 89 + _ => unreachable!(), 90 + }; 91 + }, 92 + Some(("passwd", m)) => { 93 + let username = m.get_one::<String>("username").unwrap(); 94 + let password = rpassword::prompt_password("New Password: ").unwrap(); 95 + let users = UserMgmt::new(&config).await; 96 + users.set_password(username, &password).await; 97 + }, 98 + Some(_) => unreachable!(), 99 + None => { 100 + setup_logger(); 101 + do_lumen(config).await 348 102 }, 349 103 }; 350 - 351 - let db = rt.block_on(async { 352 - match Database::open(&config.database).await { 353 - Ok(v) => v, 354 - Err(err) => { 355 - error!("failed to open database: {}", err); 356 - exit(1); 357 - } 358 - } 359 - }); 360 - 361 - let server_name = config.lumina.server_name.clone().unwrap_or_else(|| String::from("lumen")); 362 - 363 - let state = Arc::new(SharedState_{ 364 - db, 365 - config, 366 - server_name, 367 - metrics: common::metrics::Metrics::default(), 368 - }); 369 - 370 - let tls_acceptor; 371 - 372 - if state.config.lumina.use_tls.unwrap_or_default() { 373 - let cert_path = &state.config.lumina.tls.as_ref().expect("tls section is missing").server_cert; 374 - let mut crt = match std::fs::read(cert_path) { 375 - Ok(v) => v, 376 - Err(err) => { 377 - error!("failed to read certificate file: {}", err); 378 - exit(1); 379 - } 380 - }; 381 - let pkcs_passwd = std::env::var("PKCSPASSWD").unwrap_or_default(); 382 - let id = match Identity::from_pkcs12(&crt, &pkcs_passwd) { 383 - Ok(v) => v, 384 - Err(err) => { 385 - error!("failed to parse tls certificate: {}", err); 386 - exit(1); 387 - } 388 - }; 389 - let _ = pkcs_passwd; 390 - crt.iter_mut().for_each(|v| *v = 0); 391 - let _ = crt; 392 - let mut accpt = native_tls::TlsAcceptor::builder(id); 393 - accpt.min_protocol_version(Some(native_tls::Protocol::Sslv3)); 394 - let accpt = match accpt.build() { 395 - Ok(v) => v, 396 - Err(err) => { 397 - error!("failed to build tls acceptor: {}", err); 398 - exit(1); 399 - }, 400 - }; 401 - let accpt = tokio_native_tls::TlsAcceptor::from(accpt); 402 - tls_acceptor = Some(accpt); 403 - } else { 404 - tls_acceptor = None; 405 - } 406 - 407 - let web_handle = if let Some(ref webcfg) = state.config.api_server { 408 - let bind_addr = webcfg.bind_addr; 409 - let state = state.clone(); 410 - info!("starting http api server on {:?}", &bind_addr); 411 - Some(rt.spawn(async move { 412 - web::start_webserver(bind_addr, state).await; 413 - })) 414 - } else { 415 - None 416 - }; 417 - 418 - let (exit_signal_tx, exit_signal_rx) = tokio::sync::oneshot::channel::<()>(); 419 - 420 - let async_server = async move { 421 - let server = match TcpListener::bind(state.config.lumina.bind_addr).await { 422 - Ok(v) => v, 423 - Err(err) => { 424 - error!("failed to bind server port: {}", err); 425 - exit(1); 426 - }, 427 - }; 428 - 429 - info!("listening on {:?} secure={}", server.local_addr().unwrap(), tls_acceptor.is_some()); 430 - 431 - serve(server, tls_acceptor, state, exit_signal_rx).await; 432 - }; 433 - 434 - rt.block_on(async { 435 - let server_handle = tokio::task::spawn(async_server); 436 - tokio::signal::ctrl_c().await.unwrap(); 437 - debug!("CTRL-C; exiting..."); 438 - if let Some(handle) = web_handle { 439 - handle.abort(); 440 - } 441 - exit_signal_tx.send(()).unwrap(); 442 - server_handle.await.unwrap(); 443 - }); 444 - drop(rt); 445 - info!("Goodbye."); 446 104 }
+651
lumen/src/server.rs
··· 1 + use std::{ 2 + borrow::Cow, 3 + collections::HashMap, 4 + mem::discriminant, 5 + process::exit, 6 + sync::Arc, 7 + time::{Duration, Instant}, 8 + }; 9 + 10 + use common::{ 11 + async_drop::AsyncDropper, 12 + config::Config, 13 + db::{self, Database}, 14 + make_pretty_hex, md, 15 + metrics::LuminaVersion, 16 + rpc::{self, Creds, Error, HelloResult, RpcFail, RpcHello, RpcMessage}, 17 + SharedState, SharedState_, 18 + }; 19 + use log::{debug, error, info, trace, warn}; 20 + use native_tls::Identity; 21 + use tokio::{ 22 + io::{AsyncRead, AsyncWrite}, 23 + net::TcpListener, 24 + time::timeout, 25 + }; 26 + 27 + use crate::web; 28 + 29 + struct Session<'a> { 30 + state: &'a SharedState_, 31 + hello_msg: RpcHello<'a>, 32 + _creds: db::schema::Creds<'a>, 33 + creds_id: Option<i32>, 34 + last_cred_check: Instant, 35 + } 36 + impl<'a> Session<'a> { 37 + /// Check if the user changed in the database. 38 + pub async fn is_valid(&mut self) -> bool { 39 + let db = &self.state.db; 40 + if let Some(cred_id) = self.creds_id { 41 + if self.last_cred_check.elapsed() > Duration::from_secs(60 * 5) { 42 + match db.get_user_by_id(cred_id).await { 43 + Ok(v) => { 44 + if !v.is_enabled 45 + || v.is_admin != self._creds.is_admin 46 + || v.passwd_salt != self._creds.passwd_salt 47 + { 48 + // user changed, force them to login again. 49 + return false; 50 + } 51 + self.last_cred_check = Instant::now(); 52 + return true; 53 + }, 54 + Err(err) => { 55 + error!("db error: {err}"); 56 + return false; 57 + }, 58 + } 59 + } 60 + } 61 + true 62 + } 63 + } 64 + 65 + async fn handle_transaction<'a, S: AsyncRead + AsyncWrite + Unpin>( 66 + session: &mut Session<'a>, mut stream: S, 67 + ) -> Result<(), Error> { 68 + let state = session.state; 69 + let db = &state.db; 70 + let server_name = state.server_name.as_str(); 71 + 72 + trace!("waiting for command.."); 73 + let rpkt = async { 74 + let hdr = rpc::read_packet_header(&mut stream).await?; 75 + // we don't want to read a whole request just to find out the user was revoked... 76 + if !session.is_valid().await { 77 + return Err(Error::Timeout); 78 + } 79 + hdr.read(&mut stream).await 80 + }; 81 + let req = match timeout(Duration::from_secs(3600), rpkt).await { 82 + Ok(res) => match res { 83 + Ok(v) => v, 84 + Err(e) => return Err(e), 85 + }, 86 + Err(_) => { 87 + _ = RpcMessage::Fail(RpcFail { 88 + result: 0, 89 + error: &format!("{server_name} client idle for too long.\n"), 90 + }) 91 + .async_write(&mut stream) 92 + .await; 93 + return Err(Error::Timeout); 94 + }, 95 + }; 96 + trace!("got command!"); 97 + 98 + if !session.is_valid().await { 99 + return Err(Error::Timeout); 100 + } 101 + 102 + let req = match RpcMessage::deserialize(&req) { 103 + Ok(v) => v, 104 + Err(err) => { 105 + warn!("bad message: \n{}\n", make_pretty_hex(&req)); 106 + error!("failed to process rpc message: {}", err); 107 + let resp = rpc::RpcFail { 108 + result: 0, 109 + error: &format!("{server_name}: error: invalid data.\n"), 110 + }; 111 + let resp = RpcMessage::Fail(resp); 112 + resp.async_write(&mut stream).await?; 113 + 114 + return Ok(()); 115 + }, 116 + }; 117 + match req { 118 + RpcMessage::PullMetadata(md) => { 119 + let start = Instant::now(); 120 + let funcs = match timeout(Duration::from_secs(4 * 60), db.get_funcs(&md.pattern_ids)) 121 + .await 122 + { 123 + Ok(r) => match r { 124 + Ok(v) => v, 125 + Err(e) => { 126 + error!("pull failed, db: {}", e); 127 + rpc::RpcMessage::Fail(rpc::RpcFail { 128 + result: 0, 129 + error: &format!("{server_name}: db error; please try again later..\n"), 130 + }) 131 + .async_write(&mut stream) 132 + .await?; 133 + return Ok(()); 134 + }, 135 + }, 136 + Err(_) => { 137 + RpcMessage::Fail(RpcFail { 138 + result: 0, 139 + error: &format!("{server_name}: query took too long to execute.\n"), 140 + }) 141 + .async_write(&mut stream) 142 + .await?; 143 + debug!("pull query timeout"); 144 + return Err(Error::Timeout); 145 + }, 146 + }; 147 + let pulled_funcs = funcs.iter().filter(|v| v.is_some()).count(); 148 + state.metrics.pulls.inc_by(pulled_funcs as _); 149 + state.metrics.queried_funcs.inc_by(md.pattern_ids.len() as _); 150 + debug!( 151 + "pull {pulled_funcs}/{} funcs ended after {:?}", 152 + md.pattern_ids.len(), 153 + start.elapsed() 154 + ); 155 + 156 + let statuses: Vec<u32> = funcs.iter().map(|v| u32::from(v.is_none())).collect(); 157 + let found = funcs 158 + .into_iter() 159 + .flatten() 160 + .map(|v| rpc::PullMetadataResultFunc { 161 + popularity: v.popularity, 162 + len: v.len, 163 + name: Cow::Owned(v.name), 164 + mb_data: Cow::Owned(v.data), 165 + }) 166 + .collect(); 167 + 168 + RpcMessage::PullMetadataResult(rpc::PullMetadataResult { 169 + codes: Cow::Owned(statuses), 170 + funcs: Cow::Owned(found), 171 + }) 172 + .async_write(&mut stream) 173 + .await?; 174 + }, 175 + RpcMessage::PushMetadata(mds) => { 176 + // parse the function's metadata 177 + let start = Instant::now(); 178 + let scores: Vec<u32> = mds.funcs.iter().map(md::get_score).collect(); 179 + 180 + let status = 181 + match db.push_funcs(&session.hello_msg, session.creds_id, &mds, &scores).await { 182 + Ok(v) => v.into_iter().map(u32::from).collect::<Vec<u32>>(), 183 + Err(err) => { 184 + log::error!("push failed, db: {}", err); 185 + rpc::RpcMessage::Fail(rpc::RpcFail { 186 + result: 0, 187 + error: &format!("{server_name}: db error; please try again later.\n"), 188 + }) 189 + .async_write(&mut stream) 190 + .await?; 191 + return Ok(()); 192 + }, 193 + }; 194 + state.metrics.pushes.inc_by(status.len() as _); 195 + let new_funcs = 196 + status.iter().fold(0u64, |counter, &v| if v > 0 { counter + 1 } else { counter }); 197 + state.metrics.new_funcs.inc_by(new_funcs); 198 + debug!( 199 + "push {} funcs ended after {:?} ({new_funcs} new)", 200 + status.len(), 201 + start.elapsed() 202 + ); 203 + 204 + RpcMessage::PushMetadataResult(rpc::PushMetadataResult { status: Cow::Owned(status) }) 205 + .async_write(&mut stream) 206 + .await?; 207 + }, 208 + RpcMessage::DelHistory(req) => { 209 + let is_delete_allowed = state.config.lumina.allow_deletes.unwrap_or(false); 210 + if !is_delete_allowed { 211 + RpcMessage::Fail(rpc::RpcFail { 212 + result: 2, 213 + error: &format!("{server_name}: Delete command is disabled on this server."), 214 + }) 215 + .async_write(&mut stream) 216 + .await?; 217 + } else { 218 + if let Err(err) = db.delete_metadata(&req).await { 219 + error!("delete failed. db: {err}"); 220 + RpcMessage::Fail(rpc::RpcFail { 221 + result: 3, 222 + error: &format!("{server_name}: db error, please try again later."), 223 + }) 224 + .async_write(&mut stream) 225 + .await?; 226 + return Ok(()); 227 + } 228 + RpcMessage::DelHistoryResult(rpc::DelHistoryResult { 229 + ndeleted: req.calcrel_hashes.len() as u32, 230 + }) 231 + .async_write(&mut stream) 232 + .await?; 233 + } 234 + }, 235 + RpcMessage::GetFuncHistories(req) => { 236 + let limit = state.config.lumina.get_history_limit.unwrap_or(0); 237 + 238 + if limit == 0 { 239 + RpcMessage::Fail(rpc::RpcFail { 240 + result: 4, 241 + error: &format!( 242 + "{server_name}: function histories are disabled on this server." 243 + ), 244 + }) 245 + .async_write(&mut stream) 246 + .await?; 247 + return Ok(()); 248 + } 249 + 250 + let mut statuses = vec![]; 251 + let mut res = vec![]; 252 + for chksum in req.funcs.iter().map(|v| v.data) { 253 + let history = match db.get_func_histories(chksum, limit).await { 254 + Ok(v) => v, 255 + Err(err) => { 256 + error!("failed to get function histories: {err:?}"); 257 + RpcMessage::Fail(rpc::RpcFail { 258 + result: 3, 259 + error: &format!("{server_name}: db error, please try again later."), 260 + }) 261 + .async_write(&mut stream) 262 + .await?; 263 + return Ok(()); 264 + }, 265 + }; 266 + let status = !history.is_empty() as u32; 267 + statuses.push(status); 268 + if history.is_empty() { 269 + continue; 270 + } 271 + let log = history 272 + .into_iter() 273 + .map(|(updated, name, metadata)| rpc::FunctionHistory { 274 + unk0: 0, 275 + unk1: 0, 276 + name: Cow::Owned(name), 277 + metadata: Cow::Owned(metadata), 278 + timestamp: updated.unix_timestamp() as u64, 279 + author_idx: 0, 280 + idb_path_idx: 0, 281 + }) 282 + .collect::<Vec<_>>(); 283 + res.push(rpc::FunctionHistories { log: Cow::Owned(log) }); 284 + } 285 + 286 + trace!("returning {} histories", res.len()); 287 + 288 + RpcMessage::GetFuncHistoriesResult(rpc::GetFuncHistoriesResult { 289 + status: statuses.into(), 290 + funcs: Cow::Owned(res), 291 + authors: vec![].into(), 292 + idb_paths: vec![].into(), 293 + }) 294 + .async_write(&mut stream) 295 + .await?; 296 + }, 297 + _ => { 298 + RpcMessage::Fail(rpc::RpcFail { 299 + result: 0, 300 + error: &format!("{server_name}: invalid data.\n"), 301 + }) 302 + .async_write(&mut stream) 303 + .await?; 304 + }, 305 + } 306 + Ok(()) 307 + } 308 + 309 + async fn http_reply<W: AsyncRead + AsyncWrite + Unpin>(mut stream: W) { 310 + use tokio::io::AsyncWriteExt; 311 + 312 + const HTTP_REPLY: &str = concat!( 313 + "HTTP/1.1 400 bad request\r\n", 314 + "Refresh: 2; URL=https://github.com/naim94a/lumen\r\n", 315 + "Server: lumen\r\n", 316 + "Connection: close\r\n", 317 + "Content-Type: text/html\r\n", 318 + "", 319 + "\r\n", 320 + "<pre>This is not an HTTP server. <br />Redirecting to <a href=\"https://github.com/naim94a/lumen\">lumen</a> ...</pre>\n", 321 + ); 322 + 323 + let _ = stream.write_all(HTTP_REPLY.as_bytes()).await; 324 + } 325 + 326 + async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>( 327 + state: &SharedState, mut stream: S, 328 + ) -> Result<(), rpc::Error> { 329 + let server_name = &state.server_name; 330 + let rpkt = async { 331 + let hdr = rpc::read_packet_header(&mut stream).await?; 332 + if hdr.is_http() { 333 + // looks like someone is using a browser instead of IDA, what a fool. 334 + debug!("ignoring http request..."); 335 + http_reply(&mut stream).await; 336 + return Err(Error::Eof); 337 + } 338 + hdr.read(&mut stream).await 339 + }; 340 + let hello = match timeout(Duration::from_secs(15), rpkt).await { 341 + Ok(v) => v?, 342 + Err(_) => { 343 + debug!("didn't get hello in time."); 344 + return Ok(()); 345 + }, 346 + }; 347 + 348 + let (hello, creds) = match RpcMessage::deserialize(&hello) { 349 + Ok(RpcMessage::Hello(v, creds)) => { 350 + debug!("hello protocol={}, login creds: {creds:?}", v.client_version); 351 + (v, creds) 352 + }, 353 + _ => { 354 + // send error 355 + error!("got bad hello message"); 356 + 357 + let resp = rpc::RpcFail { result: 0, error: &format!("{server_name}: bad sequence.") }; 358 + let resp = rpc::RpcMessage::Fail(resp); 359 + resp.async_write(&mut stream).await?; 360 + 361 + return Ok(()); 362 + }, 363 + }; 364 + state 365 + .metrics 366 + .lumina_version 367 + .get_or_create(&LuminaVersion { protocol_version: hello.client_version }) 368 + .inc(); 369 + 370 + let creds = creds.unwrap_or(Creds { username: "guest", password: "guest" }); 371 + 372 + let user = if creds.username != "guest" { 373 + match state.db.get_user_by_username(creds.username).await { 374 + Ok((user_id, db_creds)) if db_creds.verify_password(creds.password) => { 375 + let _ = state.db.update_last_active(user_id).await; 376 + info!("{} logged in successfully.", db_creds.username); 377 + (user_id, db_creds) 378 + }, 379 + Ok(_) => { 380 + rpc::RpcMessage::Fail(rpc::RpcFail { 381 + result: 1, 382 + error: &format!("{server_name}: invalid username or password."), 383 + }) 384 + .async_write(&mut stream) 385 + .await?; 386 + return Ok(()); 387 + }, 388 + Err(err) => { 389 + error!("error while fetching user information: {err}"); 390 + rpc::RpcMessage::Fail(rpc::RpcFail { 391 + result: 1, 392 + error: &format!("{server_name}: internal error, please try again later."), 393 + }) 394 + .async_write(&mut stream) 395 + .await?; 396 + return Ok(()); 397 + }, 398 + } 399 + } else { 400 + ( 401 + -1, 402 + db::schema::Creds { 403 + username: creds.username.into(), 404 + is_enabled: state.config.users.allow_guests, 405 + ..Default::default() 406 + }, 407 + ) 408 + }; 409 + 410 + if !user.1.is_enabled { 411 + info!("attempt to login to disabled account [{}].", user.1.username); 412 + rpc::RpcMessage::Fail(rpc::RpcFail { 413 + result: 1, 414 + error: &format!("{server_name}: account disabled."), 415 + }) 416 + .async_write(&mut stream) 417 + .await?; 418 + return Ok(()); 419 + } 420 + 421 + let resp = match hello.client_version { 422 + 0..=4 => rpc::RpcMessage::Ok(()), 423 + 424 + // starting IDA 8.3 425 + 5.. => { 426 + let mut features = 0; 427 + 428 + if user.1.is_admin { 429 + features |= 0x01; 430 + } 431 + 432 + if user.0 != -1 && state.config.lumina.allow_deletes.unwrap_or(false) { 433 + features |= 0x02; 434 + } 435 + 436 + let last_active = user.1.last_active.map_or(0, |v| v.unix_timestamp() as u64); 437 + 438 + rpc::RpcMessage::HelloResult(HelloResult { 439 + username: Cow::Borrowed(&user.1.username), 440 + last_active, 441 + features, 442 + ..Default::default() 443 + }) 444 + }, 445 + }; 446 + resp.async_write(&mut stream).await?; 447 + 448 + let creds_id = if user.0 == -1 { None } else { Some(user.0) }; 449 + let mut session = Session { 450 + state, 451 + hello_msg: hello, 452 + _creds: user.1, 453 + creds_id, 454 + last_cred_check: Instant::now(), 455 + }; 456 + 457 + loop { 458 + handle_transaction(&mut session, &mut stream).await?; 459 + } 460 + } 461 + 462 + async fn handle_connection<S: AsyncRead + AsyncWrite + Unpin>(state: &SharedState, s: S) { 463 + if let Err(err) = handle_client(state, s).await { 464 + if discriminant(&err) != discriminant(&Error::Eof) { 465 + warn!("err: {}", err); 466 + } 467 + } 468 + } 469 + 470 + async fn serve( 471 + listener: TcpListener, accpt: Option<tokio_native_tls::TlsAcceptor>, state: SharedState, 472 + mut shutdown_signal: tokio::sync::oneshot::Receiver<()>, 473 + ) { 474 + let accpt = accpt.map(Arc::new); 475 + 476 + let (async_drop, worker) = AsyncDropper::new(); 477 + tokio::task::spawn(worker); 478 + 479 + let connections = Arc::new(tokio::sync::Mutex::new(HashMap::< 480 + std::net::SocketAddr, 481 + tokio::task::JoinHandle<()>, 482 + >::new())); 483 + 484 + loop { 485 + let (client, addr) = tokio::select! { 486 + _ = &mut shutdown_signal => { 487 + drop(state); 488 + info!("shutting down..."); 489 + let m = connections.lock().await; 490 + m.iter().for_each(|(k, v)| { 491 + debug!("aborting task for {k}..."); 492 + v.abort(); 493 + }); 494 + return; 495 + }, 496 + res = listener.accept() => match res { 497 + Ok(v) => v, 498 + Err(err) => { 499 + warn!("failed to accept(): {}", err); 500 + continue; 501 + } 502 + }, 503 + }; 504 + 505 + let start = Instant::now(); 506 + 507 + let state = state.clone(); 508 + let accpt = accpt.clone(); 509 + 510 + let conns2 = connections.clone(); 511 + let counter = state.metrics.active_connections.clone(); 512 + let guard = async_drop.defer(async move { 513 + let count = counter.dec() - 1; 514 + debug!( 515 + "connection with {:?} ended after {:?}; {} active connections", 516 + addr, 517 + start.elapsed(), 518 + count 519 + ); 520 + 521 + let mut guard = conns2.lock().await; 522 + if guard.remove(&addr).is_none() { 523 + error!("Couldn't remove connection from set {addr}"); 524 + } 525 + }); 526 + 527 + let counter = state.metrics.active_connections.clone(); 528 + let handle = tokio::spawn(async move { 529 + let _guard = guard; 530 + let count = { counter.inc() + 1 }; 531 + let protocol = if accpt.is_some() { " [TLS]" } else { "" }; 532 + debug!("Connection from {:?}{}: {} active connections", &addr, protocol, count); 533 + match accpt { 534 + Some(accpt) => { 535 + match timeout(Duration::from_secs(10), accpt.accept(client)).await { 536 + Ok(r) => match r { 537 + Ok(s) => { 538 + handle_connection(&state, s).await; 539 + }, 540 + Err(err) => debug!("tls accept ({}): {}", &addr, err), 541 + }, 542 + Err(_) => { 543 + debug!("client {} didn't complete ssl handshake in time.", &addr); 544 + }, 545 + }; 546 + }, 547 + None => handle_connection(&state, client).await, 548 + } 549 + }); 550 + 551 + let mut guard = connections.lock().await; 552 + guard.insert(addr, handle); 553 + } 554 + } 555 + 556 + pub(crate) async fn do_lumen(config: Arc<Config>) { 557 + info!("starting private lumen server..."); 558 + 559 + let db = match Database::open(&config.database).await { 560 + Ok(v) => v, 561 + Err(err) => { 562 + error!("failed to open database: {}", err); 563 + exit(1); 564 + }, 565 + }; 566 + 567 + let server_name = config.lumina.server_name.clone().unwrap_or_else(|| String::from("lumen")); 568 + 569 + let state = Arc::new(SharedState_ { 570 + db, 571 + config, 572 + server_name, 573 + metrics: common::metrics::Metrics::default(), 574 + }); 575 + 576 + let tls_acceptor; 577 + 578 + if state.config.lumina.use_tls.unwrap_or_default() { 579 + let cert_path = 580 + &state.config.lumina.tls.as_ref().expect("tls section is missing").server_cert; 581 + let mut crt = match std::fs::read(cert_path) { 582 + Ok(v) => v, 583 + Err(err) => { 584 + error!("failed to read certificate file: {}", err); 585 + exit(1); 586 + }, 587 + }; 588 + let pkcs_passwd = std::env::var("PKCSPASSWD").unwrap_or_default(); 589 + let id = match Identity::from_pkcs12(&crt, &pkcs_passwd) { 590 + Ok(v) => v, 591 + Err(err) => { 592 + error!("failed to parse tls certificate: {}", err); 593 + exit(1); 594 + }, 595 + }; 596 + let _ = pkcs_passwd; 597 + crt.iter_mut().for_each(|v| *v = 0); 598 + let _ = crt; 599 + let mut accpt = native_tls::TlsAcceptor::builder(id); 600 + accpt.min_protocol_version(Some(native_tls::Protocol::Sslv3)); 601 + let accpt = match accpt.build() { 602 + Ok(v) => v, 603 + Err(err) => { 604 + error!("failed to build tls acceptor: {}", err); 605 + exit(1); 606 + }, 607 + }; 608 + let accpt = tokio_native_tls::TlsAcceptor::from(accpt); 609 + tls_acceptor = Some(accpt); 610 + } else { 611 + tls_acceptor = None; 612 + } 613 + 614 + let web_handle = if let Some(ref webcfg) = state.config.api_server { 615 + let bind_addr = webcfg.bind_addr; 616 + let state = state.clone(); 617 + info!("starting http api server on {:?}", &bind_addr); 618 + Some(tokio::spawn(async move { 619 + web::start_webserver(bind_addr, state).await; 620 + })) 621 + } else { 622 + None 623 + }; 624 + 625 + let (exit_signal_tx, exit_signal_rx) = tokio::sync::oneshot::channel::<()>(); 626 + 627 + let async_server = async move { 628 + let server = match TcpListener::bind(state.config.lumina.bind_addr).await { 629 + Ok(v) => v, 630 + Err(err) => { 631 + error!("failed to bind server port: {}", err); 632 + exit(1); 633 + }, 634 + }; 635 + 636 + info!("listening on {:?} secure={}", server.local_addr().unwrap(), tls_acceptor.is_some()); 637 + 638 + serve(server, tls_acceptor, state, exit_signal_rx).await; 639 + }; 640 + 641 + let server_handle = tokio::task::spawn(async_server); 642 + tokio::signal::ctrl_c().await.unwrap(); 643 + debug!("CTRL-C; exiting..."); 644 + if let Some(handle) = web_handle { 645 + handle.abort(); 646 + } 647 + exit_signal_tx.send(()).unwrap(); 648 + server_handle.await.unwrap(); 649 + 650 + info!("Goodbye."); 651 + }
+43
lumen/src/users.rs
··· 1 + use std::process::exit; 2 + 3 + use common::{config::Config, db::Database}; 4 + 5 + pub struct UserMgmt { 6 + db: Database, 7 + pbkd2_iters: u32, 8 + } 9 + 10 + impl UserMgmt { 11 + pub async fn new(cfg: &Config) -> Self { 12 + let db = match Database::open(&cfg.database).await { 13 + Ok(v) => v, 14 + Err(err) => { 15 + eprintln!("failed to open database: {}", err); 16 + exit(1); 17 + }, 18 + }; 19 + 20 + Self { db, pbkd2_iters: cfg.users.pbkdf2_iterations.get() } 21 + } 22 + 23 + pub async fn list_users(&self) { 24 + let users = self.db.get_users().await.expect("failed to retreive users from database"); 25 + println!("{users:?}"); 26 + } 27 + 28 + pub async fn set_password(&self, username: &str, password: &str) { 29 + self.db 30 + .set_password(username, password.to_owned(), self.pbkd2_iters) 31 + .await 32 + .expect("failed to set user's password") 33 + } 34 + 35 + pub async fn add_user(&self, username: &str, email: &str, is_admin: bool) { 36 + let id = self.db.add_user(username, email, is_admin).await.expect("failed to add user"); 37 + println!("{username}'s id is {id}.") 38 + } 39 + 40 + pub async fn delete_user(&self, username: &str) { 41 + self.db.delete_user(username).await.expect("failed to delete user"); 42 + } 43 + }
+33 -35
lumen/src/web.rs
··· 1 - use std::net::SocketAddr; 2 - 3 - use log::error; 4 - use warp::{Filter, hyper::StatusCode, reply::Response}; 5 - use common::{SharedState, web::api::api_root}; 6 - 7 - pub async fn start_webserver<A: Into<SocketAddr> + 'static>(bind_addr: A, shared_state: SharedState) { 8 - let root = warp::get() 9 - .and(warp::path::end()) 10 - .map(|| warp::reply::html(include_str!("home.html"))); 11 - 12 - let shared_state1 = shared_state.clone(); 13 - let api = warp::path("api") 14 - .and(api_root(shared_state1)); 15 - 16 - let metrics = warp::get().and(warp::path("metrics")).and(warp::path::end()) 17 - .map(move || { 18 - let mut res = String::new(); 19 - if let Err(err) = prometheus_client::encoding::text::encode(&mut res, &shared_state.metrics.registry) { 20 - error!("failed to encode metrics: {err}"); 21 - let mut r = Response::default(); 22 - *r.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 23 - r 24 - } else { 25 - warp::reply::Response::new(res.into()) 26 - } 27 - }); 28 - 29 - let routes = root 30 - .or(api) 31 - .or(metrics); 32 - 33 - warp::serve(routes) 34 - .run(bind_addr).await; 35 - } 1 + use std::net::SocketAddr; 2 + 3 + use common::{web::api::api_root, SharedState}; 4 + use log::error; 5 + use warp::{hyper::StatusCode, reply::Response, Filter}; 6 + 7 + pub async fn start_webserver<A: Into<SocketAddr> + 'static>( 8 + bind_addr: A, shared_state: SharedState, 9 + ) { 10 + let root = 11 + warp::get().and(warp::path::end()).map(|| warp::reply::html(include_str!("home.html"))); 12 + 13 + let shared_state1 = shared_state.clone(); 14 + let api = warp::path("api").and(api_root(shared_state1)); 15 + 16 + let metrics = warp::get().and(warp::path("metrics")).and(warp::path::end()).map(move || { 17 + let mut res = String::new(); 18 + if let Err(err) = 19 + prometheus_client::encoding::text::encode(&mut res, &shared_state.metrics.registry) 20 + { 21 + error!("failed to encode metrics: {err}"); 22 + let mut r = Response::default(); 23 + *r.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; 24 + r 25 + } else { 26 + warp::reply::Response::new(res.into()) 27 + } 28 + }); 29 + 30 + let routes = root.or(api).or(metrics); 31 + 32 + warp::serve(routes).run(bind_addr).await; 33 + }
+5
rustfmt.toml
··· 1 + edition = "2021" 2 + fn_params_layout = "Compressed" 3 + match_block_trailing_comma = true 4 + newline_style = "Unix" 5 + use_small_heuristics = "Max"