├── .github ├── CODEOWNERS └── workflows │ ├── Python-CI.yaml │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── RDB_File_Format.md └── RDB_Version_History.md ├── pyproject.toml ├── src ├── constants.rs ├── decoder │ ├── common │ │ ├── listpack.rs │ │ ├── mod.rs │ │ ├── utils.rs │ │ └── ziplist.rs │ ├── hash.rs │ ├── list.rs │ ├── mod.rs │ ├── rdb.rs │ ├── set.rs │ └── sorted_set.rs ├── filter.rs ├── formatter │ ├── json.rs │ ├── mod.rs │ ├── nil.rs │ ├── plain.rs │ └── protocol.rs ├── lib.rs ├── main.rs └── types.rs ├── tests ├── dumps │ ├── README.md │ ├── dictionary.rdb │ ├── easily_compressible_string_key.rdb │ ├── empty_database.rdb │ ├── hash_as_ziplist.rdb │ ├── hash_list_pack.rdb │ ├── integer_keys.rdb │ ├── intset_16.rdb │ ├── intset_32.rdb │ ├── intset_64.rdb │ ├── json │ │ ├── dictionary.json │ │ ├── easily_compressible_string_key.json │ │ ├── empty_database.json │ │ ├── hash_as_ziplist.json │ │ ├── hash_list_pack.json │ │ ├── integer_keys.json │ │ ├── intset_16.json │ │ ├── intset_32.json │ │ ├── intset_64.json │ │ ├── keys_with_expiry.json │ │ ├── linkedlist.json │ │ ├── multidb-skipping.json │ │ ├── multiple_databases.json │ │ ├── parser_filters.json │ │ ├── quicklist_with_multiple_nodes.json │ │ ├── quicklist_with_one_node.json │ │ ├── rdb_version_5_with_checksum.json │ │ ├── regular_set.json │ │ ├── regular_sorted_set.json │ │ ├── sorted_set_as_ziplist.json │ │ ├── uncompressible_string_keys.json │ │ ├── ziplist_that_compresses_easily.json │ │ ├── ziplist_that_doesnt_compress.json │ │ ├── ziplist_with_integers.json │ │ ├── zipmap_that_compresses_easily.json │ │ ├── zipmap_that_doesnt_compress.json │ │ └── zipmap_with_big_values.json │ ├── keys_with_expiry.rdb │ ├── linkedlist.rdb │ ├── multidb-skipping.rdb │ ├── multiple_databases.rdb │ ├── parser_filters.rdb │ ├── plain │ │ ├── dictionary.plain │ │ ├── easily_compressible_string_key.plain │ │ ├── empty_database.plain │ │ ├── hash_as_ziplist.plain │ │ ├── hash_list_pack.plain │ │ ├── integer_keys.plain │ │ ├── intset_16.plain │ │ ├── intset_32.plain │ │ ├── intset_64.plain │ │ ├── keys_with_expiry.plain │ │ ├── linkedlist.plain │ │ ├── multidb-skipping.plain │ │ ├── multiple_databases.plain │ │ ├── parser_filters.plain │ │ ├── quicklist_with_multiple_nodes.plain │ │ ├── quicklist_with_one_node.plain │ │ ├── rdb_version_5_with_checksum.plain │ │ ├── regular_set.plain │ │ ├── regular_sorted_set.plain │ │ ├── sorted_set_as_ziplist.plain │ │ ├── uncompressible_string_keys.plain │ │ ├── ziplist_that_compresses_easily.plain │ │ ├── ziplist_that_doesnt_compress.plain │ │ ├── ziplist_with_integers.plain │ │ ├── zipmap_that_compresses_easily.plain │ │ ├── zipmap_that_doesnt_compress.plain │ │ └── zipmap_with_big_values.plain │ ├── protocol │ │ ├── dictionary.protocol │ │ ├── easily_compressible_string_key.protocol │ │ ├── empty_database.protocol │ │ ├── hash_as_ziplist.protocol │ │ ├── hash_list_pack.protocol │ │ ├── integer_keys.protocol │ │ ├── intset_16.protocol │ │ ├── intset_32.protocol │ │ ├── intset_64.protocol │ │ ├── keys_with_expiry.protocol │ │ ├── linkedlist.protocol │ │ ├── multidb-skipping.protocol │ │ ├── multiple_databases.protocol │ │ ├── parser_filters.protocol │ │ ├── quicklist_with_multiple_nodes.protocol │ │ ├── quicklist_with_one_node.protocol │ │ ├── rdb_version_5_with_checksum.protocol │ │ ├── regular_set.protocol │ │ ├── regular_sorted_set.protocol │ │ ├── sorted_set_as_ziplist.protocol │ │ ├── uncompressible_string_keys.protocol │ │ ├── ziplist_that_compresses_easily.protocol │ │ ├── ziplist_that_doesnt_compress.protocol │ │ ├── ziplist_with_integers.protocol │ │ ├── zipmap_that_compresses_easily.protocol │ │ ├── zipmap_that_doesnt_compress.protocol │ │ └── zipmap_with_big_values.protocol │ ├── quicklist_with_multiple_nodes.rdb │ ├── quicklist_with_one_node.rdb │ ├── rdb_version_5_with_checksum.rdb │ ├── regular_set.rdb │ ├── regular_sorted_set.rdb │ ├── sorted_set_as_ziplist.rdb │ ├── uncompressible_string_keys.rdb │ ├── ziplist_that_compresses_easily.rdb │ ├── ziplist_that_doesnt_compress.rdb │ ├── ziplist_with_integers.rdb │ ├── zipmap_that_compresses_easily.rdb │ ├── zipmap_that_doesnt_compress.rdb │ └── zipmap_with_big_values.rdb └── integration_tests.rs ├── utils └── PKGBUILD └── uv.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @bimtauer 2 | -------------------------------------------------------------------------------- /.github/workflows/Python-CI.yaml: -------------------------------------------------------------------------------- 1 | # This file is autogenerated by maturin v1.8.1 2 | # To update, run 3 | # 4 | # maturin generate-ci github --platform linux --platform windows --platform macos 5 | # 6 | name: CI 7 | 8 | on: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | tags: 14 | - '*' 15 | pull_request: 16 | workflow_dispatch: 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | linux: 23 | runs-on: ${{ matrix.platform.runner }} 24 | strategy: 25 | matrix: 26 | platform: 27 | - runner: ubuntu-22.04 28 | target: x86_64 29 | - runner: ubuntu-22.04 30 | target: x86 31 | - runner: ubuntu-22.04 32 | target: aarch64 33 | - runner: ubuntu-22.04 34 | target: armv7 35 | - runner: ubuntu-22.04 36 | target: s390x 37 | - runner: ubuntu-22.04 38 | target: ppc64le 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions/setup-python@v5 42 | with: 43 | python-version: 3.x 44 | - name: Build wheels 45 | uses: PyO3/maturin-action@v1 46 | with: 47 | target: ${{ matrix.platform.target }} 48 | args: --release --out dist --find-interpreter 49 | sccache: 'true' 50 | manylinux: auto 51 | - name: Upload wheels 52 | uses: actions/upload-artifact@v4 53 | with: 54 | name: wheels-linux-${{ matrix.platform.target }} 55 | path: dist 56 | 57 | windows: 58 | runs-on: ${{ matrix.platform.runner }} 59 | strategy: 60 | matrix: 61 | platform: 62 | - runner: windows-latest 63 | target: x64 64 | - runner: windows-latest 65 | target: x86 66 | steps: 67 | - uses: actions/checkout@v4 68 | - uses: actions/setup-python@v5 69 | with: 70 | python-version: 3.x 71 | architecture: ${{ matrix.platform.target }} 72 | - name: Build wheels 73 | uses: PyO3/maturin-action@v1 74 | with: 75 | target: ${{ matrix.platform.target }} 76 | args: --release --out dist --find-interpreter 77 | sccache: 'true' 78 | - name: Upload wheels 79 | uses: actions/upload-artifact@v4 80 | with: 81 | name: wheels-windows-${{ matrix.platform.target }} 82 | path: dist 83 | 84 | macos: 85 | runs-on: ${{ matrix.platform.runner }} 86 | strategy: 87 | matrix: 88 | platform: 89 | - runner: macos-13 90 | target: x86_64 91 | - runner: macos-14 92 | target: aarch64 93 | steps: 94 | - uses: actions/checkout@v4 95 | - uses: actions/setup-python@v5 96 | with: 97 | python-version: 3.x 98 | - name: Build wheels 99 | uses: PyO3/maturin-action@v1 100 | with: 101 | target: ${{ matrix.platform.target }} 102 | args: --release --out dist --find-interpreter 103 | sccache: 'true' 104 | - name: Upload wheels 105 | uses: actions/upload-artifact@v4 106 | with: 107 | name: wheels-macos-${{ matrix.platform.target }} 108 | path: dist 109 | 110 | sdist: 111 | runs-on: ubuntu-latest 112 | steps: 113 | - uses: actions/checkout@v4 114 | - name: Build sdist 115 | uses: PyO3/maturin-action@v1 116 | with: 117 | command: sdist 118 | args: --out dist 119 | - name: Upload sdist 120 | uses: actions/upload-artifact@v4 121 | with: 122 | name: wheels-sdist 123 | path: dist 124 | 125 | release: 126 | name: Release 127 | runs-on: ubuntu-latest 128 | if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} 129 | needs: [linux, windows, macos, sdist] 130 | permissions: 131 | # Use to sign the release artifacts 132 | id-token: write 133 | # Used to upload release artifacts 134 | contents: write 135 | # Used to generate artifact attestation 136 | attestations: write 137 | steps: 138 | - uses: actions/download-artifact@v4 139 | - name: Generate artifact attestation 140 | uses: actions/attest-build-provenance@v1 141 | with: 142 | subject-path: 'wheels-*/*' 143 | - name: Publish to PyPI 144 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 145 | uses: PyO3/maturin-action@v1 146 | with: 147 | command: upload 148 | args: --non-interactive --skip-existing wheels-*/* 149 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Cargo Build & Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build_and_test: 12 | name: Rust project - latest 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | toolchain: 17 | - stable 18 | - beta 19 | - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: setup toolchain 23 | run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} 24 | 25 | - name: Cache cargo 26 | uses: Swatinem/rust-cache@v2 27 | 28 | - name: cargo build 29 | run: cargo build --verbose 30 | 31 | - name: cargo test 32 | run: cargo test --verbose 33 | 34 | - name: Check formatting 35 | if: matrix.toolchain == 'stable' 36 | run: cargo fmt --all -- --check 37 | 38 | - name: Clippy 39 | if: matrix.toolchain == 'stable' 40 | run: cargo clippy --all --all-features --tests -- -D warnings 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Python 13 | .python-version 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Added 11 | - New RDB version and Datatypes 12 | - Rust based integration tests 13 | - Fixtures for protocol and plain output 14 | - Redis integration test covering 6.2 - 7.4 15 | - Option to output to file 16 | - Error handling with thiserror 17 | - Support for new encoding types: 18 | - listpack 19 | - quicklist 20 | - sorted set v2 21 | - Python bindings with Maturin 22 | 23 | ### Changed 24 | - Ported CLI to clap 25 | - Encoding of non-ascii characters - previously escaped, resulting in possible duplicate json keys, now as hex string 26 | - Separated decoding and formatting logic 27 | 28 | ### Removed 29 | - Previous docs and build pipeline 30 | 31 | 32 | --- 33 | # Previous: 34 | 35 | ### 0.2.1 - 2016-08-03 36 | 37 | * Bug fix: Correctly handle skipping blobs 38 | * Fix: Pin dependency versions 39 | 40 | ### 0.2.0 - 2015-04-04 41 | 42 | * Make it work on Rust 1.0 beta 43 | 44 | ### 0.1.0 - 2015-03-23 45 | 46 | Initial release with basic functionality 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rdb" 3 | edition = "2021" 4 | version = "0.3.0" 5 | authors = ["Jan-Erik Rediger ", "Tim Bauer "] 6 | keywords = ["redis", "database", "rdb", "parser"] 7 | description = "Fast and efficient RDB parsing utility" 8 | readme = "README.md" 9 | license = "MIT" 10 | documentation = "https://docs.rs/rdb/" 11 | repository = "https://github.com/bimtauer/rdb-rs" 12 | 13 | include = [ 14 | "Cargo.toml", 15 | "README*", 16 | "LICENSE*", 17 | "src/**/*", 18 | "tests/**/*", 19 | "examples/**/*", 20 | ] 21 | 22 | [[bin]] 23 | name = "rdb" 24 | path = "src/main.rs" 25 | doc = false 26 | 27 | [lib] 28 | name = "rdb" 29 | crate-type = ["cdylib", "rlib"] 30 | 31 | [dependencies] 32 | lzf = "1.0" 33 | rustc-serialize = "0.3" 34 | regex = "1.11" 35 | byteorder = "1.5" 36 | thiserror = "2.0" 37 | pyo3 = { version = "0.24.0", features = ["extension-module"], optional = true } 38 | clap = { version = "4.5", features = ["derive"] } 39 | indexmap = "2.8.0" 40 | 41 | [dev-dependencies] 42 | tokio = { version = "1.44", features = ["full"] } 43 | pretty_assertions = "1.4.1" 44 | redis = "0.29.2" 45 | rstest = "0.25.0" 46 | testcontainers = "0.23.1" 47 | testcontainers-modules = { version = "0.11.4", features = ["redis"] } 48 | tempfile = "3.19.1" 49 | assert_cmd = "2.0.16" 50 | 51 | [features] 52 | default = [] 53 | python = ["pyo3"] 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jan-Erik Rediger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rdb-rs - RDB parsing, formatting, analyzing. All in one library 2 | 3 | --- 4 | 5 | **2024-03-29: THIS CODEBASE IS NOW MAINTAINED AT ** 6 | 7 | --- 8 | 9 | See [changelog](CHANGELOG.md). 10 | 11 | Inspired and based on [redis-rdb-tools][]. 12 | 13 | ## Documentation 14 | 15 | TBD 16 | 17 | ## Build 18 | 19 | ``` 20 | cargo build --release 21 | ``` 22 | 23 | ## Basic operation 24 | 25 | rdb-rs exposes just one important method: `parse`. 26 | This methods takes care of reading the RDB from a stream, 27 | parsing the containted data and calling the provided formatter with already-parsed values. 28 | 29 | ```rust 30 | use std::io::BufReader; 31 | use std::fs::File; 32 | use std::path::Path; 33 | 34 | let file = File::open(&Path::new("dump.rdb")).unwrap(); 35 | let reader = BufReader::new(file); 36 | rdb::parse(reader, rdb::formatter::JSON::new(), rdb::filter::Simple::new()); 37 | ``` 38 | 39 | ### Formatter 40 | 41 | rdb-rs brings 4 pre-defined formatters, which can be used: 42 | 43 | * `Plain`: Just plain output for testing 44 | * `JSON`: JSON-encoded output 45 | * `Nil`: Surpresses all output 46 | * `Protocol`: Formats the data in [RESP][], 47 | the Redis Serialization Protocol 48 | 49 | These formatters adhere to the `Formatter` trait and supply a method for each possible datatype or opcode. 50 | Its up to the formatter to correctly handle all provided data such as lists, sets, hashes, expires and metadata. 51 | 52 | ### Command-line 53 | 54 | rdb-rs brings a Command Line application as well. 55 | 56 | This application will take a RDB file as input and format it in the specified format (JSON by default). 57 | 58 | Example: 59 | 60 | ``` 61 | $ rdb --format json dump.rdb 62 | [{"key":"value"}] 63 | $ rdb --format protocol dump.rdb 64 | *2 65 | $6 66 | SELECT 67 | $1 68 | 0 69 | *3 70 | $3 71 | SET 72 | $3 73 | key 74 | $5 75 | value 76 | ``` 77 | 78 | ## Tests 79 | 80 | Run tests with: 81 | 82 | ``` 83 | make test 84 | ``` 85 | 86 | This will run the code tests with cargo as well as checking that it can parse all included dump files. 87 | 88 | ## Contribute 89 | 90 | If you find bugs or want to help otherwise, please [open an issue][issues]. 91 | 92 | ## License 93 | 94 | MIT. See [LICENSE](LICENSE). 95 | 96 | [redis-rdb-tools]: https://github.com/sripathikrishnan/redis-rdb-tools 97 | [RESP]: http://redis.io/topics/protocol 98 | [issues]: https://github.com/bimtauer/rdb-rs/issues 99 | [doc]: https://docs.rs/rdb/ 100 | -------------------------------------------------------------------------------- /docs/RDB_Version_History.md: -------------------------------------------------------------------------------- 1 | % RDB Version History 2 | ## RDB Version History 3 | 4 | This document tracks the changes made to the dump file format over time. 5 | 6 | An RDB file is forwards compatible. An older dump file format will always work with a newer version of Redis. 7 | 8 | ## Version 7 9 | 10 | Introduced 2014-01-08, integrated into Redis 2.9.x. 11 | 12 | * New opcode: `RESIZEDB` (251). This encodes hash tables sizes to allow for faster loading. 13 | Followed by two length-encoded integers indicating: 14 | * Database hash table size 15 | * Expiry hash table size 16 | * New opcode: `AUX` (250). This allows for arbitrary key-value settings. Unknown keys are ignored. 17 | Followed by two length-prefixed strings representing the key and value of the setting. Currently implemented fields: 18 | * `redis-ver`: The Redis Version that wrote the RDB 19 | * `redis-bits`: Bit architecture of the system that wrote the RDB, either 32 or 64 20 | * `ctime`: Creation time of the RDB 21 | * `used-mem`: Used memory of the instance that wrote the RDB 22 | * New encoding type `LIST_QUICKLIST` (14) 23 | 24 | Relevant links: 25 | 26 | * opcode `AUX`: [redis#206cd219](https://github.com/antirez/redis/commit/206cd219b63c2255c0238cb9c602b65f05e98120), [redis#4c0e8923](https://github.com/antirez/redis/commit/4c0e8923a6cb376c7b2a53fa76ae95f74610285c) 27 | * opcode `RESIZEDB`: [redis#e8614a1a](https://github.com/antirez/redis/commit/e8614a1a77d2989f7be3cb7b24cd88b01f14f17e) 28 | * new type `LIST_QUICKLIST`: [redis#101b3a6e](https://github.com/antirez/redis/commit/101b3a6e42e84e5cb423ef413225d8b8d8cc0bbc), [Redis Quicklist: Do you even list?](https://matt.sh/redis-quicklist-visions) 29 | 30 | **Caution**: This breaks backwards-compatibility. Redis 2.8 cannot load a RDB version 7 file. 31 | 32 | ## Version 6 33 | 34 | In previous versions, ziplists used a variable length encoding scheme for integers. 35 | Integers were stored in 16, 32 or 64 bits. In this version, this variable length 36 | encoding system has been extended. 37 | 38 | The following additions have been made : 39 | 40 | * Integers 0 through 12, both inclusive, are now encoded as part of the entry header 41 | * Numbers between -128 and 127, both inclusive, are stored in 1 byte 42 | * Numbers between -2^23 and 2^23 -1, both inclusive, are stored in 3 bytes 43 | 44 | Issue ID: [redis#469](https://github.com/antirez/redis/issues/469) 45 | 46 | To migrate to version 5: 47 | 48 | * In redis.conf, set `list-max-ziplist-entries` to 0 49 | * Restart Redis Server, and issue the `SAVE` command 50 | * Edit the dump.rdb file, and change the rdb version in the header to `REDIS0005` 51 | 52 | 53 | ## Version 5 54 | 55 | This version introduced an 8 byte checksum (CRC64) at the end of the file. If checksum is disabled in redis.conf, 56 | the last 8 bytes will be zeroes. 57 | 58 | Issue ID: [redis#366](https://github.com/antirez/redis/issues/366) 59 | 60 | To migrate to version 4: 61 | 62 | * Delete the last 8 bytes of the file (i.e. after the byte `0xFF`) 63 | * Change the rdb version in the header to `REDIS0004` 64 | 65 | 66 | ## Version 4 67 | 68 | This version introduced a new encoding for hashmaps - "Hashmaps encoded as Zip Lists". This version also deprecates 69 | the Zipmap encoding that was used in previous versions. 70 | 71 | "Hashmaps encoded as ziplists" has encoding type = 13. The value is parsed like a ziplist, and adjacent entries 72 | in the list are considered key=value pairs in the hashmap. 73 | 74 | Issue ID: [redis#285](https://github.com/antirez/redis/pull/285) 75 | 76 | To migrate to version 3: 77 | 78 | * In redis.conf, set `hash-max-ziplist-entries` to 0 79 | * Restart Redis Server, and issue the `SAVE` command 80 | * Edit the dump.rdb file, and change the rdb version in the header to `REDIS0003` 81 | 82 | ## Version 3 83 | 84 | This version introduced key expiry with millisecond precision. 85 | 86 | Earlier versions stored key expiry in the format `0xFD <4 byte timestamp>`. In version 3, key expiry is stored as 87 | `0xFC <8 byte timestamp>`. Here, 0xFD and 0xFC are the opcodes to indicate key expiry in seconds and milliseconds respectively. 88 | 89 | Issue ID: [redis#169](https://github.com/antirez/redis/issues/169) 90 | 91 | To migrate to version 2: 92 | 93 | * If you don't use key expiry, simply change the version in the header to `REDIS0002` 94 | * If you use key expiry, you can still migrate, but there will be some loss in expiry precision. Also, the migration is a bit involved. 95 | * For each key=value pair in the dump file, you will have to convert `0xFC <8 byte timestamp>` to `0xFD <4 byte timestamp>`. 96 | * After converting the timestamps, change the version in the header to `REDIS0002` 97 | 98 | ## Version 2 99 | 100 | This version introduced special encoding for small hashmaps, lists and sets. 101 | 102 | Specifically, it introduced the following encoding types: 103 | 104 | REDIS_RDB_TYPE_HASH_ZIPMAP = 9 105 | REDIS_RDB_TYPE_LIST_ZIPLIST = 10 106 | REDIS_RDB_TYPE_SET_INTSET = 11 107 | REDIS_RDB_TYPE_ZSET_ZIPLIST = 12 108 | 109 | Commit: [redis#6b52ad87](https://github.com/antirez/redis/commit/6b52ad87c05ca2162a2d21f1f5b5329bf52a7678) 110 | 111 | To migrate to version 1: 112 | 113 | * In redis.conf, set the following properties to 0 `hash-max-zipmap-entries, list-max-ziplist-entries, set-max-intset-entries, zset-max-ziplist-entries` 114 | * Restart Redis, and issue the SAVE command 115 | * Edit the dump.rdb file, and change the rdb version in the header to `REDIS0001` 116 | 117 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rdb-py" 3 | version = "0.3.0" 4 | description = "Python wrapper around the fast and efficient RDB parsing utility" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "maturin>=1.8.0", 9 | "cffi==1.17.1" 10 | ] 11 | classifiers = [ 12 | "Programming Language :: Rust", 13 | "Programming Language :: Python :: Implementation :: CPython", 14 | "Programming Language :: Python :: Implementation :: PyPy", 15 | ] 16 | 17 | 18 | [build-system] 19 | requires = ["maturin>=1,<2"] 20 | build-backend = "maturin" 21 | 22 | [tool.maturin] 23 | features = ["python"] 24 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | pub mod version { 2 | pub const SUPPORTED_MINIMUM: u32 = 1; 3 | pub const SUPPORTED_MAXIMUM: u32 = 12; 4 | } 5 | 6 | pub mod constant { 7 | pub const RDB_6BITLEN: u8 = 0; 8 | pub const RDB_14BITLEN: u8 = 1; 9 | pub const RDB_ENCVAL: u8 = 3; 10 | pub const RDB_MAGIC: &str = "REDIS"; 11 | } 12 | 13 | pub mod op_code { 14 | pub const MODULE_AUX: u8 = 247; 15 | pub const IDLE: u8 = 248; 16 | pub const FREQ: u8 = 249; 17 | pub const AUX: u8 = 250; 18 | pub const RESIZEDB: u8 = 251; 19 | pub const EXPIRETIME_MS: u8 = 252; 20 | pub const EXPIRETIME: u8 = 253; 21 | pub const SELECTDB: u8 = 254; 22 | pub const EOF: u8 = 255; 23 | } 24 | 25 | pub mod encoding_type { 26 | pub const STRING: u8 = 0; 27 | pub const LIST: u8 = 1; 28 | pub const SET: u8 = 2; 29 | pub const ZSET: u8 = 3; 30 | pub const HASH: u8 = 4; 31 | pub const ZSET_2: u8 = 5; 32 | pub const MODULE: u8 = 6; 33 | pub const MODULE_2: u8 = 7; 34 | pub const HASH_ZIPMAP: u8 = 9; 35 | pub const LIST_ZIPLIST: u8 = 10; 36 | pub const SET_INTSET: u8 = 11; 37 | pub const ZSET_ZIPLIST: u8 = 12; 38 | pub const HASH_ZIPLIST: u8 = 13; 39 | pub const LIST_QUICKLIST: u8 = 14; 40 | pub const STREAM_LIST_PACKS: u8 = 15; 41 | pub const HASH_LIST_PACK: u8 = 16; 42 | pub const ZSET_LIST_PACK: u8 = 17; 43 | pub const LIST_QUICKLIST_2: u8 = 18; 44 | pub const STREAM_LIST_PACKS_2: u8 = 19; 45 | pub const SET_LIST_PACK: u8 = 20; 46 | pub const STREAM_LIST_PACKS_3: u8 = 21; 47 | } 48 | 49 | pub mod encoding { 50 | pub const INT8: u32 = 0; 51 | pub const INT16: u32 = 1; 52 | pub const INT32: u32 = 2; 53 | pub const LZF: u32 = 3; 54 | } 55 | -------------------------------------------------------------------------------- /src/decoder/common/listpack.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{RdbError, RdbResult}; 2 | use byteorder::ReadBytesExt; 3 | use std::io::Read; 4 | 5 | /// Skip the backlen field in a listpack entry 6 | /// The backlen field is used to traverse the listpack backwards 7 | fn skip_backlen(reader: &mut R, element_len: u32) -> RdbResult<()> { 8 | let backlen = if element_len <= 127 { 9 | 1 10 | } else if element_len < (1 << 14) - 1 { 11 | 2 12 | } else if element_len < (1 << 21) - 1 { 13 | 3 14 | } else if element_len < (1 << 28) - 1 { 15 | 4 16 | } else { 17 | 5 18 | }; 19 | 20 | let mut buf = vec![0; backlen]; 21 | reader.read_exact(&mut buf)?; 22 | Ok(()) 23 | } 24 | 25 | /// Read a single entry from a listpack as a string 26 | /// Format (first 2 bits): 27 | /// 00/01: 7-bit integer 28 | /// 10: string with 6-bit length 29 | /// 11: complex encoding (integers or strings) 30 | pub fn read_list_pack_entry_as_string(reader: &mut R) -> RdbResult> { 31 | let header = reader.read_u8()?; 32 | 33 | match header >> 6 { 34 | 0 | 1 => { 35 | let val = (header & 0x7F) as i8; 36 | skip_backlen(reader, 1)?; 37 | Ok(val.to_string().into_bytes()) 38 | } 39 | 2 => { 40 | let str_len = (header & 0x3F) as usize; 41 | let mut result = vec![0; str_len]; 42 | reader.read_exact(&mut result)?; 43 | 44 | let content_len = 1 + str_len; 45 | skip_backlen(reader, content_len as u32)?; 46 | 47 | Ok(result) 48 | } 49 | 3 => match header >> 4 { 50 | 12 | 13 => { 51 | let next = reader.read_u8()?; 52 | let mut val = (((header & 0x1F) as u16) << 8) | (next as u16); 53 | if val >= 1 << 12 { 54 | val = !(8191 - val); 55 | } 56 | skip_backlen(reader, 2)?; 57 | Ok(val.to_string().into_bytes()) 58 | } 59 | 14 => { 60 | let len_high = (header & 0x0F) as u16; 61 | let len_low = reader.read_u8()? as u16; 62 | let str_len = ((len_high << 8) | len_low) as usize; 63 | 64 | let mut result = vec![0; str_len]; 65 | reader.read_exact(&mut result)?; 66 | 67 | skip_backlen(reader, (2 + str_len) as u32)?; 68 | Ok(result) 69 | } 70 | _ => match header & 0x0F { 71 | 0 => { 72 | let mut len_bytes = [0u8; 4]; 73 | reader.read_exact(&mut len_bytes)?; 74 | let str_len = u32::from_le_bytes(len_bytes) as usize; 75 | 76 | let mut result = vec![0; str_len]; 77 | reader.read_exact(&mut result)?; 78 | 79 | skip_backlen(reader, (5 + str_len) as u32)?; 80 | Ok(result) 81 | } 82 | 1..=4 => { 83 | let size = match header & 0x0F { 84 | 1 => 2, 85 | 2 => 3, 86 | 3 => 4, 87 | 4 => 8, 88 | _ => unreachable!(), 89 | }; 90 | let mut int_bytes = vec![0; size]; 91 | reader.read_exact(&mut int_bytes)?; 92 | 93 | let val = match size { 94 | 2 => i16::from_le_bytes(int_bytes.try_into().unwrap()) as i64, 95 | 3 => { 96 | let mut bytes = [0u8; 4]; 97 | bytes[..3].copy_from_slice(&int_bytes); 98 | i32::from_le_bytes(bytes) as i64 >> 8 99 | } 100 | 4 => i32::from_le_bytes(int_bytes.try_into().unwrap()) as i64, 101 | 8 => i64::from_le_bytes(int_bytes.try_into().unwrap()), 102 | _ => unreachable!(), 103 | }; 104 | 105 | skip_backlen(reader, (size + 1) as u32)?; 106 | Ok(val.to_string().into_bytes()) 107 | } 108 | 15 => Err(RdbError::MissingValue("listpack entry")), 109 | _ => Err(RdbError::ParsingError { 110 | context: "read_list_pack_entry_as_string", 111 | message: format!("Unknown encoding value: {}", header), 112 | }), 113 | }, 114 | }, 115 | _ => unreachable!(), 116 | } 117 | } 118 | 119 | pub fn read_list_pack_length(buf: &[u8], cursor: &mut usize) -> usize { 120 | let _total_bytes = u32::from_le_bytes(buf[*cursor..*cursor + 4].try_into().unwrap()) as usize; 121 | *cursor += 4; 122 | let count = u16::from_le_bytes(buf[*cursor..*cursor + 2].try_into().unwrap()) as usize; 123 | *cursor += 2; 124 | count 125 | } 126 | -------------------------------------------------------------------------------- /src/decoder/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod listpack; 2 | pub mod utils; 3 | mod ziplist; 4 | 5 | pub use listpack::{read_list_pack_entry_as_string, read_list_pack_length}; 6 | pub use ziplist::{read_ziplist_entry_string, read_ziplist_metadata}; 7 | -------------------------------------------------------------------------------- /src/decoder/common/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::types::RdbError; 2 | use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; 3 | use lzf; 4 | use std::io::Read; 5 | 6 | #[doc(hidden)] 7 | use crate::constants::{constant, encoding, version}; 8 | 9 | #[doc(hidden)] 10 | pub use crate::types::{RdbOk, RdbResult}; 11 | 12 | pub fn read_length_with_encoding(input: &mut R) -> RdbResult<(u32, bool)> { 13 | let length; 14 | let mut is_encoded = false; 15 | 16 | let enc_type = input.read_u8()?; 17 | 18 | match (enc_type & 0xC0) >> 6 { 19 | constant::RDB_ENCVAL => { 20 | is_encoded = true; 21 | length = (enc_type & 0x3F) as u32; 22 | } 23 | constant::RDB_6BITLEN => { 24 | length = (enc_type & 0x3F) as u32; 25 | } 26 | constant::RDB_14BITLEN => { 27 | let next_byte = input.read_u8()?; 28 | length = (((enc_type & 0x3F) as u32) << 8) | next_byte as u32; 29 | } 30 | _ => { 31 | length = input.read_u32::()?; 32 | } 33 | } 34 | 35 | Ok((length, is_encoded)) 36 | } 37 | 38 | pub fn read_length(input: &mut R) -> RdbResult { 39 | let (length, _) = read_length_with_encoding(input)?; 40 | Ok(length) 41 | } 42 | 43 | pub fn verify_magic(input: &mut R) -> RdbOk { 44 | let mut magic = [0; 5]; 45 | match input.read(&mut magic) { 46 | Ok(5) => (), 47 | Ok(_) => return Err(RdbError::MissingValue("magic bytes")), 48 | Err(e) => return Err(RdbError::Io(e)), 49 | }; 50 | 51 | if magic == constant::RDB_MAGIC.as_bytes() { 52 | Ok(()) 53 | } else { 54 | Err(RdbError::MissingValue("invalid magic string")) 55 | } 56 | } 57 | 58 | pub fn verify_version(input: &mut R) -> RdbOk { 59 | let mut buf = [0u8; 4]; 60 | input.read_exact(&mut buf)?; 61 | 62 | // Check if all characters are ASCII digits 63 | for &byte in &buf { 64 | if !byte.is_ascii_digit() { 65 | return Err(RdbError::MissingValue("invalid version number")); 66 | } 67 | } 68 | 69 | // Convert ASCII string to number (e.g., "0003" -> 3) 70 | let version_str = std::str::from_utf8(&buf).unwrap(); 71 | let version = version_str.parse::().unwrap(); 72 | 73 | // Check if version is in supported range 74 | let is_ok = (version::SUPPORTED_MINIMUM..=version::SUPPORTED_MAXIMUM).contains(&version); 75 | 76 | if !is_ok { 77 | return Err(RdbError::MissingValue("unsupported version")); 78 | } 79 | 80 | Ok(()) 81 | } 82 | 83 | pub fn read_blob(input: &mut R) -> RdbResult> { 84 | let (length, is_encoded) = read_length_with_encoding(input)?; 85 | 86 | if is_encoded { 87 | let result = match length { 88 | encoding::INT8 => int_to_vec(i32::from(input.read_i8()?)), 89 | encoding::INT16 => int_to_vec(i32::from(input.read_i16::()?)), 90 | encoding::INT32 => int_to_vec(input.read_i32::()?), 91 | encoding::LZF => { 92 | let compressed_length = read_length(input)?; 93 | let real_length = read_length(input)?; 94 | let data = read_exact(input, compressed_length as usize)?; 95 | lzf::decompress(&data, real_length as usize).unwrap() 96 | } 97 | _ => { 98 | panic!("Unknown encoding: {}", length) 99 | } 100 | }; 101 | 102 | Ok(result) 103 | } else { 104 | read_exact(input, length as usize) 105 | } 106 | } 107 | 108 | pub fn int_to_vec(number: i32) -> Vec { 109 | let number = number.to_string(); 110 | let mut result = Vec::with_capacity(number.len()); 111 | for &c in number.as_bytes().iter() { 112 | result.push(c); 113 | } 114 | result 115 | } 116 | 117 | pub fn read_exact(reader: &mut T, len: usize) -> RdbResult> { 118 | let mut buf = vec![0; len]; 119 | reader.read_exact(&mut buf)?; 120 | 121 | Ok(buf) 122 | } 123 | 124 | pub fn read_sequence(input: &mut R, mut transform: F) -> RdbResult> 125 | where 126 | F: FnMut(&mut R) -> RdbResult, 127 | { 128 | let mut len = read_length(input)?; 129 | let mut values = Vec::with_capacity(len as usize); 130 | 131 | while len > 0 { 132 | values.push(transform(input)?); 133 | len -= 1; 134 | } 135 | 136 | Ok(values) 137 | } 138 | 139 | #[cfg(test)] 140 | mod tests { 141 | use super::*; 142 | use rstest::*; 143 | use std::io::Cursor; 144 | 145 | #[rstest] 146 | #[case(&[0x0], (0, false), 1)] 147 | #[case(&[0x7f, 0xff], (16383, false), 2)] 148 | #[case(&[0x80, 0xff, 0xff, 0xff, 0xff], (4294967295, false), 5)] 149 | #[case(&[0xC0], (0, true), 1)] 150 | fn test_read_length( 151 | #[case] input: &[u8], 152 | #[case] expected: (u32, bool), 153 | #[case] expected_position: u64, 154 | ) { 155 | let mut cursor = Cursor::new(Vec::from(input)); 156 | assert_eq!(expected, read_length_with_encoding(&mut cursor).unwrap()); 157 | assert_eq!(expected_position, cursor.position()); 158 | } 159 | 160 | #[test] 161 | fn test_read_blob() { 162 | assert_eq!( 163 | vec![0x61, 0x62, 0x63, 0x64], 164 | read_blob(&mut Cursor::new(vec![4, 0x61, 0x62, 0x63, 0x64])).unwrap() 165 | ); 166 | } 167 | 168 | #[test] 169 | fn test_verify_version() { 170 | // Valid version "0003" should succeed 171 | let success = verify_version(&mut Cursor::new(vec![0x30, 0x30, 0x30, 0x33])); 172 | assert!(success.is_ok(), "Expected success for valid version"); 173 | 174 | // Invalid version "000:" should fail 175 | let failure = verify_version(&mut Cursor::new(vec![0x30, 0x30, 0x30, 0x3a])); 176 | assert!(failure.is_err()); 177 | } 178 | 179 | #[test] 180 | fn test_verify_magic() { 181 | let success = verify_magic(&mut Cursor::new(vec![0x52, 0x45, 0x44, 0x49, 0x53])); 182 | assert!(success.is_ok(), "Expected success for valid magic bytes"); 183 | 184 | let failure = verify_magic(&mut Cursor::new(vec![0x51, 0x0, 0x0, 0x0, 0x0])); 185 | assert!(failure.is_err(), "Expected error for invalid magic bytes"); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/decoder/common/ziplist.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use super::utils::read_exact; 4 | use crate::types::{RdbError, RdbResult}; 5 | use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum ZiplistEntry { 9 | String(Vec), 10 | Number(i64), 11 | } 12 | 13 | pub fn read_ziplist_metadata(input: &mut T) -> RdbResult<(u32, u32, u16)> { 14 | let zlbytes = input.read_u32::()?; 15 | let zltail = input.read_u32::()?; 16 | let zllen = input.read_u16::()?; 17 | 18 | Ok((zlbytes, zltail, zllen)) 19 | } 20 | 21 | pub fn read_ziplist_entry_string(input: &mut R) -> RdbResult> { 22 | let entry = read_ziplist_entry(input)?; 23 | match entry { 24 | ZiplistEntry::String(val) => Ok(val), 25 | ZiplistEntry::Number(val) => Ok(val.to_string().into_bytes()), 26 | } 27 | } 28 | 29 | fn read_ziplist_entry(input: &mut R) -> RdbResult { 30 | // 1. 1 or 5 bytes length of previous entry 31 | let byte = input.read_u8()?; 32 | if byte == 254 { 33 | let mut bytes = [0; 4]; 34 | match input.read(&mut bytes) { 35 | Ok(4) => (), 36 | Ok(_) => { 37 | return Err(RdbError::MissingValue( 38 | "4 bytes to skip after ziplist length", 39 | )) 40 | } 41 | Err(e) => return Err(RdbError::Io(e)), 42 | }; 43 | } 44 | 45 | let number_value: i64; 46 | 47 | // 2. Read flag or number valu 48 | let flag = input.read_u8()?; 49 | 50 | let length = match (flag & 0xC0) >> 6 { 51 | 0 => (flag & 0x3F) as u64, 52 | 1 => { 53 | let next_byte = input.read_u8()?; 54 | (((flag & 0x3F) as u64) << 8) | next_byte as u64 55 | } 56 | 2 => input.read_u32::()? as u64, 57 | _ => { 58 | match (flag & 0xF0) >> 4 { 59 | 0xC => number_value = input.read_i16::()? as i64, 60 | 0xD => number_value = input.read_i32::()? as i64, 61 | 0xE => number_value = input.read_i64::()?, 62 | 0xF => match flag & 0xF { 63 | 0 => { 64 | let mut bytes = [0; 3]; 65 | match input.read(&mut bytes) { 66 | Ok(3) => (), 67 | Ok(_) => return Err(RdbError::MissingValue("24bit number")), 68 | Err(e) => return Err(RdbError::Io(e)), 69 | }; 70 | 71 | let number: i32 = (((bytes[2] as i32) << 24) 72 | ^ ((bytes[1] as i32) << 16) 73 | ^ ((bytes[0] as i32) << 8) 74 | ^ 48) 75 | >> 8; 76 | 77 | number_value = number as i64; 78 | } 79 | 0xE => { 80 | number_value = input.read_i8()? as i64; 81 | } 82 | _ => { 83 | number_value = (flag & 0xF) as i64 - 1; 84 | } 85 | }, 86 | _ => { 87 | return Err(RdbError::ParsingError { 88 | context: "read_ziplist_entry", 89 | message: format!("Unknown encoding value: {}", flag), 90 | }); 91 | } 92 | } 93 | 94 | return Ok(ZiplistEntry::Number(number_value)); 95 | } 96 | }; 97 | 98 | // 3. Read value 99 | let rawval = read_exact(input, length as usize)?; 100 | Ok(ZiplistEntry::String(rawval)) 101 | } 102 | -------------------------------------------------------------------------------- /src/decoder/hash.rs: -------------------------------------------------------------------------------- 1 | use super::common::utils::{read_blob, read_exact, read_length}; 2 | use super::common::{ 3 | read_list_pack_entry_as_string, read_list_pack_length, read_ziplist_entry_string, 4 | read_ziplist_metadata, 5 | }; 6 | use crate::types::{RdbError, RdbResult, RdbValue}; 7 | use byteorder::{LittleEndian, ReadBytesExt}; 8 | use indexmap::IndexMap; 9 | use std::io::{Cursor, Read}; 10 | 11 | pub fn read_hash(input: &mut R, key: &[u8], expiry: Option) -> RdbResult { 12 | let mut hash_items = read_length(input)?; 13 | let mut values = IndexMap::new(); 14 | 15 | while hash_items > 0 { 16 | let field = read_blob(input)?; 17 | let val = read_blob(input)?; 18 | values.insert(field, val); 19 | hash_items -= 1; 20 | } 21 | 22 | Ok(RdbValue::Hash { 23 | key: key.to_vec(), 24 | values, 25 | expiry, 26 | }) 27 | } 28 | 29 | pub fn read_hash_ziplist( 30 | input: &mut R, 31 | key: &[u8], 32 | expiry: Option, 33 | ) -> RdbResult { 34 | let ziplist = read_blob(input)?; 35 | let mut reader = Cursor::new(ziplist); 36 | let (_zlbytes, _zltail, zllen) = read_ziplist_metadata(&mut reader)?; 37 | 38 | assert!(zllen % 2 == 0); 39 | let zllen = zllen / 2; 40 | 41 | let mut values = IndexMap::new(); 42 | 43 | for _ in 0..zllen { 44 | let field = read_ziplist_entry_string(&mut reader)?; 45 | let value = read_ziplist_entry_string(&mut reader)?; 46 | values.insert(field, value); 47 | } 48 | 49 | let last_byte = reader.read_u8()?; 50 | if last_byte != 0xFF { 51 | return Err(RdbError::ParsingError { 52 | context: "read_hash_ziplist", 53 | message: format!("Unknown encoding value: {}", last_byte), 54 | }); 55 | } 56 | 57 | Ok(RdbValue::Hash { 58 | key: key.to_vec(), 59 | values, 60 | expiry, 61 | }) 62 | } 63 | 64 | pub fn read_hash_zipmap( 65 | input: &mut R, 66 | key: &[u8], 67 | expiry: Option, 68 | ) -> RdbResult { 69 | let zipmap = read_blob(input)?; 70 | let mut reader = Cursor::new(zipmap); 71 | 72 | let zmlen = reader.read_u8()?; 73 | 74 | let mut length: i32; 75 | if zmlen <= 254 { 76 | length = zmlen as i32; 77 | } else { 78 | length = -1; 79 | } 80 | 81 | let mut values = IndexMap::new(); 82 | 83 | loop { 84 | let next_byte = reader.read_u8()?; 85 | 86 | if next_byte == 0xFF { 87 | break; // End of list. 88 | } 89 | 90 | let field = read_zipmap_entry(next_byte, &mut reader)?; 91 | 92 | let next_byte = reader.read_u8()?; 93 | let _free = reader.read_u8()?; 94 | let value = read_zipmap_entry(next_byte, &mut reader)?; 95 | 96 | values.insert(field, value); 97 | 98 | if length > 0 { 99 | length -= 1; 100 | } 101 | 102 | if length == 0 { 103 | let last_byte = reader.read_u8()?; 104 | 105 | if last_byte != 0xFF { 106 | return Err(RdbError::ParsingError { 107 | context: "read_hash_zipmap", 108 | message: format!("Unknown encoding value: {}", last_byte), 109 | }); 110 | } 111 | break; 112 | } 113 | } 114 | 115 | Ok(RdbValue::Hash { 116 | key: key.to_vec(), 117 | values, 118 | expiry, 119 | }) 120 | } 121 | 122 | fn read_zipmap_entry(next_byte: u8, zipmap: &mut T) -> RdbResult> { 123 | let elem_len = match next_byte { 124 | 253 => zipmap.read_u32::().unwrap(), 125 | 254 | 255 => { 126 | return Err(RdbError::ParsingError { 127 | context: "read_zipmap_entry", 128 | message: format!("Unknown encoding value: {}", next_byte), 129 | }); 130 | } 131 | _ => next_byte as u32, 132 | }; 133 | 134 | read_exact(zipmap, elem_len as usize) 135 | } 136 | 137 | pub fn read_hash_list_pack( 138 | input: &mut R, 139 | key: &[u8], 140 | expiry: Option, 141 | ) -> RdbResult { 142 | let listpack = read_blob(input)?; 143 | let mut cursor = 0; 144 | let size = read_list_pack_length(&listpack, &mut cursor); 145 | 146 | let mut values = IndexMap::new(); 147 | let mut reader = Cursor::new(listpack); 148 | reader.set_position(cursor as u64); 149 | 150 | for _ in 0..size / 2 { 151 | let field = read_list_pack_entry_as_string(&mut reader)?; 152 | let value = read_list_pack_entry_as_string(&mut reader)?; 153 | values.insert(field, value); 154 | } 155 | 156 | Ok(RdbValue::Hash { 157 | key: key.to_vec(), 158 | values, 159 | expiry, 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /src/decoder/list.rs: -------------------------------------------------------------------------------- 1 | use super::common::utils::{read_blob, read_length, read_sequence}; 2 | use super::common::{ 3 | read_list_pack_entry_as_string, read_ziplist_entry_string, read_ziplist_metadata, 4 | }; 5 | use crate::types::{RdbError, RdbResult, RdbValue}; 6 | use byteorder::{LittleEndian, ReadBytesExt}; 7 | use std::io::{Cursor, Read}; 8 | 9 | pub fn read_linked_list( 10 | input: &mut R, 11 | key: &[u8], 12 | expiry: Option, 13 | ) -> RdbResult { 14 | let values = read_sequence(input, |input| read_blob(input))?; 15 | 16 | Ok(RdbValue::List { 17 | key: key.to_vec(), 18 | values, 19 | expiry, 20 | }) 21 | } 22 | 23 | pub fn read_list_ziplist( 24 | input: &mut R, 25 | key: &[u8], 26 | expiry: Option, 27 | ) -> RdbResult { 28 | let ziplist = read_blob(input)?; 29 | let mut reader = Cursor::new(ziplist); 30 | let (_zlbytes, _zltail, zllen) = read_ziplist_metadata(&mut reader)?; 31 | 32 | let mut values = Vec::with_capacity(zllen as usize); 33 | 34 | for _ in 0..zllen { 35 | let entry = read_ziplist_entry_string(&mut reader)?; 36 | values.push(entry); 37 | } 38 | 39 | let last_byte = reader.read_u8()?; 40 | if last_byte != 0xFF { 41 | return Err(RdbError::ParsingError { 42 | context: "read_list_ziplist", 43 | message: format!("Unknown encoding value: {}", last_byte), 44 | }); 45 | } 46 | 47 | Ok(RdbValue::List { 48 | key: key.to_vec(), 49 | values, 50 | expiry, 51 | }) 52 | } 53 | 54 | pub fn read_quicklist( 55 | input: &mut R, 56 | key: &[u8], 57 | expiry: Option, 58 | ) -> RdbResult { 59 | let len = read_length(input)?; 60 | let mut values = Vec::new(); 61 | 62 | for _ in 0..len { 63 | let mut ziplist_values = read_quicklist_ziplist(input, key)?; 64 | values.append(&mut ziplist_values); 65 | } 66 | 67 | Ok(RdbValue::List { 68 | key: key.to_vec(), 69 | values, 70 | expiry, 71 | }) 72 | } 73 | 74 | pub fn read_quicklist_2( 75 | input: &mut R, 76 | key: &[u8], 77 | expiry: Option, 78 | ) -> RdbResult { 79 | let len = read_length(input)?; 80 | let mut values = Vec::new(); 81 | 82 | for _ in 0..len { 83 | let container_type = read_length(input)?; 84 | match container_type { 85 | 1 => { 86 | // QUICKLIST_NODE_CONTAINER_PLAIN 87 | let entry = read_blob(input)?; 88 | values.push(entry); 89 | } 90 | 2 => { 91 | // QUICKLIST_NODE_CONTAINER_PACKED 92 | let mut listpack_values = read_quicklist_listpack(input)?; 93 | values.append(&mut listpack_values); 94 | } 95 | _ => { 96 | return Err(RdbError::ParsingError { 97 | context: "read_quicklist_2", 98 | message: format!("Unknown encoding value: {}", container_type), 99 | }) 100 | } 101 | } 102 | } 103 | 104 | Ok(RdbValue::List { 105 | key: key.to_vec(), 106 | values, 107 | expiry, 108 | }) 109 | } 110 | 111 | fn read_quicklist_ziplist(input: &mut R, _key: &[u8]) -> RdbResult>> { 112 | let ziplist = read_blob(input)?; 113 | let mut reader = Cursor::new(ziplist); 114 | let (_zlbytes, _zltail, zllen) = read_ziplist_metadata(&mut reader)?; 115 | 116 | let mut values = Vec::with_capacity(zllen as usize); 117 | 118 | for _ in 0..zllen { 119 | let entry = read_ziplist_entry_string(&mut reader)?; 120 | values.push(entry); 121 | } 122 | 123 | let last_byte = reader.read_u8()?; 124 | if last_byte != 0xFF { 125 | return Err(RdbError::ParsingError { 126 | context: "read_quicklist_ziplist", 127 | message: format!("Unknown encoding value: {}", last_byte), 128 | }); 129 | } 130 | 131 | Ok(values) 132 | } 133 | 134 | fn read_quicklist_listpack(input: &mut R) -> RdbResult>> { 135 | let listpack = read_blob(input)?; 136 | let mut reader = Cursor::new(listpack); 137 | let total_bytes = reader.read_u32::()?; 138 | let num_elements = reader.read_u16::()?; 139 | 140 | let mut values = Vec::with_capacity(num_elements as usize); 141 | 142 | // Read until we reach the end of the listpack 143 | while reader.position() < total_bytes as u64 - 1 { 144 | let entry = read_list_pack_entry_as_string(&mut reader)?; 145 | values.push(entry); 146 | } 147 | 148 | // Verify end byte 149 | let last_byte = reader.read_u8()?; 150 | if last_byte != 0xFF { 151 | return Err(RdbError::ParsingError { 152 | context: "read_quicklist_listpack", 153 | message: format!("Unknown encoding value: {}", last_byte), 154 | }); 155 | } 156 | 157 | Ok(values) 158 | } 159 | -------------------------------------------------------------------------------- /src/decoder/mod.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | mod hash; 3 | mod list; 4 | mod rdb; 5 | mod set; 6 | mod sorted_set; 7 | 8 | use std::io::Read; 9 | 10 | use self::rdb::DecoderState; 11 | use crate::filter::Filter; 12 | use crate::types::{RdbResult, RdbValue}; 13 | 14 | pub struct RdbDecoder { 15 | reader: R, 16 | filter: F, 17 | state: DecoderState, 18 | } 19 | 20 | impl RdbDecoder { 21 | pub(crate) fn new(mut reader: R, filter: F) -> RdbResult { 22 | rdb::verify_header(&mut reader)?; 23 | Ok(Self { 24 | reader, 25 | filter, 26 | state: DecoderState::default(), 27 | }) 28 | } 29 | } 30 | 31 | impl Iterator for RdbDecoder { 32 | type Item = RdbResult; 33 | 34 | fn next(&mut self) -> Option { 35 | if self.state.reached_eof { 36 | return None; 37 | } 38 | Some(rdb::process_next_operation( 39 | &mut self.reader, 40 | &self.filter, 41 | &mut self.state, 42 | )) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/decoder/rdb.rs: -------------------------------------------------------------------------------- 1 | use super::common::utils::{ 2 | read_blob, read_length, read_length_with_encoding, verify_magic, verify_version, 3 | }; 4 | use super::{hash, list, set, sorted_set}; 5 | use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; 6 | use std::io::Read; 7 | 8 | use crate::constants::{encoding, encoding_type, op_code}; 9 | use crate::filter::Filter; 10 | use crate::types::{RdbError, RdbResult, RdbValue}; 11 | 12 | #[derive(Default)] 13 | pub(crate) struct DecoderState { 14 | pub last_expiretime: Option, 15 | pub current_database: u32, 16 | pub reached_eof: bool, 17 | } 18 | 19 | pub(crate) fn verify_header(input: &mut R) -> RdbResult<()> { 20 | verify_magic(input)?; 21 | verify_version(input) 22 | } 23 | 24 | pub(crate) fn read_type( 25 | input: &mut R, 26 | key: &[u8], 27 | value_type: u8, 28 | expiry: Option, 29 | ) -> RdbResult { 30 | let result = match value_type { 31 | encoding_type::STRING => { 32 | let value = read_blob(input)?; 33 | RdbValue::String { 34 | key: key.to_vec(), 35 | value, 36 | expiry, 37 | } 38 | } 39 | encoding_type::LIST => list::read_linked_list(input, key, expiry)?, 40 | encoding_type::SET => set::read_set(input, key, expiry)?, 41 | encoding_type::ZSET => sorted_set::read_sorted_set(input, key, expiry, false)?, 42 | encoding_type::HASH => hash::read_hash(input, key, expiry)?, 43 | encoding_type::HASH_ZIPMAP => hash::read_hash_zipmap(input, key, expiry)?, 44 | encoding_type::LIST_ZIPLIST => list::read_list_ziplist(input, key, expiry)?, 45 | encoding_type::SET_INTSET => set::read_set_intset(input, key, expiry)?, 46 | encoding_type::ZSET_ZIPLIST => sorted_set::read_sorted_set_ziplist(input, key, expiry)?, 47 | encoding_type::HASH_ZIPLIST => hash::read_hash_ziplist(input, key, expiry)?, 48 | encoding_type::LIST_QUICKLIST => list::read_quicklist(input, key, expiry)?, 49 | encoding_type::HASH_LIST_PACK => hash::read_hash_list_pack(input, key, expiry)?, 50 | encoding_type::ZSET_2 => sorted_set::read_sorted_set(input, key, expiry, true)?, 51 | encoding_type::LIST_QUICKLIST_2 => list::read_quicklist_2(input, key, expiry)?, 52 | encoding_type::STREAM_LIST_PACKS => { 53 | todo!("read_stream_list_packs v1 not implemented"); 54 | } 55 | encoding_type::STREAM_LIST_PACKS_2 => { 56 | todo!("read_stream_list_packs v2 not implemented"); 57 | } 58 | encoding_type::STREAM_LIST_PACKS_3 => { 59 | todo!("read_stream_list_packs v3 not implemented"); 60 | } 61 | encoding_type::ZSET_LIST_PACK => sorted_set::read_sorted_set_listpack(input, key, expiry)?, 62 | encoding_type::SET_LIST_PACK => set::read_set_list_pack(input, key, expiry)?, 63 | unknown_type => { 64 | skip_object(input, unknown_type)?; 65 | return Err(RdbError::MissingValue("skip")); 66 | } 67 | }; 68 | Ok(result) 69 | } 70 | 71 | pub(crate) fn skip(input: &mut R, skip_bytes: usize) -> RdbResult<()> { 72 | let mut buf = vec![0; skip_bytes]; 73 | input.read_exact(&mut buf).map_err(RdbError::Io)?; 74 | Ok(()) 75 | } 76 | 77 | pub(crate) fn skip_blob(input: &mut R) -> RdbResult<()> { 78 | let (len, is_encoded) = read_length_with_encoding(input)?; 79 | 80 | let skip_bytes = if is_encoded { 81 | match len { 82 | encoding::INT8 => 1, 83 | encoding::INT16 => 2, 84 | encoding::INT32 => 4, 85 | encoding::LZF => { 86 | let compressed_length = read_length(input)?; 87 | let _real_length = read_length(input)?; 88 | compressed_length 89 | } 90 | _ => { 91 | return Err(RdbError::ParsingError { 92 | context: "skip_blob", 93 | message: format!("Unknown encoding value: {}", len), 94 | }); 95 | } 96 | } 97 | } else { 98 | len 99 | }; 100 | 101 | skip(input, skip_bytes as usize) 102 | } 103 | 104 | pub(crate) fn skip_object(input: &mut R, enc_type: u8) -> RdbResult<()> { 105 | let blobs_count = match enc_type { 106 | encoding_type::STRING 107 | | encoding_type::HASH_ZIPMAP 108 | | encoding_type::LIST_ZIPLIST 109 | | encoding_type::SET_INTSET 110 | | encoding_type::ZSET_ZIPLIST 111 | | encoding_type::HASH_ZIPLIST 112 | | encoding_type::HASH_LIST_PACK => 1, 113 | encoding_type::LIST | encoding_type::SET | encoding_type::LIST_QUICKLIST => { 114 | read_length(input)? 115 | } 116 | encoding_type::ZSET | encoding_type::HASH => read_length(input)? * 2, 117 | _ => return Err(RdbError::UnknownEncoding(enc_type)), 118 | }; 119 | 120 | for _ in 0..blobs_count { 121 | skip_blob(input)?; 122 | } 123 | Ok(()) 124 | } 125 | 126 | pub(crate) fn skip_key_and_object(input: &mut R, enc_type: u8) -> RdbResult<()> { 127 | skip_blob(input)?; 128 | skip_object(input, enc_type)?; 129 | Ok(()) 130 | } 131 | 132 | pub(crate) fn process_next_operation( 133 | input: &mut R, 134 | filter: &F, 135 | state: &mut DecoderState, 136 | ) -> RdbResult { 137 | let next_op = match input.read_u8() { 138 | Ok(op) => op, 139 | Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { 140 | return Ok(RdbValue::Checksum(vec![])) 141 | } 142 | Err(e) => return Err(e.into()), 143 | }; 144 | 145 | match next_op { 146 | op_code::SELECTDB => { 147 | state.current_database = read_length(input)?; 148 | Ok(RdbValue::SelectDb(state.current_database)) 149 | } 150 | op_code::EOF => { 151 | let mut checksum = Vec::new(); 152 | input.read_to_end(&mut checksum)?; 153 | state.reached_eof = true; 154 | Ok(RdbValue::Checksum(checksum)) 155 | } 156 | op_code::EXPIRETIME_MS => { 157 | state.last_expiretime = Some(input.read_u64::()?); 158 | process_next_operation(input, filter, state) 159 | } 160 | op_code::EXPIRETIME => { 161 | state.last_expiretime = Some(input.read_u32::()? as u64 * 1000); 162 | process_next_operation(input, filter, state) 163 | } 164 | op_code::RESIZEDB => { 165 | let db_size = read_length(input)?; 166 | let expires_size = read_length(input)?; 167 | Ok(RdbValue::ResizeDb { 168 | db_size, 169 | expires_size, 170 | }) 171 | } 172 | op_code::AUX => { 173 | let key = read_blob(input)?; 174 | let value = read_blob(input)?; 175 | Ok(RdbValue::AuxField { key, value }) 176 | } 177 | op_code::MODULE_AUX => { 178 | skip_blob(input)?; 179 | process_next_operation(input, filter, state) 180 | } 181 | op_code::IDLE => { 182 | let _idle_time = read_length(input)?; 183 | process_next_operation(input, filter, state) 184 | } 185 | op_code::FREQ => { 186 | let _freq = input.read_u8()?; 187 | process_next_operation(input, filter, state) 188 | } 189 | value_type => { 190 | if !filter.matches_db(state.current_database) { 191 | skip_key_and_object(input, value_type)?; 192 | return Ok(RdbValue::SelectDb(state.current_database)); 193 | } 194 | 195 | let key = read_blob(input)?; 196 | if !filter.matches_type(value_type) || !filter.matches_key(&key) { 197 | skip_object(input, value_type)?; 198 | return Ok(RdbValue::SelectDb(state.current_database)); 199 | } 200 | 201 | read_type(input, &key, value_type, state.last_expiretime) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/decoder/set.rs: -------------------------------------------------------------------------------- 1 | use super::common::read_list_pack_entry_as_string; 2 | use super::common::utils::{read_blob, read_sequence}; 3 | use crate::types::{RdbError, RdbResult, RdbValue}; 4 | use byteorder::{LittleEndian, ReadBytesExt}; 5 | use std::io::{Cursor, Read}; 6 | 7 | pub fn read_set(input: &mut R, key: &[u8], expiry: Option) -> RdbResult { 8 | let values = read_sequence(input, |input| read_blob(input))?; 9 | let members = values.into_iter().collect(); 10 | 11 | Ok(RdbValue::Set { 12 | key: key.to_vec(), 13 | members, 14 | expiry, 15 | }) 16 | } 17 | 18 | pub fn read_set_intset( 19 | input: &mut R, 20 | key: &[u8], 21 | expiry: Option, 22 | ) -> RdbResult { 23 | let intset = read_blob(input)?; 24 | 25 | let mut reader = Cursor::new(intset); 26 | let byte_size = reader.read_u32::()?; 27 | let intset_length = reader.read_u32::()?; 28 | 29 | let mut members = Vec::with_capacity(intset_length as usize); 30 | 31 | for _ in 0..intset_length { 32 | let val = match byte_size { 33 | 2 => reader.read_i16::()? as i64, 34 | 4 => reader.read_i32::()? as i64, 35 | 8 => reader.read_i64::()?, 36 | _ => panic!("unhandled byte size in intset: {}", byte_size), 37 | }; 38 | 39 | members.push(val.to_string().as_bytes().to_vec()); 40 | } 41 | 42 | Ok(RdbValue::Set { 43 | key: key.to_vec(), 44 | members: members.into_iter().collect(), 45 | expiry, 46 | }) 47 | } 48 | 49 | pub fn read_set_list_pack( 50 | input: &mut R, 51 | key: &[u8], 52 | expiry: Option, 53 | ) -> RdbResult { 54 | let listpack = read_blob(input)?; 55 | let mut reader = Cursor::new(listpack); 56 | 57 | // Read total bytes and number of elements 58 | let total_bytes = reader.read_u32::()?; 59 | let num_elements = reader.read_u16::()?; 60 | 61 | let mut members = Vec::with_capacity(num_elements as usize); 62 | 63 | // Read until we reach the end of the listpack 64 | while reader.position() < total_bytes as u64 - 1 { 65 | let entry = read_list_pack_entry_as_string(&mut reader)?; 66 | members.push(entry); 67 | } 68 | 69 | // Verify end byte 70 | let last_byte = reader.read_u8()?; 71 | if last_byte != 0xFF { 72 | return Err(RdbError::ParsingError { 73 | context: "read_set_list_pack", 74 | message: format!("Unknown encoding value: {}", last_byte), 75 | }); 76 | } 77 | 78 | Ok(RdbValue::Set { 79 | key: key.to_vec(), 80 | members: members.into_iter().collect(), 81 | expiry, 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /src/decoder/sorted_set.rs: -------------------------------------------------------------------------------- 1 | use super::common::utils::{read_blob, read_exact, read_length}; 2 | use super::common::{read_list_pack_length, read_ziplist_entry_string, read_ziplist_metadata}; 3 | use crate::decoder::common::read_list_pack_entry_as_string; 4 | use crate::types::{RdbError, RdbResult, RdbValue}; 5 | use byteorder::ReadBytesExt; 6 | use std::io::{Cursor, Read}; 7 | use std::str; 8 | 9 | pub fn read_sorted_set( 10 | input: &mut R, 11 | key: &[u8], 12 | expiry: Option, 13 | is_zset2: bool, 14 | ) -> RdbResult { 15 | let mut set_items = read_length(input)?; 16 | let mut values = Vec::with_capacity(set_items as usize); 17 | 18 | while set_items > 0 { 19 | let val = read_blob(input)?; 20 | 21 | let score = if is_zset2 { 22 | // ZSET2 format uses binary encoding of float64 23 | input.read_f64::()? 24 | } else { 25 | // Original format uses string representation 26 | let score_length = input.read_u8()?; 27 | match score_length { 28 | 253 => f64::NAN, 29 | 254 => f64::INFINITY, 30 | 255 => f64::NEG_INFINITY, 31 | _ => { 32 | let tmp = read_exact(input, score_length as usize)?; 33 | String::from_utf8_lossy(&tmp).parse::().unwrap() 34 | } 35 | } 36 | }; 37 | 38 | values.push((score, val)); 39 | set_items -= 1; 40 | } 41 | 42 | Ok(RdbValue::SortedSet { 43 | key: key.to_vec(), 44 | values, 45 | expiry, 46 | }) 47 | } 48 | 49 | pub fn read_sorted_set_ziplist( 50 | input: &mut R, 51 | key: &[u8], 52 | expiry: Option, 53 | ) -> RdbResult { 54 | let ziplist = read_blob(input)?; 55 | let mut reader = Cursor::new(ziplist); 56 | let (_zlbytes, _zltail, zllen) = read_ziplist_metadata(&mut reader)?; 57 | 58 | assert!(zllen % 2 == 0); 59 | let zllen = zllen / 2; 60 | let mut values = Vec::with_capacity(zllen as usize); 61 | 62 | for _ in 0..zllen { 63 | let entry = read_ziplist_entry_string(&mut reader)?; 64 | let score = read_ziplist_entry_string(&mut reader)?; 65 | let score = str::from_utf8(&score).unwrap().parse::().unwrap(); 66 | values.push((score, entry)); 67 | } 68 | 69 | let last_byte = reader.read_u8()?; 70 | if last_byte != 0xFF { 71 | return Err(RdbError::ParsingError { 72 | context: "read_sortedset_ziplist", 73 | message: format!("Unknown encoding value: {}", last_byte), 74 | }); 75 | } 76 | 77 | Ok(RdbValue::SortedSet { 78 | key: key.to_vec(), 79 | values, 80 | expiry, 81 | }) 82 | } 83 | 84 | pub fn read_sorted_set_listpack( 85 | input: &mut R, 86 | key: &[u8], 87 | expiry: Option, 88 | ) -> RdbResult { 89 | let listpack = read_blob(input)?; 90 | let mut reader = Cursor::new(&listpack); 91 | let mut values = Vec::new(); 92 | 93 | // Read number of elements (size) 94 | let buf = reader.get_ref(); 95 | let mut cursor = 0; 96 | let size = read_list_pack_length(buf, &mut cursor); 97 | reader.set_position(cursor as u64); 98 | 99 | assert!(size % 2 == 0); 100 | let num_entries = size / 2; 101 | 102 | for _ in 0..num_entries { 103 | let member = read_list_pack_entry_as_string(&mut reader)?; 104 | let score_str = read_list_pack_entry_as_string(&mut reader)?; 105 | 106 | let score = String::from_utf8_lossy(&score_str) 107 | .parse::() 108 | .map_err(|_| RdbError::ParsingError { 109 | context: "read_sorted_set_listpack", 110 | message: format!( 111 | "Failed to parse score: {:?}", 112 | String::from_utf8_lossy(&score_str) 113 | ), 114 | })?; 115 | 116 | values.push((score, member)); 117 | } 118 | 119 | Ok(RdbValue::SortedSet { 120 | key: key.to_vec(), 121 | values, 122 | expiry, 123 | }) 124 | } 125 | -------------------------------------------------------------------------------- /src/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Type; 2 | use regex::Regex; 3 | 4 | pub trait Filter { 5 | fn matches_db(&self, _db: u32) -> bool { 6 | true 7 | } 8 | fn matches_type(&self, _enc_type: u8) -> bool { 9 | true 10 | } 11 | fn matches_key(&self, _key: &[u8]) -> bool { 12 | true 13 | } 14 | } 15 | 16 | #[derive(Default)] 17 | pub struct Simple { 18 | databases: Vec, 19 | types: Vec, 20 | keys: Option, 21 | } 22 | 23 | impl Simple { 24 | pub fn new() -> Simple { 25 | Simple::default() 26 | } 27 | 28 | pub fn add_database(&mut self, db: u32) { 29 | self.databases.push(db); 30 | } 31 | 32 | pub fn add_type(&mut self, typ: Type) { 33 | self.types.push(typ); 34 | } 35 | 36 | pub fn add_keys(&mut self, re: Regex) { 37 | self.keys = Some(re); 38 | } 39 | } 40 | 41 | impl Filter for Simple { 42 | fn matches_db(&self, db: u32) -> bool { 43 | if self.databases.is_empty() { 44 | true 45 | } else { 46 | self.databases.iter().any(|&x| x == db) 47 | } 48 | } 49 | 50 | fn matches_type(&self, enc_type: u8) -> bool { 51 | if self.types.is_empty() { 52 | return true; 53 | } 54 | 55 | let typ = Type::from_encoding(enc_type).unwrap(); 56 | self.types.iter().any(|x| *x == typ) 57 | } 58 | 59 | fn matches_key(&self, key: &[u8]) -> bool { 60 | match self.keys.clone() { 61 | None => true, 62 | Some(re) => { 63 | let key = String::from_utf8_lossy(key); 64 | re.is_match(&key) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/formatter/json.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_must_use)] 2 | use super::write_str; 3 | use crate::formatter::Formatter; 4 | use indexmap::IndexMap; 5 | use rustc_serialize::json; 6 | use std::io; 7 | use std::io::Write; 8 | use std::path::PathBuf; 9 | use std::str; 10 | 11 | pub struct JSON { 12 | out: Box, 13 | is_first_db: bool, 14 | has_databases: bool, 15 | is_first_key_in_db: bool, 16 | elements_in_key: u32, 17 | element_index: u32, 18 | } 19 | 20 | impl JSON { 21 | pub fn new(file_path: Option) -> JSON { 22 | let out: Box = match file_path { 23 | Some(path) => match std::fs::File::create(path) { 24 | Ok(file) => Box::new(file), 25 | Err(_) => Box::new(io::stdout()), 26 | }, 27 | None => Box::new(io::stdout()), 28 | }; 29 | 30 | JSON { 31 | out, 32 | is_first_db: true, 33 | has_databases: false, 34 | is_first_key_in_db: true, 35 | elements_in_key: 0, 36 | element_index: 0, 37 | } 38 | } 39 | 40 | fn start_key(&mut self, length: u32) { 41 | if !self.is_first_key_in_db { 42 | write_str(&mut self.out, ","); 43 | } 44 | 45 | self.is_first_key_in_db = false; 46 | self.elements_in_key = length; 47 | self.element_index = 0; 48 | } 49 | 50 | fn end_key(&mut self) {} 51 | 52 | fn write_comma(&mut self) { 53 | if self.element_index > 0 { 54 | write_str(&mut self.out, ","); 55 | } 56 | self.element_index += 1; 57 | } 58 | 59 | fn write_key(&mut self, key: &[u8]) { 60 | self.out.write_all(encode_to_ascii(key).as_bytes()); 61 | } 62 | fn write_value(&mut self, value: &[u8]) { 63 | self.out.write_all(encode_to_ascii(value).as_bytes()); 64 | } 65 | } 66 | 67 | fn encode_to_ascii(value: &[u8]) -> String { 68 | match str::from_utf8(value) { 69 | Ok(s) => json::encode(&s).unwrap(), 70 | Err(_) => { 71 | let s: String = value 72 | .iter() 73 | .map(|&b| { 74 | if (32..127).contains(&b) { 75 | // ASCII printable characters 76 | (b as char).to_string() 77 | } else { 78 | format!("\\u{:04x}", b as u16) 79 | } 80 | }) 81 | .collect(); 82 | format!("\"{}\"", s) 83 | } 84 | } 85 | } 86 | 87 | impl Formatter for JSON { 88 | fn start_rdb(&mut self) { 89 | write_str(&mut self.out, "["); 90 | } 91 | 92 | fn end_rdb(&mut self) { 93 | if self.has_databases { 94 | write_str(&mut self.out, "}"); 95 | } 96 | write_str(&mut self.out, "]\n"); 97 | } 98 | 99 | fn start_database(&mut self, _db_number: u32) { 100 | if !self.is_first_db { 101 | write_str(&mut self.out, "},"); 102 | } 103 | 104 | write_str(&mut self.out, "{"); 105 | self.is_first_db = false; 106 | self.has_databases = true; 107 | self.is_first_key_in_db = true; 108 | } 109 | 110 | fn string(&mut self, key: &[u8], value: &[u8], _expiry: &Option) { 111 | self.start_key(0); 112 | self.write_key(key); 113 | write_str(&mut self.out, ":"); 114 | self.write_value(value); 115 | } 116 | 117 | fn hash(&mut self, key: &[u8], values: &IndexMap, Vec>, _expiry: &Option) { 118 | self.start_key(values.len() as u32); 119 | self.write_key(key); 120 | write_str(&mut self.out, ":{"); 121 | for (field, value) in values { 122 | self.write_comma(); 123 | self.write_key(field); 124 | write_str(&mut self.out, ":"); 125 | self.write_value(value); 126 | } 127 | self.end_key(); 128 | write_str(&mut self.out, "}"); 129 | } 130 | 131 | fn set(&mut self, key: &[u8], values: &[Vec], _expiry: &Option) { 132 | self.start_key(values.len() as u32); 133 | self.write_key(key); 134 | write_str(&mut self.out, ":["); 135 | for value in values { 136 | self.write_comma(); 137 | self.write_value(value); 138 | } 139 | self.end_key(); 140 | write_str(&mut self.out, "]"); 141 | } 142 | 143 | fn list(&mut self, key: &[u8], values: &[Vec], _expiry: &Option) { 144 | self.start_key(values.len() as u32); 145 | self.write_key(key); 146 | write_str(&mut self.out, ":["); 147 | for value in values { 148 | self.write_comma(); 149 | self.write_value(value); 150 | } 151 | self.end_key(); 152 | write_str(&mut self.out, "]"); 153 | } 154 | 155 | fn sorted_set(&mut self, key: &[u8], values: &[(f64, Vec)], _expiry: &Option) { 156 | self.start_key(values.len() as u32); 157 | self.write_key(key); 158 | write_str(&mut self.out, ":{"); 159 | for (score, member) in values { 160 | self.write_comma(); 161 | self.write_key(member); 162 | write_str(&mut self.out, ":"); 163 | self.write_value(score.to_string().as_bytes()); 164 | } 165 | self.end_key(); 166 | write_str(&mut self.out, "}"); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/formatter/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use indexmap::IndexMap; 4 | 5 | pub use self::json::JSON; 6 | pub use self::nil::Nil; 7 | pub use self::plain::Plain; 8 | pub use self::protocol::Protocol; 9 | 10 | use super::types::RdbValue; 11 | 12 | pub mod json; 13 | pub mod nil; 14 | pub mod plain; 15 | pub mod protocol; 16 | 17 | pub fn write_str(out: &mut W, data: &str) { 18 | out.write_all(data.as_bytes()).unwrap(); 19 | } 20 | 21 | #[allow(unused_variables)] 22 | pub trait Formatter { 23 | fn start_rdb(&mut self) {} 24 | fn end_rdb(&mut self) {} 25 | fn checksum(&mut self, checksum: &[u8]) {} 26 | 27 | fn start_database(&mut self, db_index: u32) {} 28 | fn end_database(&mut self, db_index: u32) {} 29 | 30 | fn resizedb(&mut self, db_size: u32, expires_size: u32) {} 31 | fn aux_field(&mut self, key: &[u8], value: &[u8]) {} 32 | 33 | fn string(&mut self, key: &[u8], value: &[u8], expiry: &Option) {} 34 | 35 | fn hash(&mut self, key: &[u8], values: &IndexMap, Vec>, expiry: &Option) {} 36 | 37 | fn set(&mut self, key: &[u8], values: &[Vec], expiry: &Option) {} 38 | 39 | fn list(&mut self, key: &[u8], values: &[Vec], expiry: &Option) {} 40 | 41 | fn sorted_set(&mut self, key: &[u8], values: &[(f64, Vec)], expiry: &Option) {} 42 | 43 | fn format(&mut self, value: &RdbValue) -> std::io::Result<()> { 44 | match value { 45 | RdbValue::Set { 46 | key, 47 | members, 48 | expiry, 49 | } => { 50 | self.set(key, members, expiry); 51 | Ok(()) 52 | } 53 | RdbValue::Hash { 54 | key, 55 | values, 56 | expiry, 57 | } => { 58 | self.hash(key, values, expiry); 59 | Ok(()) 60 | } 61 | RdbValue::List { 62 | key, 63 | values, 64 | expiry, 65 | } => { 66 | self.list(key, values, expiry); 67 | Ok(()) 68 | } 69 | RdbValue::SortedSet { 70 | key, 71 | values, 72 | expiry, 73 | } => { 74 | self.sorted_set(key, values, expiry); 75 | Ok(()) 76 | } 77 | RdbValue::String { key, value, expiry } => { 78 | self.string(key, value, expiry); 79 | Ok(()) 80 | } 81 | RdbValue::SelectDb(db_number) => { 82 | self.start_database(*db_number); 83 | Ok(()) 84 | } 85 | RdbValue::ResizeDb { 86 | db_size, 87 | expires_size, 88 | } => Ok(()), 89 | RdbValue::AuxField { key, value } => { 90 | self.aux_field(key, value); 91 | Ok(()) 92 | } 93 | RdbValue::Checksum(checksum) => { 94 | self.checksum(checksum); 95 | Ok(()) 96 | } 97 | } 98 | } 99 | } 100 | 101 | pub enum FormatterType { 102 | Json(JSON), 103 | Plain(Plain), 104 | Nil(Nil), 105 | Protocol(Protocol), 106 | } 107 | 108 | impl Formatter for FormatterType { 109 | fn format(&mut self, value: &RdbValue) -> std::io::Result<()> { 110 | match self { 111 | Self::Json(f) => f.format(value), 112 | Self::Plain(f) => f.format(value), 113 | Self::Nil(f) => f.format(value), 114 | Self::Protocol(f) => f.format(value), 115 | } 116 | } 117 | 118 | fn start_rdb(&mut self) { 119 | match self { 120 | Self::Json(f) => f.start_rdb(), 121 | Self::Plain(f) => f.start_rdb(), 122 | Self::Nil(f) => f.start_rdb(), 123 | Self::Protocol(f) => f.start_rdb(), 124 | } 125 | } 126 | 127 | fn end_rdb(&mut self) { 128 | match self { 129 | Self::Json(f) => f.end_rdb(), 130 | Self::Plain(f) => f.end_rdb(), 131 | Self::Nil(f) => f.end_rdb(), 132 | Self::Protocol(f) => f.end_rdb(), 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/formatter/nil.rs: -------------------------------------------------------------------------------- 1 | use crate::formatter::Formatter; 2 | use crate::types::RdbValue; 3 | use std::io; 4 | use std::io::Write; 5 | use std::path::PathBuf; 6 | 7 | pub struct Nil { 8 | _out: Box, 9 | } 10 | 11 | impl Nil { 12 | pub fn new(file_path: Option) -> Nil { 13 | let _out: Box = match file_path { 14 | Some(path) => match std::fs::File::create(path) { 15 | Ok(file) => Box::new(file), 16 | Err(_) => Box::new(io::stdout()), 17 | }, 18 | None => Box::new(io::stdout()), 19 | }; 20 | Nil { _out } 21 | } 22 | } 23 | 24 | impl Formatter for Nil { 25 | fn format(&mut self, _value: &RdbValue) -> std::io::Result<()> { 26 | Ok(()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/formatter/plain.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_must_use)] 2 | use super::write_str; 3 | use crate::formatter::Formatter; 4 | use indexmap::IndexMap; 5 | use rustc_serialize::hex::ToHex; 6 | use std::io; 7 | use std::io::Write; 8 | use std::path::PathBuf; 9 | 10 | pub struct Plain { 11 | out: Box, 12 | dbnum: u32, 13 | } 14 | 15 | impl Plain { 16 | pub fn new(file_path: Option) -> Plain { 17 | let out: Box = match file_path { 18 | Some(path) => match std::fs::File::create(path) { 19 | Ok(file) => Box::new(file), 20 | Err(_) => Box::new(io::stdout()), 21 | }, 22 | None => Box::new(io::stdout()), 23 | }; 24 | 25 | Plain { out, dbnum: 0 } 26 | } 27 | 28 | fn write_line_start(&mut self) { 29 | write_str(&mut self.out, &format!("db={} ", self.dbnum)); 30 | } 31 | 32 | fn hash_element(&mut self, key: &[u8], field: &[u8], value: &[u8]) { 33 | self.write_line_start(); 34 | 35 | self.out.write_all(key); 36 | write_str(&mut self.out, " . "); 37 | self.out.write_all(field); 38 | write_str(&mut self.out, " -> "); 39 | self.out.write_all(value); 40 | write_str(&mut self.out, "\n"); 41 | self.out.flush(); 42 | } 43 | 44 | fn set_element(&mut self, key: &[u8], member: &[u8]) { 45 | self.write_line_start(); 46 | 47 | self.out.write_all(key); 48 | write_str(&mut self.out, " { "); 49 | self.out.write_all(member); 50 | write_str(&mut self.out, " } "); 51 | write_str(&mut self.out, "\n"); 52 | self.out.flush(); 53 | } 54 | 55 | fn list_element(&mut self, index: usize, key: &[u8], value: &[u8]) { 56 | self.write_line_start(); 57 | 58 | self.out.write_all(key); 59 | write_str(&mut self.out, &format!("[{}]", index)); 60 | write_str(&mut self.out, " -> "); 61 | self.out.write_all(value); 62 | write_str(&mut self.out, "\n"); 63 | self.out.flush(); 64 | } 65 | 66 | fn sorted_set_element(&mut self, index: usize, key: &[u8], score: f64, member: &[u8]) { 67 | self.write_line_start(); 68 | 69 | self.out.write_all(key); 70 | write_str(&mut self.out, &format!("[{}]", index)); 71 | write_str(&mut self.out, " -> {"); 72 | self.out.write_all(member); 73 | write_str(&mut self.out, &format!(", score={}", score)); 74 | write_str(&mut self.out, "}\n"); 75 | self.out.flush(); 76 | } 77 | } 78 | 79 | impl Formatter for Plain { 80 | fn string(&mut self, key: &[u8], value: &[u8], _expiry: &Option) { 81 | self.write_line_start(); 82 | self.out.write_all(key); 83 | write_str(&mut self.out, " -> "); 84 | self.out.write_all(value); 85 | write_str(&mut self.out, "\n"); 86 | self.out.flush(); 87 | } 88 | 89 | fn hash(&mut self, key: &[u8], values: &IndexMap, Vec>, _expiry: &Option) { 90 | for (field, value) in values { 91 | self.hash_element(key, field, value); 92 | } 93 | } 94 | 95 | fn set(&mut self, key: &[u8], values: &[Vec], _expiry: &Option) { 96 | for value in values { 97 | self.set_element(key, value); 98 | } 99 | } 100 | 101 | fn list(&mut self, key: &[u8], values: &[Vec], _expiry: &Option) { 102 | for (i, value) in values.iter().enumerate() { 103 | self.list_element(i, key, value); 104 | } 105 | } 106 | 107 | fn sorted_set(&mut self, key: &[u8], values: &[(f64, Vec)], _expiry: &Option) { 108 | for (i, (score, member)) in values.iter().enumerate() { 109 | self.sorted_set_element(i, key, *score, member); 110 | } 111 | } 112 | 113 | fn checksum(&mut self, checksum: &[u8]) { 114 | if !checksum.is_empty() { 115 | write_str(&mut self.out, "checksum "); 116 | write_str(&mut self.out, &checksum.to_hex()); 117 | write_str(&mut self.out, "\n"); 118 | } 119 | } 120 | 121 | fn start_database(&mut self, db_number: u32) { 122 | self.dbnum = db_number; 123 | } 124 | 125 | fn aux_field(&mut self, key: &[u8], value: &[u8]) { 126 | write_str(&mut self.out, "aux "); 127 | self.out.write_all(key); 128 | write_str(&mut self.out, " -> "); 129 | self.out.write_all(value); 130 | write_str(&mut self.out, "\n"); 131 | self.out.flush(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/formatter/protocol.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_must_use)] 2 | use indexmap::IndexMap; 3 | 4 | use super::write_str; 5 | use crate::formatter::Formatter; 6 | use std::io; 7 | use std::io::Write; 8 | use std::path::PathBuf; 9 | 10 | pub struct Protocol { 11 | out: Box, 12 | last_expiry: Option, 13 | } 14 | 15 | impl Protocol { 16 | pub fn new(file_path: Option) -> Protocol { 17 | let out: Box = match file_path { 18 | Some(path) => match std::fs::File::create(path) { 19 | Ok(file) => Box::new(file), 20 | Err(_) => Box::new(io::stdout()), 21 | }, 22 | None => Box::new(io::stdout()), 23 | }; 24 | 25 | Protocol { 26 | out, 27 | last_expiry: None, 28 | } 29 | } 30 | } 31 | 32 | impl Protocol { 33 | fn emit(&mut self, args: Vec<&[u8]>) { 34 | write_str(&mut self.out, "*"); 35 | self.out.write_all(args.len().to_string().as_bytes()); 36 | write_str(&mut self.out, "\r\n"); 37 | for arg in &args { 38 | write_str(&mut self.out, "$"); 39 | self.out.write_all(arg.len().to_string().as_bytes()); 40 | write_str(&mut self.out, "\r\n"); 41 | self.out.write_all(arg); 42 | write_str(&mut self.out, "\r\n"); 43 | } 44 | } 45 | 46 | fn pre_expire(&mut self, expiry: &Option) { 47 | self.last_expiry = *expiry; 48 | } 49 | 50 | fn post_expire(&mut self, key: &[u8]) { 51 | if let Some(expire) = self.last_expiry { 52 | let expire = expire.to_string(); 53 | self.emit(vec!["PEXPIREAT".as_bytes(), key, expire.as_bytes()]); 54 | self.last_expiry = None; 55 | } 56 | } 57 | 58 | fn set(&mut self, key: &[u8], value: &[u8], expiry: &Option) { 59 | self.pre_expire(expiry); 60 | self.emit(vec!["SET".as_bytes(), key, value]); 61 | self.post_expire(key); 62 | } 63 | 64 | fn start_hash(&mut self, expiry: &Option) { 65 | self.pre_expire(expiry); 66 | } 67 | fn end_hash(&mut self, key: &[u8]) { 68 | self.post_expire(key); 69 | } 70 | fn hash_element(&mut self, key: &[u8], field: &[u8], value: &[u8]) { 71 | self.emit(vec!["HSET".as_bytes(), key, field, value]); 72 | } 73 | 74 | fn start_set(&mut self, expiry: &Option) { 75 | self.pre_expire(expiry); 76 | } 77 | fn end_set(&mut self, key: &[u8]) { 78 | self.post_expire(key); 79 | } 80 | fn set_element(&mut self, key: &[u8], member: &[u8]) { 81 | self.emit(vec!["SADD".as_bytes(), key, member]); 82 | } 83 | 84 | fn start_list(&mut self, expiry: &Option) { 85 | self.pre_expire(expiry); 86 | } 87 | fn end_list(&mut self, key: &[u8]) { 88 | self.post_expire(key); 89 | } 90 | fn list_element(&mut self, key: &[u8], value: &[u8]) { 91 | self.emit(vec!["RPUSH".as_bytes(), key, value]); 92 | } 93 | 94 | fn start_sorted_set(&mut self, expiry: &Option) { 95 | self.pre_expire(expiry); 96 | } 97 | fn end_sorted_set(&mut self, key: &[u8]) { 98 | self.post_expire(key); 99 | } 100 | fn sorted_set_element(&mut self, key: &[u8], score: f64, member: &[u8]) { 101 | let score = score.to_string(); 102 | self.emit(vec!["ZADD".as_bytes(), key, score.as_bytes(), member]); 103 | } 104 | } 105 | 106 | impl Formatter for Protocol { 107 | fn string(&mut self, key: &[u8], value: &[u8], _expiry: &Option) { 108 | self.set(key, value, _expiry); 109 | } 110 | 111 | fn hash(&mut self, key: &[u8], values: &IndexMap, Vec>, expiry: &Option) { 112 | self.start_hash(expiry); 113 | for (field, value) in values { 114 | self.hash_element(key, field, value); 115 | } 116 | self.end_hash(key); 117 | } 118 | 119 | fn set(&mut self, key: &[u8], values: &[Vec], expiry: &Option) { 120 | self.start_set(expiry); 121 | for value in values { 122 | self.set_element(key, value); 123 | } 124 | self.end_set(key); 125 | } 126 | 127 | fn list(&mut self, key: &[u8], values: &[Vec], expiry: &Option) { 128 | self.start_list(expiry); 129 | for value in values { 130 | self.list_element(key, value); 131 | } 132 | self.end_list(key); 133 | } 134 | 135 | fn sorted_set(&mut self, key: &[u8], values: &[(f64, Vec)], expiry: &Option) { 136 | self.start_sorted_set(expiry); 137 | for (score, member) in values { 138 | self.sorted_set_element(key, *score, member); 139 | } 140 | self.end_sorted_set(key); 141 | } 142 | 143 | fn start_database(&mut self, db_number: u32) { 144 | let db = db_number.to_string(); 145 | self.emit(vec!["SELECT".as_bytes(), db.as_bytes()]) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! rdb - Parse, analyze and dump RDB files 2 | //! 3 | //! A RDB file is a binary representation of the in-memory data of Redis. 4 | //! This binary file is sufficient to completely restore Redis’ state. 5 | //! 6 | //! This library provides the methods to parse and analyze a RDB file 7 | //! and to reformat and dump it in another format such as JSON or 8 | //! RESP, the Redis Serialization. 9 | //! 10 | //! You can depend on this library via Cargo: 11 | //! 12 | //! ```ini 13 | //! [dependencies] 14 | //! rdb = "*" 15 | //! ``` 16 | //! 17 | //! # Basic operation 18 | //! 19 | //! rdb-rs exposes just one important method: `parse`. 20 | //! This methods takes care of reading the RDB from a stream, 21 | //! parsing the containted data and calling the provided formatter with already-parsed values. 22 | //! 23 | //! ```rust,no_run 24 | //! # #![allow(unstable)] 25 | //! # use std::io::BufReader; 26 | //! # use std::fs::File; 27 | //! # use std::path::Path; 28 | //! let file = File::open(&Path::new("dump.rdb")).unwrap(); 29 | //! let reader = BufReader::new(file); 30 | //! rdb::parse(reader, rdb::formatter::JSON::new(None), rdb::filter::Simple::new()); 31 | //! ``` 32 | //! 33 | //! # Formatter 34 | //! 35 | //! rdb-rs brings 4 pre-defined formatters, which can be used: 36 | //! 37 | //! * `PlainFormatter`: Just plain output for testing 38 | //! * `JSONFormatter`: JSON-encoded output 39 | //! * `NilFormatter`: Surpresses all output 40 | //! * `ProtocolFormatter`: Formats the data in [RESP](http://redis.io/topics/protocol), 41 | //! the Redis Serialization Protocol 42 | //! 43 | //! These formatters adhere to the `RdbParseFormatter` trait 44 | //! and supply a method for each possible datatype or opcode. 45 | //! Its up to the formatter to correctly handle all provided data such as 46 | //! lists, sets, hashes, expires and metadata. 47 | //! 48 | //! # Command-line 49 | //! 50 | //! rdb-rs brings a Command Line application as well. 51 | //! 52 | //! This application will take a RDB file as input and format it in the specified format (JSON by 53 | //! default). 54 | //! 55 | //! Example: 56 | //! 57 | //! ```shell,no_compile 58 | //! $ rdb --format json dump.rdb 59 | //! [{"key":"value"}] 60 | //! $ rdb --format protocol dump.rdb 61 | //! *2 62 | //! $6 63 | //! SELECT 64 | //! $1 65 | //! 0 66 | //! *3 67 | //! $3 68 | //! SET 69 | //! $3 70 | //! key 71 | //! $5 72 | //! value 73 | //! ``` 74 | 75 | #[cfg(feature = "python")] 76 | use pyo3::exceptions::PyValueError; 77 | #[cfg(feature = "python")] 78 | use pyo3::prelude::*; 79 | 80 | use std::io::Read; 81 | 82 | #[doc(hidden)] 83 | pub use types::{RdbError, RdbOk, RdbResult, Type}; 84 | 85 | pub mod constants; 86 | pub mod decoder; 87 | pub mod filter; 88 | pub mod formatter; 89 | pub mod types; 90 | 91 | pub use decoder::RdbDecoder; 92 | pub use filter::{Filter, Simple}; 93 | pub use formatter::{Formatter, FormatterType}; 94 | 95 | // Main entry point for parsing RDB files 96 | pub struct RdbParser { 97 | decoder: RdbDecoder, 98 | formatter: Option, 99 | } 100 | 101 | impl RdbParser { 102 | pub fn builder() -> RdbParserBuilder { 103 | RdbParserBuilder { 104 | reader: None, 105 | filter: None, 106 | formatter: None, 107 | } 108 | } 109 | 110 | //pub fn into_iter(self) -> RdbDecoder { 111 | // self.decoder 112 | //} 113 | } 114 | 115 | #[derive(Default)] 116 | pub struct RdbParserBuilder { 117 | reader: Option, 118 | filter: Option, 119 | formatter: Option, 120 | } 121 | 122 | impl RdbParserBuilder { 123 | pub fn build(self) -> RdbParser { 124 | let reader = self.reader.unwrap(); 125 | let filter = self.filter.unwrap_or_default(); 126 | let formatter = self.formatter; 127 | RdbParser { 128 | decoder: RdbDecoder::new(reader, filter).unwrap(), 129 | formatter, 130 | } 131 | } 132 | 133 | pub fn with_reader(mut self, reader: R) -> Self { 134 | self.reader = Some(reader); 135 | self 136 | } 137 | 138 | pub fn with_filter(mut self, filter: L) -> Self { 139 | self.filter = Some(filter); 140 | self 141 | } 142 | 143 | pub fn with_formatter(mut self, formatter: F) -> Self { 144 | self.formatter = Some(formatter); 145 | self 146 | } 147 | } 148 | 149 | impl RdbParser { 150 | pub fn parse(self) -> RdbResult<()> { 151 | if let Some(mut formatter) = self.formatter { 152 | formatter.start_rdb(); 153 | for value in self.decoder { 154 | formatter.format(&value?)?; 155 | } 156 | formatter.end_rdb(); 157 | } 158 | Ok(()) 159 | } 160 | } 161 | 162 | pub fn parse( 163 | reader: R, 164 | formatter: F, 165 | filter: L, 166 | ) -> RdbResult<()> { 167 | let parser = RdbParser::builder() 168 | .with_reader(reader) 169 | .with_filter(filter) 170 | .with_formatter(formatter) 171 | .build(); 172 | parser.parse() 173 | } 174 | 175 | #[cfg(feature = "python")] 176 | #[pyclass(name = "RdbDecoder")] 177 | pub struct PyRdbDecoder { 178 | decoder: RdbDecoder, 179 | } 180 | 181 | #[cfg(feature = "python")] 182 | #[pymethods] 183 | impl PyRdbDecoder { 184 | #[new] 185 | pub fn new(path: &str) -> PyResult { 186 | let file = std::fs::File::open(path) 187 | .map_err(|e| PyValueError::new_err(format!("Failed to open file: {}", e)))?; 188 | 189 | let mut filter = Simple::new(); 190 | 191 | for t in [ 192 | Type::Hash, 193 | Type::String, 194 | Type::List, 195 | Type::Set, 196 | Type::SortedSet, 197 | ] { 198 | filter.add_type(t); 199 | } 200 | 201 | let decoder = RdbDecoder::new(file, filter) 202 | .map_err(|e| PyValueError::new_err(format!("Failed to create decoder: {}", e)))?; 203 | 204 | Ok(PyRdbDecoder { decoder }) 205 | } 206 | 207 | fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { 208 | slf 209 | } 210 | 211 | fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult> { 212 | match slf.decoder.next() { 213 | Some(Ok(value)) => Python::with_gil(|py| { 214 | value 215 | .into_pyobject(py) 216 | .map(|obj| Some(obj.into())) 217 | .map_err(|e| PyValueError::new_err(format!("Conversion error: {}", e))) 218 | }), 219 | Some(Err(e)) => Err(PyValueError::new_err(format!("Parsing error: {}", e))), 220 | None => Ok(None), 221 | } 222 | } 223 | } 224 | 225 | #[cfg(feature = "python")] 226 | #[pymodule(name = "rdb")] 227 | fn rdb_py(m: &Bound<'_, PyModule>) -> PyResult<()> { 228 | m.add_class::()?; 229 | Ok(()) 230 | } 231 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use regex::Regex; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | use std::path::PathBuf; 6 | 7 | #[derive(Parser)] 8 | #[command(name = "rdb")] 9 | #[command(override_usage = "rdb [options] dump.rdb")] 10 | struct Cli { 11 | /// Path to the RDB dump file 12 | dump_file: PathBuf, 13 | 14 | /// Format to output. Valid: json, plain, nil, protocol 15 | #[arg(short, long, value_name = "FORMAT")] 16 | format: Option, 17 | 18 | /// Keys to show. Can be a regular expression 19 | #[arg(short, long, value_name = "KEYS")] 20 | keys: Option, 21 | 22 | /// Database to show. Can be specified multiple times 23 | #[arg(short = 'd', long = "databases", value_name = "DB")] 24 | databases: Vec, 25 | 26 | /// Type to show. Can be specified multiple times 27 | #[arg(short = 't', long = "type", value_name = "TYPE")] 28 | type_: Vec, 29 | 30 | /// Output file path. If not specified, writes to stdout 31 | #[arg(short = 'o', long = "output", value_name = "FILE")] 32 | output: Option, 33 | } 34 | 35 | fn parse_type(type_str: &str) -> Option { 36 | match type_str { 37 | "string" => Some(rdb::Type::String), 38 | "list" => Some(rdb::Type::List), 39 | "set" => Some(rdb::Type::Set), 40 | "sortedset" | "sorted-set" | "sorted_set" => Some(rdb::Type::SortedSet), 41 | "hash" => Some(rdb::Type::Hash), 42 | _ => None, 43 | } 44 | } 45 | 46 | pub fn main() { 47 | let cli = Cli::parse(); 48 | let mut filter = rdb::filter::Simple::new(); 49 | 50 | // Add databases to filter 51 | for db in cli.databases { 52 | filter.add_database(db); 53 | } 54 | 55 | // Add types to filter 56 | for t in &cli.type_ { 57 | match parse_type(t) { 58 | Some(typ) => filter.add_type(typ), 59 | None => { 60 | println!("Unknown type: {}\n", t); 61 | std::process::exit(1); 62 | } 63 | } 64 | } 65 | 66 | // Add key pattern to filter if specified 67 | if let Some(k) = cli.keys { 68 | match Regex::new(&k) { 69 | Ok(re) => filter.add_keys(re), 70 | Err(err) => { 71 | println!("Incorrect regexp: {:?}\n", err); 72 | std::process::exit(1); 73 | } 74 | } 75 | } 76 | 77 | // Open and read the dump file 78 | let file = match File::open(&cli.dump_file) { 79 | Ok(f) => f, 80 | Err(err) => { 81 | println!("Failed to open file: {:?}\n", err); 82 | std::process::exit(1); 83 | } 84 | }; 85 | let reader = BufReader::new(file); 86 | 87 | // Parse with the specified formatter 88 | let formatter: rdb::FormatterType = match cli.format.as_deref().unwrap_or("json") { 89 | "json" => rdb::FormatterType::Json(rdb::formatter::JSON::new(cli.output)), 90 | "plain" => rdb::FormatterType::Plain(rdb::formatter::Plain::new(cli.output)), 91 | "nil" => rdb::FormatterType::Nil(rdb::formatter::Nil::new(cli.output)), 92 | "protocol" => rdb::FormatterType::Protocol(rdb::formatter::Protocol::new(cli.output)), 93 | f => { 94 | println!("Unknown format: {}\n", f); 95 | std::process::exit(1); 96 | } 97 | }; 98 | 99 | rdb::parse(reader, formatter, filter).expect("Failed to parse RDB file"); 100 | } 101 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use indexmap::IndexMap; 4 | 5 | use crate::constants::encoding_type; 6 | 7 | #[cfg(feature = "python")] 8 | use pyo3::prelude::*; 9 | #[cfg(feature = "python")] 10 | use pyo3::types::PyDict; 11 | 12 | #[derive(Error, Debug)] 13 | pub enum RdbError { 14 | #[error("IO error: {0}")] 15 | Io(#[from] std::io::Error), 16 | #[error("No value found after {0}")] 17 | MissingValue(&'static str), 18 | #[error("Unknown encoding type: {0}")] 19 | UnknownEncoding(u8), 20 | #[error("Parsing error in {context}: {message}")] 21 | ParsingError { 22 | context: &'static str, 23 | message: String, 24 | }, 25 | } 26 | pub type RdbResult = Result; 27 | 28 | pub type RdbOk = RdbResult<()>; 29 | 30 | #[derive(Debug, PartialEq)] 31 | pub enum Type { 32 | String, 33 | List, 34 | Set, 35 | SortedSet, 36 | Hash, 37 | Stream, 38 | Module, 39 | } 40 | 41 | impl Type { 42 | pub fn from_encoding(enc_type: u8) -> RdbResult { 43 | match enc_type { 44 | encoding_type::STRING => Ok(Type::String), 45 | encoding_type::HASH 46 | | encoding_type::HASH_ZIPMAP 47 | | encoding_type::HASH_ZIPLIST 48 | | encoding_type::HASH_LIST_PACK => Ok(Type::Hash), 49 | encoding_type::LIST 50 | | encoding_type::LIST_ZIPLIST 51 | | encoding_type::LIST_QUICKLIST 52 | | encoding_type::LIST_QUICKLIST_2 => Ok(Type::List), 53 | encoding_type::SET | encoding_type::SET_INTSET | encoding_type::SET_LIST_PACK => { 54 | Ok(Type::Set) 55 | } 56 | encoding_type::ZSET 57 | | encoding_type::ZSET_ZIPLIST 58 | | encoding_type::ZSET_2 59 | | encoding_type::ZSET_LIST_PACK => Ok(Type::SortedSet), 60 | encoding_type::STREAM_LIST_PACKS 61 | | encoding_type::STREAM_LIST_PACKS_2 62 | | encoding_type::STREAM_LIST_PACKS_3 => Ok(Type::Stream), 63 | encoding_type::MODULE | encoding_type::MODULE_2 => Ok(Type::Module), 64 | _ => Err(RdbError::UnknownEncoding(enc_type)), 65 | } 66 | } 67 | } 68 | 69 | #[derive(Debug, PartialEq)] 70 | pub enum EncodingType { 71 | String, 72 | LinkedList, 73 | Hashtable, 74 | Skiplist, 75 | Intset(u64), 76 | Ziplist(u64), 77 | Zipmap(u64), 78 | Quicklist, 79 | Quicklist2, 80 | ZSet2, 81 | ListPack(u64), 82 | } 83 | 84 | #[derive(Debug)] 85 | pub enum RdbValue { 86 | SelectDb(u32), 87 | ResizeDb { 88 | db_size: u32, 89 | expires_size: u32, 90 | }, 91 | AuxField { 92 | key: Vec, 93 | value: Vec, 94 | }, 95 | Checksum(Vec), 96 | String { 97 | key: Vec, 98 | value: Vec, 99 | expiry: Option, 100 | }, 101 | Hash { 102 | key: Vec, 103 | values: IndexMap, Vec>, 104 | expiry: Option, 105 | }, 106 | Set { 107 | key: Vec, 108 | members: Vec>, 109 | expiry: Option, 110 | }, 111 | List { 112 | key: Vec, 113 | values: Vec>, 114 | expiry: Option, 115 | }, 116 | SortedSet { 117 | key: Vec, 118 | values: Vec<(f64, Vec)>, // (score, member) 119 | expiry: Option, 120 | }, 121 | } 122 | 123 | #[cfg(feature = "python")] 124 | impl<'py> IntoPyObject<'py> for RdbValue { 125 | type Target = PyDict; 126 | type Output = Bound<'py, PyDict>; 127 | type Error = PyErr; 128 | 129 | fn into_pyobject(self, py: Python<'py>) -> Result { 130 | match self { 131 | RdbValue::Hash { 132 | key, 133 | values, 134 | expiry, 135 | } => { 136 | let dict = PyDict::new(py); 137 | let values_dict = PyDict::new(py); 138 | for (k, v) in values { 139 | values_dict.set_item(k, v)?; 140 | } 141 | dict.set_item("type", "hash")?; 142 | dict.set_item("key", key)?; 143 | dict.set_item("values", values_dict)?; 144 | dict.set_item("expiry", expiry)?; 145 | Ok(dict) 146 | } 147 | RdbValue::List { 148 | key, 149 | values, 150 | expiry, 151 | } => { 152 | let dict = PyDict::new(py); 153 | dict.set_item("type", "list")?; 154 | dict.set_item("key", key)?; 155 | dict.set_item("values", values)?; 156 | dict.set_item("expiry", expiry)?; 157 | Ok(dict) 158 | } 159 | RdbValue::Set { 160 | key, 161 | members, 162 | expiry, 163 | } => { 164 | let dict = PyDict::new(py); 165 | dict.set_item("type", "set")?; 166 | dict.set_item("key", key)?; 167 | dict.set_item("members", members)?; 168 | dict.set_item("expiry", expiry)?; 169 | Ok(dict) 170 | } 171 | RdbValue::SortedSet { 172 | key, 173 | values, 174 | expiry, 175 | } => { 176 | let dict = PyDict::new(py); 177 | dict.set_item("type", "sorted_set")?; 178 | dict.set_item("key", key)?; 179 | dict.set_item("values", values)?; 180 | dict.set_item("expiry", expiry)?; 181 | Ok(dict) 182 | } 183 | RdbValue::String { key, value, expiry } => { 184 | let dict = PyDict::new(py); 185 | dict.set_item("type", "string")?; 186 | dict.set_item("key", key)?; 187 | dict.set_item("value", value)?; 188 | dict.set_item("expiry", expiry)?; 189 | Ok(dict) 190 | } 191 | RdbValue::SelectDb(db) => { 192 | let dict = PyDict::new(py); 193 | dict.set_item("type", "select_db")?; 194 | dict.set_item("db", db)?; 195 | Ok(dict) 196 | } 197 | RdbValue::ResizeDb { 198 | db_size, 199 | expires_size, 200 | } => { 201 | let dict = PyDict::new(py); 202 | dict.set_item("type", "resize_db")?; 203 | dict.set_item("db_size", db_size)?; 204 | dict.set_item("expires_size", expires_size)?; 205 | Ok(dict) 206 | } 207 | RdbValue::AuxField { key, value } => { 208 | let dict = PyDict::new(py); 209 | dict.set_item("type", "aux_field")?; 210 | dict.set_item("key", key)?; 211 | dict.set_item("value", value)?; 212 | Ok(dict) 213 | } 214 | RdbValue::Checksum(checksum) => { 215 | let dict = PyDict::new(py); 216 | dict.set_item("type", "checksum")?; 217 | dict.set_item("checksum", checksum)?; 218 | Ok(dict) 219 | } 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/dumps/README.md: -------------------------------------------------------------------------------- 1 | The included dump files are taken from the redis-rdb-tools project. 2 | See https://github.com/sripathikrishnan/redis-rdb-tools for more. 3 | -------------------------------------------------------------------------------- /tests/dumps/dictionary.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/dictionary.rdb -------------------------------------------------------------------------------- /tests/dumps/easily_compressible_string_key.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/easily_compressible_string_key.rdb -------------------------------------------------------------------------------- /tests/dumps/empty_database.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/empty_database.rdb -------------------------------------------------------------------------------- /tests/dumps/hash_as_ziplist.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/hash_as_ziplist.rdb -------------------------------------------------------------------------------- /tests/dumps/hash_list_pack.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/hash_list_pack.rdb -------------------------------------------------------------------------------- /tests/dumps/integer_keys.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/integer_keys.rdb -------------------------------------------------------------------------------- /tests/dumps/intset_16.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/intset_16.rdb -------------------------------------------------------------------------------- /tests/dumps/intset_32.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/intset_32.rdb -------------------------------------------------------------------------------- /tests/dumps/intset_64.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/intset_64.rdb -------------------------------------------------------------------------------- /tests/dumps/json/easily_compressible_string_key.json: -------------------------------------------------------------------------------- 1 | [{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa":"Key that redis should compress easily"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/empty_database.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/hash_as_ziplist.json: -------------------------------------------------------------------------------- 1 | [{"zipmap_compresses_easily":{"a":"aa","aa":"aaaa","aaaaa":"aaaaaaaaaaaaaa"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/hash_list_pack.json: -------------------------------------------------------------------------------- 1 | [{"\u0002\u0000\u0000\u0000driver_id\u0004\u0000\u0000\u0000\u0008\u0000\u0000\u0000\u00ea\u0003\u0000\u0000\u0000\u0000\u0000\u0000my_project":{"_ts:driver_hourly_stats":"\u0008\u00f0\u0080\u0081\u00bb\u0006","a`\u00e3\u00da":"5}\u009e\u00aa=","\u00fa^X\u00ad":"5\u00f5\u00daa>","\u0018\u00a5\u00e5\u00a3":" \u008a\u0005","_ts:driver_hourly_stats_fresh":"\u0008\u00f0\u0080\u0081\u00bb\u0006","\u0003\u00ed\u0010F":"5}\u009e\u00aa=","\u00e2s\u0086\u00b9":"5\u00f5\u00daa>","?\u0009e\u00d3":" \u008a\u0005"},"\u0002\u0000\u0000\u0000driver_id\u0004\u0000\u0000\u0000\u0008\u0000\u0000\u0000\u00e9\u0003\u0000\u0000\u0000\u0000\u0000\u0000my_project":{"_ts:driver_hourly_stats":"\b󱁻\u0006","a`\u00e3\u00da":"5\u0000\u0000\u0080?","\u00fa^X\u00ad":"5\u0000\u0000\u0080?","\u0018\u00a5\u00e5\u00a3":" \u00e8\u0007","_ts:driver_hourly_stats_fresh":"\u0008\u00ac\u00b2\u0081\u00bb\u0006","\u0003\u00ed\u0010F":"5\u0000\u0000\u0080?","\u00e2s\u0086\u00b9":"5\u0000\u0000\u0080?","?\u0009e\u00d3":" \u00e8\u0007"},"\u0002\u0000\u0000\u0000driver_id\u0004\u0000\u0000\u0000\u0008\u0000\u0000\u0000\u00ec\u0003\u0000\u0000\u0000\u0000\u0000\u0000my_project":{"_ts:driver_hourly_stats":"\u0008\u00f0\u0080\u0081\u00bb\u0006","a`\u00e3\u00da":"5G\u0091\u0008>","\u00fa^X\u00ad":"5o*\u00f9>","\u0018\u00a5\u00e5\u00a3":" \u00a9\u0001","_ts:driver_hourly_stats_fresh":"\u0008\u00f0\u0080\u0081\u00bb\u0006","\u0003\u00ed\u0010F":"5G\u0091\u0008>","\u00e2s\u0086\u00b9":"5o*\u00f9>","?\u0009e\u00d3":" \u00a9\u0001"},"\u0002\u0000\u0000\u0000driver_id\u0004\u0000\u0000\u0000\u0008\u0000\u0000\u0000\u00ed\u0003\u0000\u0000\u0000\u0000\u0000\u0000my_project":{"_ts:driver_hourly_stats":"\u0008\u00f0\u0080\u0081\u00bb\u0006","a`\u00e3\u00da":"5%\u00b7\u0098>","\u00fa^X\u00ad":"5ϭ@>","\u0018\u00a5\u00e5\u00a3":" \u0098\u0004","_ts:driver_hourly_stats_fresh":"\u0008\u00f0\u0080\u0081\u00bb\u0006","\u0003\u00ed\u0010F":"5%\u00b7\u0098>","\u00e2s\u0086\u00b9":"5ϭ@>","?\u0009e\u00d3":" \u0098\u0004"},"\u0002\u0000\u0000\u0000driver_id\u0004\u0000\u0000\u0000\u0008\u0000\u0000\u0000\u00eb\u0003\u0000\u0000\u0000\u0000\u0000\u0000my_project":{"_ts:driver_hourly_stats":"\u0008\u00f0\u0080\u0081\u00bb\u0006","a`\u00e3\u00da":"5*\u007fo?","\u00fa^X\u00ad":"5^\u00f0\u00f8>","\u0018\u00a5\u00e5\u00a3":" \u00d6\u0005","_ts:driver_hourly_stats_fresh":"\u0008\u00f0\u0080\u0081\u00bb\u0006","\u0003\u00ed\u0010F":"5*\u007fo?","\u00e2s\u0086\u00b9":"5^\u00f0\u00f8>","?\u0009e\u00d3":" \u00d6\u0005"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/integer_keys.json: -------------------------------------------------------------------------------- 1 | [{"183358245":"Positive 32 bit integer","125":"Positive 8 bit integer","-29477":"Negative 16 bit integer","-123":"Negative 8 bit integer","43947":"Positive 16 bit integer","-183358245":"Negative 32 bit integer"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/intset_16.json: -------------------------------------------------------------------------------- 1 | [{"intset_16":["32764","32765","32766"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/intset_32.json: -------------------------------------------------------------------------------- 1 | [{"intset_32":["2147418108","2147418109","2147418110"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/intset_64.json: -------------------------------------------------------------------------------- 1 | [{"intset_64":["9223090557583032316","9223090557583032317","9223090557583032318"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/keys_with_expiry.json: -------------------------------------------------------------------------------- 1 | [{"expires_ms_precision":"2022-12-25 10:11:12.573 UTC"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/multidb-skipping.json: -------------------------------------------------------------------------------- 1 | [{"foo":"bar"},{"foo":"bar"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/multiple_databases.json: -------------------------------------------------------------------------------- 1 | [{"key_in_zeroth_database":"zero"},{"key_in_second_database":"second"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/parser_filters.json: -------------------------------------------------------------------------------- 1 | [{"k1":"ssssssss","k3":"wwwwwwww","s1":".ahaa bit longer and with spaceslonger than 256 characters and trivially compressible --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------","s2":"now_exists","n5b":"1000","l10":["100001","100002","100003","100004"],"l11":["9999999999","9999999998","9999999997"],"l12":["9999999997","9999999998","9999999999"],"b1":"\u00ff","b2":"\u0000\u00ff","b3":"\u0000\u0000\u00ff","b4":"\u0000\u0000\u0000\u00ff","b5":"\u0000\u0000\u0000\u0000\u00ff","h1":{"c":"now this is quite a bit longer, but sort of boring....................................................................................................................................................................................................................................................................................................................................................................","a":"aha","b":"a bit longer, but not very much"},"h2":{"a":"101010"},"h3":{"b":"b2","c":"c2","d":"d"},"l1":["yup","aha"],"set1":["c","d","a","b"],"l2":["something","now a bit longer and perhaps more interesting"],"set2":["d","a"],"n1":"-6","l3":["this one is going to be longer -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------","a bit more"],"set3":["b"],"set4":["1","2","3","4","5","6","7","8","9","10"],"n2":"501","l4":["b","c","d"],"set5":["100000","100001","100002","100003"],"n3":"500001","l5":["c","a"],"set6":["9999999997","9999999998","9999999999"],"n4":"1","l6":["b"],"n5":"1000","l7":["a","b"],"n6":"1000000","n4b":"1","l8":["c","1","2","3","4"],"l9":["10001","10002","10003","10004"],"n6b":"1000000","z1":{"a":"1","c":"13"},"z2":{"1":"1","2":"2","3":"3"},"z3":{"10002":"10001","10003":"10003"},"z4":{"10000000001":"10000000001","10000000002":"10000000002","10000000003":"10000000003"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/quicklist_with_multiple_nodes.json: -------------------------------------------------------------------------------- 1 | [{"quicklist":["baaaaaaaaaaaaaaam","baz","3","2","1","bar","foo"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/quicklist_with_one_node.json: -------------------------------------------------------------------------------- 1 | [{"quicklist":["baaaaaaaaaaaaaaam","baz","3","2","1","bar","foo"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/rdb_version_5_with_checksum.json: -------------------------------------------------------------------------------- 1 | [{"abcd":"efgh","foo":"bar","bar":"baz","abcdef":"abcdef","longerstring":"thisisalongerstring.idontknowwhatitmeans","abc":"def"}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/regular_set.json: -------------------------------------------------------------------------------- 1 | [{"regular_set":["beta","delta","alpha","phi","gamma","kappa"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/sorted_set_as_ziplist.json: -------------------------------------------------------------------------------- 1 | [{"sorted_set_as_ziplist":{"8b6ba6718a786daefa69438148361901":"1","cb7a24bb7528f934b841b34c3a73e0c7":"2.37","523af537946b79c4f8369ed39ba78605":"3.423"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/ziplist_that_compresses_easily.json: -------------------------------------------------------------------------------- 1 | [{"ziplist_compresses_easily":["aaaaaa","aaaaaaaaaaaa","aaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/ziplist_that_doesnt_compress.json: -------------------------------------------------------------------------------- 1 | [{"ziplist_doesnt_compress":["aj2410","cc953a17a8e096e76a44169ad3f9ac87c5f8248a403274416179aa9fbd852344"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/ziplist_with_integers.json: -------------------------------------------------------------------------------- 1 | [{"ziplist_with_integers":["0","1","2","3","4","5","6","7","8","9","10","11","12","-2","13","25","-61","63","16380","-16000","65535","-65523","4194304","9223372036854775807"]}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/zipmap_that_compresses_easily.json: -------------------------------------------------------------------------------- 1 | [{"zipmap_compresses_easily":{"a":"aa","aa":"aaaa","aaaaa":"aaaaaaaaaaaaaa"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/zipmap_that_doesnt_compress.json: -------------------------------------------------------------------------------- 1 | [{"zimap_doesnt_compress":{"MKD1G6":"2","YNNXK":"F7TI"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/json/zipmap_with_big_values.json: -------------------------------------------------------------------------------- 1 | [{"zipmap_with_big_values":{"253bytes":"NYKK5QA4TDYJFZH0FCVT39DWI89IH7HV9HV162MULYY9S6H67MGS6YZJ54Q2NISW9U69VC6ZK3OJV6J095P0P5YNSEHGCBJGYNZ8BPK3GEFBB8ZMGPT2Y33WNSETHINMSZ4VKWUE8CXE0Y9FO7L5ZZ02EO26TLXF5NUQ0KMA98973QY62ZO1M1WDDZNS25F37KGBQ8W4R5V1YJRR2XNSQKZ4VY7GW6X038UYQG30ZM0JY1NNMJ12BKQPF2IDQ","254bytes":"IZ3PNCQQV5RG4XOAXDN7IPWJKEK0LWRARBE3393UYD89PSQFC40AG4RCNW2M4YAVJR0WD8AVO2F8KFDGUV0TGU8GF8M2HZLZ9RDX6V0XKIOXJJ3EMWQGFEY7E56RAOPTA60G6SQRZ59ZBUKA6OMEW3K0LH464C7XKAX3K8AXDUX63VGX99JDCW1W2KTXPQRN1R1PY5LXNXPW7AAIYUM2PUKN2YN2MXWS5HR8TPMKYJIFTLK2DNQNGTVAWMULON","255bytes":"6EUW8XSNBHMEPY991GZVZH4ITUQVKXQYL7UBYS614RDQSE7BDRUW00M6Y4W6WUQBDFVHH6V2EIAEQGLV72K4UY7XXKL6K6XH6IN4QVS15GU1AAH9UI40UXEA8IZ5CZRRK6SAV3R3X283O2OO9KG4K0DG0HZX1MLFDQHXGCC96M9YUVKXOEC5X35Q4EKET0SDFDSBF1QKGAVS9202EL7MP2KPOYAUKU1SZJW5OP30WAPSM9OG97EBHW2XOWGICZG","300bytes":"IJXP54329MQ96A2M28QF6SFX3XGNWGAII3M32MSIMR0O478AMZKNXDUYD5JGMHJRB9A85RZ3DC3AIS62YSDW2BDJ97IBSH7FKOVFWKJYS7XBMIBX0Z1WNLQRY7D27PFPBBGBDFDCKL0FIOBYEADX6G5UK3B0XYMGS0379GRY6F0FY5Q9JUCJLGOGDNNP8XW3SJX2L872UJZZL8G871G9THKYQ2WKPFEBIHOOTIGDNWC15NL5324W8FYDP97JHKCSMLWXNMSTYIUE7F22ZGR4NZK3T0UTBZ2AFRCT5LMT3P6B","20kbytes":"TO29G8HV1EAC44Z6NZBLD06R6P6Q4271M6AOS702PSZ2NKWF6G011IXPU4CX0UCJ1V9HUDZVSXCX2GM1N9HT8FR4W5XUPWQDO0Q5Q6PI9H1J42RT5V0X8CYK80B9YYSIQBYWA2878W8QVCIM7U2YVEFXBKOQIKA1AEUX86JDRG6AWWA7GJFNWUD6T4T5TRICEG2S9LSBUCLCAEQ7JFET8DUFN1RN1PY7NJDM2YN5JWNZHHD9IZ1HN7NUMTBQZ4GU5MPOXY84BMDRH27MLNCTT8H817WUUCRMYGJHMRDVQQ3NDPSQOWPSS9JJYOFCZY0ZA6R7K9FRF7SAX2FOAKCK1Z3RR0QN8FT508WAWYAYOOPYC9S7CK9JSR6QF046495KTO0EINUDNWJKS0GWJEG62VRWS52IWPVSUL76CKG9HPYK2UZZBSDJIJB0QH0HDGJVYWOXM87LH8FK9BD1G4YW32PT5QPIZDGBC11A8BHIUX855X29B0E2D8AZ4EIGWENSCCAS3PE2VD59Z4OGULDIMVK33HI3ZU2BOSLGJQHSLCWQUKVK0QKNV0MZCU792T5MECW8JVME719MU56GFF4C51KVR0UWEIS887VBFC5MFXPZATDORK52PJSN1I1SYH48S65YWD10S5IDXD29MCTBDKKD3ZRGOG1RLXZ7AE7U3UKR3E1B57APIC2UGFNY8WI5K757QS0F529JMCW3M97YX9WPQ1BS3BCO1GOL9T4OGA6B132JZWI0G137V08S134E94LOFX9WCW0BXJ3OX4KLTAVLIBG8X3IBJK3AONMB7SR8QST6RNQF068QKGLGIWD70I1JSNA6JS50ESMGE4NUXY0783VY5LZZPTUG6B8GTQF5I2OCJSVS7VOSH0XB6L8JRP434HBAXZPBB52I95CTLGFBIIPNERNQQS4VWRXNK4KF0MEG8VBE9XFMYL4TQL3WMZDD88WZUK3D0YRM13Y83D72ER01IGE05MSYVY3W8ZWOCH046O8TER8EQ79UUO32BXVKSASMMSGUFVLTV6EIWVVJ8H9K37Z7BLEGA5FZ3BID0NC18J86XNGOCWALKM9PUZF57HGPG2OCVYPVWSO2904CL1JJOVE5DN3WQBS47QI7YWPKP9FKPA8CJHH6T5LC93OYE02OO3SP30ASXKOOI1MYNWQQRTKLFA6IRGEZZOH8IWNXC5FIXCOYJ2THG43OLZEWV7MES4JEH30BPRF6KG0SM4B1KXTTOQIK9HA3DXTTFLAGB9IJFFD2AAXO802SB68DVWI7OLMUWWUQCJZKIOJBXRYSS2NZXNEHM9SI4UVNT49CFHAAVXPAK6GYXTNOYR6156CM3CA5CLO6M7GNTK1AXCS1A6ODR75O9JO8BN76KGCTHR4IIGM7GTO0V3304G4HMEGQHOS1KXODD32F2X5RDXZZSDNKKC3JB2DVERRR837PL5GXS48YCNMU8VZMRL9NXAXTA3JP7OINI02UXLMK0MJUTG1HTRU2VMUBSWHP3V4HXZT2BZG2WNMCZI54ZS2ZOKBLLU7Z8HUUQ6VBK70QMVJA49XA59T55TVVBFGJ7X3CTHF39Y95E7TEODDLK7CJ6XJHTESI7AUA19KT9AFOMDNE0IVQVZID3EYA4QOEDNJ5Q1OFOANHC1XP1MHLFZQ3NL9LOQ1MQIO5OWXTZZ6WK1GBD6O4TY35HWN0FMS28Z4ZPAYC4RXOGCWDIIL7PSOESVKG53HGG1298MB195A0UZE36ID49KVOGEH3YS6VWOFTZEE6AFULWLRZK0KKI0PLQI3UV1DXUMHRD5982ZUJAQU2DGBQIZPFQJ53R0H3KECVYKEYAK81VND80Z1F6SN3BTYXFB0XK696QQH5ELQ6CHKWZFBZYL0HFA4TUHJ1FPQBLGVGD5CE5S0U030ET2HLC50LKQ87CCCRZAFLDHWB6SNUTP19LH4NY85HA4I4LK8GJNZ5Y89YHP9G4KTJ4OEZ8QY0FAAN5DBSMV6YDHTQ60OV26RUYOIQAT899IYTAI4RCV8DPG2WUM9FE78YRIIPEUA84OND8U33ZN7SRADEWN313OZWDM7LSKYSUIHG1DXKLRMZZCP869ZXMEA2P3F8J30UJCTAZLK5D3AZLGOWCFUL4ZC3YV9WE3FFER8POIVKURXV3TYF2PGUTF7Y977DM3D3DNBMZTPJW3Z0NKBSJAX7MM92D3T4Q73ZYJJCZNQZ3P4Y7IIWLTEI6W8KGY1Q53BL8W127ZBPGZGYYWC0ZI5PBGVXCQSWXLNOY0GSRBVK4NAZD12IHL6AKMFDESLE50G5EJT6L9RKTZOH87J0RD283CUTCE6A9231UVWDFQQLBQ5PL0R7DO4P0D9LLB1D5Q56AOQ747M9TWK7IZ6VM273HW4OJF2FLME8EQ8LD211MR4S1AF3F1XIZ2A9R43WNB88H0FTGL7UV18EGJ9TG3P56JA6M9G3724SGJIZTJYYSOH18GE7SKK30M6JH3TPH5Q62YTKQ5HEOPTRLMELN302DP3OPTBB8ETBI9CFZ2IHH6TD4Q7HZSCDBH1KLF9X1B3PRI9WM27G2AOSP7TSTF0JC0GSPPF98PH8WSNAB17DXZPHK4RAYC4XOEN1MJ9XVN8FZKIXNF8NW8IVVXUKJ6GS5NQLX5BQ2DLUW4T99XZ5H9VQ9D5JHU766RMYM0LG9OA3FK4CUGGX7JJ0OE9BYBDCPHNMB7494K2HIXKZMBHV6KW6KESHF9QQWDAM6I716OMTNA42HTH8LJW3DPCLCVWU25GHP6MHBYY3W0LFT7YJWGP0VTKOC6BEF2CA4IYSRLQVO7EXWJULD829TA6J9VFJWL5D271HTFYIN7YP8XX33VQ187UIV9KFVWO5YR4DVU7NYRT9RM39MRYK36IBWT45YY7F0U2V4SQBJTZLK3RV35EAFC3KFBY8LZYVUN8UR2D4U044XOIYGEMN3Z3VR7413TJEZO12IQ53E5MCMHBHNHZF8MNMELEUD6BKEONC3J15FVYBFY9F1HRR4QXORSZUYM02SLYDG9VSF6YIX98LBFIHYBONUFY8U5X2H28LT26IA8R845T4K3BMF95AR8LS8C9I88C5MXCQY71UQEM6UP9VC6RZRKT08OFL99I5MM51TZWQC7X5NQ6P1EJR7KTR197TMET2NQAAZIL0SJLOKMVE53WE01PRQFNSIR4HP9S2E14GRQE50HIODG1B8BG9BG4ZK8RUC390H0YZVH736WHE2O4819TRHO198LHBXCNP71HJSYRQJHZO0X7QP4070FU1ZXN5CLYKMFC37MM5G95WRC8Q63I2MEOC7PL6V7L3FQMCYXG52840ABODC188W0HTVZYVVXY6PX5PN029CY3ZWRQF7VDZUZ2UAQOI3ATJGRYNGTSLJGNUDM8GKXR9IG6C36LV1OPW2AJINUKJG7HNK7MRIEMNWF16GP3KIG72R5826SFFGQNFU7OQ9H2OIF8DPU93AOA7KQB01D5VETLO42P5XJT1O60PN83OWXMEZG5876U1C8RK6203JS2Y9CZGSWBSB1D543SOYXKYCOBQU04GBQQ1WZZVLNRWWMX9NIATY4L2OV99UFPMLKS1DY4EZT3RVZQ1QYKT4A7N4X0713PQ6YQO9N8AE5YLAOOT7IUR3VDOE0HCCAFG0VZ42HP6OK1R8MCRO37AS2QESD1CIZPNCAPMPUVCEZK1197TQ4A4EV9LGRJAGAGD7VKI7WXO0FS8504UQ2D3LINE2RO72O1FMQ8LMLU150OTUUXAA6PIBSOMZR1S3YLCRWVQ36SBTI9T08LZ6U9OI32VFJ19H3Y0PQJ3488ELUXVJFCDQ8D4WYBZW8MW9IFP270TEVU34TXI98UL9V67EK24FCG18SHMD075ABXMFI1PVGHQVJXSNGSJJSGY5YLBDYURG2FKV9BWX77M300IKSZTMA7A9SGQHL6ZTTZVBMOH0Y3T1PXUWK4IRJCD8BJ5WNU3P4394QSSM31JPA9WONSN1AUMNCFVKGPE32UJGNPSSLECZZYU4X1RKUH68PJD85IAM8AD2NEGQU71B5A1IETA89D6R2MLZFER6GLGR1OUCUZXRKDUXSB5WIUZSJW08LJNGWI0BCJEYCCXL8DAF2QQEWL0M1TAFDM2NTB3FXITJYBRTK9ZQXE7SB5CN5EK2U523M1NLWXF15J283T34WK2V7A9Y2FIGPGNLCVP6MZ5VD2ID91MNEEEJHGZHQFE9OVM48IDQGDBT1LAP2ZI61T7PMFA9QLLV14OUJ2I500RR59O1AIBTCGJM91XVE1RS6NZ7ZXT1OUHEGXWIXV8QC5XW51ZZKXN8M0UQ38NV5QQRA5HGM0W28GN971PMKQIXII69YYHY6E02PVBO2GFUIEAQEUUV9BQXCOY01OG9XME9PDPQU86CJY3E38OG3B149ZGESAYXASGZF69X0E7MR24OB97HQ31INT39TS5PR3ACQS7ZA3K3N8PZARGQYSACFGMN6MZKLVWV6K21ARY8OT5C8Y8FHRTGC3U1HJVRFB37HPUC2488E7HICYLZVYZWH3D7P1LTTMD1GDD1OL3IWL21PQ2TH5YTXN32N57564XQV13KRSWOUMPEY9A877RPIH14CY4KT4U3WRBG9LJ389Y00LSEOVUF3VCONXT1R0X4Y6TP64LX2VEWHIHWPPHUC04PY1VGV1ID5SYZPQ3XP6XLUP9U9MXBFBPGNZV6X43G6FAN92ZVP0NS81LO1ENJ2PGWKAJZY904WV0F6GPP0HU0YDHFEMESVCNJWYCF2REA9KK3Q95ZU34GUFBFFI2YBVPU1VQ2Q8YQFHQWQKCJSR2UACOQ3CMM3YBFX1YTBEJNDHGYV8XO3M8HEJQWI5UT5CYO7PFVUMQQ4H1P9ITTHERX7NRD4Y4A23OQACYAVVKVR5EUS3PSGBX370ATD2NLG8V99S5TYXXUBJXMC6QF80BAHV2Z2VRM2XQOTEZFTXXN9Q1ZHGKXK7X3OYT36DDH08C7AYFWTF0XQ0NVZUB7A09U64MQJ6MDLLZW0GTN8ROVNSTIE44UQYDSF5Z5WTCY3AC3DGV5RLC3HBCDHOIXMWIFLWOUGUGOC7YH87OUKLVFLABZH2HLLECDZIGN6CA9CWJ81JBIX6W6MWXEEUX4ROM0830B4QTOI9LD6S31QD8PP75VTMKTCA71XSXSK0K8W1VEWOP265N2MQFRMNCFI3HUSOH8Z0GEQ2UHDVPA7L2WIOVTDYD3QWNWI2GJFFKB9WT81IBO99VLNV01AL40AEEQDKORX77XPCCSOXQ6GK8DHFOC19OPO0G1SV9L3A8IMDWZX68SG55P3PUVZIGSWZULY62X1NVZCKGFA56BBFDE04U26SW4D2P8ZE9O0PNQBP5PQEI90VFUZJC2WAW8DX3GE61V2Z862D8SN99PZBV838GJF3C3VCVWTYVDK0Q5XTRUDNCHP006VZSH2AN8G4GBFT4R58UK4DAAIGTU7DZMVQO1VI27EU16BOW4QXDZNTAL93B6OY810LUOSYOCOIQFO6YW8J11G4OJAKHAGHY68O9MTKCI7242SJSWK7WWI82OWC1BNL537M3M364TR9QE53IKG64PV4VOYWJTQ360CAGAAN2316Q1L8EIUIUYQEE82TY4GI2K1ULBE6YSUGCYG67NWBTNXSH933YWY1GLLR2UCGF4M26SAP8NOD1X3THI9T3QIJCRA2OHMSTL9KBAU9G4397T0CE29W976METU1LGIUXF3AQWQIW05NJSD2HAY2RBEY96M1QL6SOOK5MPUE743YGED4G367ZV3DV6H7SAUSORS5N3UCND7HUNHFYBN5UIE7W99MB72OVXUXHSNEM7T3P4NA49ZMSJM1NP9UW57VSX9DS1DH2YK4HZ41G6OAM306R4MH3F2FCW02MP9IM51QA8FIBYW64V41QTPNNICGZA0NZGJ93ORXKC1VK392JUY2K9V7WPRJH0RMD03NZ84KMGEF9GJK0685XDEBGMC0YAKXUW4CSJTJG9XLQ0YUCZTPJ4EH49B5T5DNXZXBMYC5E5DC9SGJOP0SM9HASZMYMH8J8J0CL5I7HKIAWC1UZZA0H59VKN0PCWOWE9JDOW1MR1LK6F57TJ4M0MFJKFRZJE9W8RRXADHCFSKHDACXLJCSKHQUH2X9KZEPIFWNK699IUMUS18BLNWUPMJB3231V5I4JVSF5WYOSHDFPSVRDEEI9DTIIKXXEM7F0XHR6P8S7635AQT5B04QQEYSJYX6FVT63MKXAOPIXWPQ9Q2L6IY7JNBFKMIGA6QTBNVX2038P7FW6I57GEQP27P115CZ9NYFQH3RGE4RV6GB27PNAO63Q5FKYYSW8V9IIXLNDWAI8I3OIVKT6FBNYDQ70URYP1OJUZPG0HS07YKCOLOSYHU5C8F83YV2AGGNUZW0FAIXZ65W96V74EINI9M5F3GVR594US0WDCZUWH3OLQQWVF8ISV33KQU0JEW2RWCPYZKAOZYVNDN2A27EU8AME4PKZ4KF4ANOW82KX7H1JL038C2R1NUP80UHN9ECK8W8BEGCRVHH223W7GN1XD1EICRXHS7A9JXFUE3D6LWHIJ0ZDYLMIIBH4BFXO5RCDJE1R2E9V4JPYBC3QM53G8ULUR31WHZUUQAB862H4WUNIIH2II8IH7E8QULGGXK7P31L5CLL3K8GM88ELJ2KBQXZ8BVWVXVL08A1GRHTWXFBBVGCIBXK9Y4NLKCC2CCGLIQCKAZB1AYJGBXDH8A49XG5QMJVCKOUCKG5QDH84YMCICNXROIHK8Y9LZ4YEKQZLVW1O1WR2XDM8T04QNVP24IRAGME9743GY5WMDXHT66X3GL9BJTDIM7428B4IJ93TUZB2BSQB1SYXD7IZEWHG1U5L966KEEPY08QK4UONKT3ZUPCULWV9EY0EAMI6SJINWPUJ4DKF9U4HVEIU1UI924RDPYWKIML5CAA6FFZD4SHB1SXA9BQPVBN214NLDLRZ2A5GB5TTRNDSX917XGV5KOEQBSCESD7D0SFI22N3Z082PVQT73KLXL2IFGA6HI8PM08CEJCGIGYYSP8NM57MPT76RBQKGXHG2U4I2UR10MGP4K8E4C6IQAL1B5RWK45Q5NCMZY3HZ35YG0TCAB8WQCHK9WVTZBVMG7794FIEVYO0U1B359A89I33LD0LINGDKPNT6I4ZUKKF57WPEI13AW1MN0OLYM4UELKFUI3TWPH9I7J7E6SXO8ZLQP6DZN6M6FOQD99U4N8ILZA0Y9SZMMZOVA6PT5FV9RGK09IZ4XOICH1VQUU6N7XEHK4YKMXRX4YOFR5S5SY4PRAXME1VN07WQVAI3Z7XU0QD52J94EJ81GQEWX00UP3O95HUID0NZV1WAQNXR6TMDG04MU9Z93F0PI1XYNU6QIDN0PA6E5N9SCTPBN6EVMIP8HE1QH3B8CL8NF1TURI7DTGBX2MJMY7VZUXI62Z7SPXB43AQE7F22JAS06YF1QTA9G9Z8IXWVF3G87P847BLLTZ02ERH2XRP750OA1AD55ERSKQLJENHISJMCK2IDUEA79LAYNJICALTDYSSW4AKBX2OV1ZGU3EU4WAIJZZDOXHQAG1PTP7FJKZN1O84EAQVIKKF5H2108VAT1K7MB7F3TZLVSUHFHELQJ6VZUS015FJ8P9RFBNBQ15GPC64HAIBFXDHWFZ98XWDTTY0U6WBKZ4F8894DAFOI0I1VZQL8BQ749CWLLK89CBGIIPPXNMJTGX8IF2XK7D1NNSWIJYLEXBB3QK29IPZX9XPBJ6ZAUYS12RWV7EK856GG3DLO59B4I11T5XOXTRMFPXLF6E5RJIS1L28PAQ7VMAL66M14W3BLCA5Q8NB0W8A2T9UVJRVH35GVJ91TC717NVDFY1TAISWJQTESGUO2TKNZ3P3W1X8CD9CY3D8DV9DIKD1HLK0M7P46IZ95ULPAFALTUSGXVXDUZCQKWQ6AZ96D2HK4YQWX25V0HL20BQ5YHYHT04COICIHHFPJ6UTCK1NPR7VISTUMU3N5MNU2TE685PXSWHRC83YQ8ENN1RICJH2556GXK326OYZ7507Z0UJGOYKI4OE48O2SHXX3CWNLW62YZ33Y2Z4QB3RMXBX5RRN6LQFRUY7ZVIP4H8YGYEMAOB9JYVR3Q0220MLPNNTYSH2A3JLKJCFHIWFXR96R24VQY5JJRE8GOWMHAYHF7VOQU6FIWS2O3X28D29X1M0I1QJMUOXA8RBB5N3LFYMZ799RQX1EW93TPWWSZ7M0F4EOKJFIHQVSRR41H1CZ84VCZK14BAG8WZKNHBEHM1FNLFQ2VUD1NTP02R2HV2QJ4N9UT6AIAH11T5CH5PR1GUPR1C0UIMI9S2EZU7UMA1FVIG28FOG4PSPRHN9DDC803URCWCDLHNL30TAHS2IETNGBL9B2LM4EDQSR1E88VHD0L82WDFXC4GKZ32SPQVZ1XMPG08VQHZZWSQAQIW90ZAZESNKI67I6E1XFFMHIDR47KXUINHF2AOFWUVL10NTDG0PKWA5SAU5QE260RZ3OZSIXYNZYCX4FYIYUXVIHZAHGAM2QQB1B42ZLYT6MOHZVQD7T95MBR62RYIC28WNDPPP68RYQXGWHJCB3JTQ40B9ML1CLMM05F22LHBNL3Y0XFHKBC8QY72ZLNF54N4QDS0TSR770UGF3KA05NOWMWMADC6UXF0GPB6BJTGA0GZE9OEI1HJQ6L9HTW8C5WW900PDIC1MUJWPWSY4E41TRGGX8FSI7T2C9GWV29MDC2QSK99JE7U2HXFATQI7UT9CMFJSYW0E009N1IU700I3NHKJAZWDDL5T7MCLC9ZM6YB6QWZSGEGDC5SW8CQZVM6GWVJFPZX94GGY4SBIX6CLSKZKJTFOPFN9QPDV816W1L1HJQV280ER53EDEN46XEXF85UOB1HT6TLY3SKTYDXW8ACMDJTUR5HWU2I2W8KDW8G4KGUJWY7JO9NQMZPUS47MUZAEGE97Q9RD982GGJDPGBJXXG6UCMO3NNJRVN45L286F02RVI5BUX9L7GESVHB4HA71R6XN22PG2J4EIGJ476T5KX776J7UQ0LDN6ILSWXRYQ25VCHAWE50U2R0L6U8E3C8ULDNXFU747PKHFT21ND2277JMX3KIL578A7AKQX25JUA2Q9KBLI30XM41PCJXQ9QGA82C0XLN9EVRAMAQAJF8N6WJP1YATKRQA7KSVWAH9U0O0PCX58IQTHKQITWOJSM3LR4BI8E81MP8PMJBX521OJYCASPLBVNFLN9WSHLAMEE3GIO72A54EWPRDN6Y3FTSFKC6ILBSCZDNEZVK9VP8VSO4YMH6NR6Q6AX3G7TCGD50UCRY30WTU7PFQIYNZRFEVK7ABS85P6D5GW15NOP0R9ELG8COMPBGGYD4KZV601IV7FXTP8Q29OO4JPZX23NH43D0836DEOX8IU0GQUMEYSFDMIOAK1TZ3TZE9ZOOECZ00GNID3ZK74X9AI75W8WDVYS931CGXSW0JYPJUX528OXH8W0I3LGCK68B0LEA05QX95TWBVXY68S26I5QNTERZ83XUZ1Y9N2KZIJ1LV88Y01X64R803ZFKGGMABVA07CN1ZE1LAXFEPNRDYS8XU98TENE5MBQZMQ826G79UELNRXA77GYPG7NZA17H5BIKW6UX4OZ394WTJD85OGFK9IEBI2T71RTGE6GZD2FUXX0J8P1C8JX66MS3Z1MKBOY92OOZM815UOTTPYE96SLZUQE15RG1PMPY7NJDZU5HH0Y13PNY6BF9ND2SR6LF8BC8IG8GRM0AS5HFSU5XUVD73PAHMABXEQ9YPV1GE8BG1S5BB8YMAUJXV1DTKJBTNIZ3QJ9NPF889FLDQ1COEFNGWSKF3CV1OLV1QUMSEGQR7HBDNTBG1OGZU06AVM0YPFM4W0LM00VMRY469S99Y1NLX5R5UXNWZEV264TWDHC2POKXUGX9MCV6ROCCT325SZ0318WAKOR30QWE347PSWUN4MLV3WSPRLXVHE67IQT2F2BC5I009I4F2EXA9U9LD85AY8D5XVWNFG1JUOSOBB5K7XLEH0234RELJDQA1EHOPZQ5DBDXQGQ9OCJX1HH2DZS1HQGNMKKX1JNT522SA48O1TYSOCOBPTDMGCI10WUYQY1Q3OP2BYDOPILQLPQE08K5E3AUMYID8NOH0IFOQMNO3HE93PB69P3LER8CIGCERV8SC161HK0WGSL1KQJ5EC6F9UCLZQQLAXJQO4M1VRRL5EN7WEG6NKM9937Z84RIET0DR6D97E8FC1OJ3C5BTT18IDGK7OQFANFWT8W3H86P8CMHFAWY1MMG6XGG34KMLGJPF9WXSWLW6OS4321B94U792YZA6FEJXKWHQK2ZW6MACJ09F3A50EAAXPQ2E5F05QEZNBIOBXT0L69511UFY3A5L34TVM2IUWYWERU8WSZ36YQ2O720NCAKT6B5FR1VB1ARCBBKTS5C2Q352JRHF7S1A74SF6LE56USIB3I1UCVQ13OYC0OJ622KNPKUI78R8URG4HSU1MQ0OT3K5BX6A9RILPGZH41M5PIHJZJY59TKUAIYJJLLZR1Z2EKO92XH7TL7RPYL3HP05T8XEFE5C4Q8TWZU94PVAOZQCBMQRXJZT6W5SBSS1PALJMF1MLPPHJKC93X8L16X8WGYG3OXEFHXXK79ME3OVA735YAP5POMKUGZOYT5BV2ER9V8B1L64L61VMQ43Y1QEX1EPIS7W8MW5Q80YI917DXUV6OP5LND9KQ7PD669DO9UZZTH91SQ9MG9E43J5FC1ANJYYQDWE3A76WVJ7LZ3EA1G9I4L4MU5XMC2RCUYE5KHXJ4PZ0SHMDS0UFBJ481QKTJ7P48WWUOP691RYXU1M3ESZVTDX4PD6IB11Y8NEHIJF5JDKSJXETD09B3HNMA7AUGH3YXK5YQFDHRRAI52L4U877FAKDA10J0FL7QAZ6VO3OLB7CNFHXXOVQ12T6XXKRW8S7JRBQXD6DNV1XNM3G4D93DGOGBIP8UNWBBIITLKAOFZJGF1G1K0W6IH796B22HGZ2QDA14UZG7XCV9ZTMUDBJKB4HVS5ACFY0XIPN2BZNYS7SG1WAS2113AMUWPTPY4SK5GGK0WZKB2RT1TOUDQSPHRFOQWFEXM30PL1WPGDQGC638CDDGON5SOZU3NG2V2MI58WEUMCPNXIUVMYX6IIP28JFP55K9FW40E0XLU9SZL6BABN18S0HOXKY8SZ2CRI5K84TFYEKZU9V0G8OTXHFJ91GS4C20B988799SLTFYRKGDNVA86P0N6KTDOL30W9MO38RX819QHSUKTBT395VXTD5VTUGEDCEJBGKEMWS53QZJBIKS9VFY4KOCHO666NATRV3LK3N5JV2S7SD1F7W51EEPU4XWV8YHZ3GQ8T8EOML065YQYD737VSKS9ITS8L27XMS60AWFQC2XCGTDHFWEPLJHO0JPOEV6MWVTE9BKK9EXZS26AHC3EDAVK56NAW0S7991ODAKF56IOGONHAIWQQ492LZCU2AWLKO22G1GLUP8EMR1O42WRMHTAA1IFX9T6FWPKZYZF7YWNW8VAF4ZES4DRQH3L41V3W3LOHH9QFXLNJ7YP4BDO34Z1IP82UDBQ34GTB0EEHH0HBDGL9HH7ZQYW27NL052U19E9QIYTJ2RDQIPDAFXCJD7ICRQMJDFWFEV80Y0ZUIBJ241BY5NPQ0RR6P8FPHM4VLU4KRR682ALONKCFPZ3CAP6GZFPAOM0CXBAEJHHK3OH8IXOLIOFA5T4NIGG2ESUT65FKELTSXCWCHQX5DDRLJVBAWP1QITRI81HINAA6C9QTAJ7OB8E4L6BDZUB9RRB9GC4F7NQW54DBA1BY1S8Y74TRHCXKSEN9P67LJNR6GA4YKK3V675CFK2OIPAD1FOAYIUBXN0AUM8XLRR9DKOF62VQ2R9OX2BUK1C3VDVL4T5B7WU4V4Q30952E5QDYV36C1J375FS5PR9S7FVYYP7BTSS3NSWHG1IL0LOPYXL8DK5T85005AJKLV7LZQX4J1P6KMYCR5VHZRKZBNL0BEWPVR3KKKXUUHP2D798YQS803Y74N40PP2O3A2F2Z5YNE7AHVRB0BQHKB92QE27PKIRJESGRZACGUHKDVKPD9V67GRTAD3ZEREMLL2J9GQXQIFG5UXZQK3N0VCWO967PGSR3S6EJI23ZYUDVEOOLM80UUJLDDXR1FROZKH4Y7GMFF6FU1HE0JXXWMRR7LC9T17GFXEPFJSEF37C1EJYLE11WZCY7GC9NIDYDQVYQQYE9WUK7ZQ26TY91S6W10A67MYGCOFWCFNA2XQ53N2U5C4T6INLQDKHCA5E34WTF467Z61LFE22RGZM07KDUNVQVZ5ICMVGP8HOTZ34K1TW1O05DJNYQPHU0A2E5K5M2XXAHMZREOUMHCOK10U7WK8O91JXVOFAKAG4Z1J7TEN42XZJDG6L8MKB7QOXVZPNX4P9GTTGXZ7MRBHZ63PCKXXLBCHZ7ETINC5AX8CRZ701YKRH2NVN6FWPI5AF9328735I69POY5IBFVGUV9DTV98CNDN617IWE4PE7LML4ERVFUKEJRGHSL6WHH8YMWVP8G2NWQ6AHT5F8XA0BRT42IKRDUXDSC0ZJHHMQERDWR5OBLXITBKG4OD40GD7GI428SHM06JRLAHWBVGOFM133T8K3YE9ORQ7FTG9Z1UYPG16SVS3545BI85P027FW8UKE29H6VS29ZHB6SP30OXQDGT2UH2LXU3IBJQUOTL1DK8ZNMUFXVN0QDD17W8Q4F7HQKNVVBDKAILOYDXVMPY9CVWA3JDNSLI3H4IX939W2CW66RIGFH78O1EAQZUV4GHKQIR06G5OB3Y3KIACEQIFQHK6QA7N2UWY8MXGWUVXQREU9YS1SJ9GI6MKK0CXTJTFVEDG198MYJRDZ0ATIQMF8TMYVXZWS36R2GW5AQ09AYRPGTDDXZ016V2N82V43QTA6FMC1O5RMFK1X3IHFLKK1VH03XYI7SGGI29XVPDDAE645ZMU3MJF2600Z1BJV599P3EAK3T6ZYBWF3XTB4SZ9K0SNL5FVDQ0UOI5ODCMPSB69HTCF9OQ5FR99UV2ZGPRCSCK1JTU98GCEWSIVBQ7WIK0VDGRHT1BSRJKZWESK82DE4OB4E2FVAQ744XPWFWAKND6ZWPB2XSGGYFOV9VCD0BRHCLSRBNBTZFPQTHCJHMZH1JMXTRFZIOJKO0B86IQHP0ZE0UH7MPHPN4X5ZJEZDCSN8XU8RTJV7DX05WKHKW3NKCPY5MP7XHQOV1Z7HN5NIDS0C3CIBE0KEHX9LLF04CWONIX150M0KTQVA8TKRYB9UHX8CDRF6J7X6U44AYOMQIT4SU0F1YF1HS9UR3BIDY42324LST8PDC3GXPAI3OPD8OGO75OUL72KP5ZP4DKRQG6WGOVH4T77FSXHSN89M44YKL12603U9RXLJA0N98HNSWXF638U3TANA6TB14YHAT5WDDHK1KM2CTCSZQTQYUE6TC78QXSAEDD3V0JK07521O0UVBB0V6D22II1506X1F0DOIJASN88V6EKHLS6KB4WU53XGMWWR2CDDOCBJSJCMA4TAIRG7SY3LD2QKJBDQ6N4ERS2ZEN1I2SHO6HMLCYILG7AEY9TPRAKBADFYB7UKWL2E2CZVJ71TVAMWE3ADK0HNLMV5V6EQND1IM1H8S4M6LFOPFHS9DJIFC28RON9F9YZ9VVDIZ82OC1TTI91GGO0UDVEQR499G3EY4D0ZF8R40XOEL4RRAK0XSWNADLCWO0HV6X7W53NHIVKMJQF229SBRQYVIS5R2QYXPBR6LD1HIC45QVCNXJR7EF5LKF32UMSY0W3XMX374LWBFBGQEK31XDC7V0VT4ITKRIF4JRQUGTJTG358HNYXUBYDEXWBOV7ORSWDC316QNM95V0BAUGYF9RPJXZT90TEUK0OD1OOLY7M3ZPVC8N2Z25GSZHAKMLLASBS167K27XY31NHGC7RTBGIQVO4ZJB8WOWKHSWVSL9503D61S08GVB8WGIGQCO5OWIJV5TAUANVOGYTQ0J6F010VZA1T49C0PZ36UO7EK1Q4E8D31R1O3A644TTQ64JUWUB43DN1SI1KO569SE13GUQSTYI7M92E72O9B2JW7NSYF8Q99IIR9RI4CCI8XLK57D3FXKZS95EHQ813XAAEUU51XM0BWCQGGKYC15GIR51RUS6N1O1KR8NVUVPLHY667XFT6S9Z1RUUGF6KMTBS2750HQ822SI79R1GTLR3TN7P1X6KKA7OF0FFT8OQ8M98RCNIJDRMTPZM3GZURYRJ2HRQFEGX14RYWR3PONHEQWTAHW8Q4X0MYFG5G35QYCNHBRKGPR8QUH8QUZOJQOLDJ8AP8G4ARDXL6XCYK6OGRUHH2A3POBKH2W9AJ04TLZ7FNOSL0767AF5W76UYYZYHCQPUFKNRU4I4NO0BPB65H9BGUUB5IK1MJYCUEITSRRXOHAU3HU2X3ZQ3DIXCEABGC5YIKN031AO5OFWCP3274C5NC48G1Q3NC1VPGF182RQ5ZH7RZX4L9GEHUJ9NBXKWRYEF05HJ4GB387XR7HUJGYQT6GD6RM6SRPAJNI5VEU1QD3JGMWY1I0ZVG97WP01F0HPV1DO8RK4MWGDFZ2FK5O9FMJGZXJM1AESB6TUGJO704Z1M0PAOWN27OZ0R8N74S7LGVMFBUMEXQRQHQ1Q4L4PKYVVPF9B46A94HG9O5F7VT18PP6WFYEXAJS5AIBUQVPQJS2R5UM7VDJAH559QZA4R7C5IX1SKUDABA1XFUZGNR34HKQLH64PKWZPJJLT60T9B490II42WZEYGAXBFKZCCRRR5WO5A9G82BO8WDQFO70YO2G739I7RFCJ73Y5TY1FU1F65NHDD2FOFO1BUM9LXAUAZWFVIXOGGHPGVYB5LDCXBDWFXTMBPG2DHZM4GHRKAZDGTCYS9CJMRXL6GLPI6PGLF0GFMX7KRZ3ZGIBPY3HDX1EXAOYT0G61MKXM02H4Q0TICA9GZOQBIOIH2OUXDWBK22CX0GW5KHVDRNLMH9UN6ZSGDBFVABVQYCM09SIJV5IY6S37BXWDJETJTENFDKYK8KIUMBZTXJ6C7EX4KKA6VQTLMQ3ZU74K5OJ3YWT0MTYRZPFH8NY07FOFVXEYOK34LTLLD18ECXKAFF97ISXE4T6UM131EW8X5TMUNIKMPW8LOHSFSCS8DXQAE64BWCKRW62PAZ43FHV9BQSNHLJ6TN7C3GW5VOPP9SU805F2M56MQGZ67AGPT770V7PKSUK7JL4P3UWVJZ843T86DLDFXS32NFB6M59598DCUHXNMXKPRRI2UPGPJNV8G3FFD7L6ZTECYX791GX7IYABA7FHQIIDTOR7TDRSKFCJZ5HE870DCTOMD4FFCHRIDEMY81RME3NKMADE8TCDNKEES8WDLLYNDYDLTU7W31WS3KPU4BB7QBA2EO67LILEUJHR9EUOEUVKZKSI0GJAU855XF8BA8IK95DJ1WR5AVZOEQT7LGPBTQL28ZJI0CTCTQJFGRZ1DJQHEDW8JP4U5916RAHY5OHHI6RF4D5M6BB1BWHHFCNJM9G8XRJN8UVB3BXPOHHMDCBYJP58Q2WVHJRN4JC9FM1P10WZQSY53BLBOLL8KW6YSZKL60ZVN38GP6OYNCKC2I2EPK7TWKDVCHNFY279JOJOOCAV9HQAYVZAFC268A0B6JX44SI5SCUZAYY71NDW1H2G3IM64W43KIH284U2M9UAO68XB6AQKY8544X3Q0B6LHPPNSHM13F09QF2C8PVXN90M5LUA71CVV0L5FYOQP7AB2X0PFBJXY9O2NKBVGAYNDJ36GLLJWQUA4ZI77O3W9EMO2N3FVJS00OX4U04126QPQL06DKAKCXWLORJJP5R182OC2A9OEJ0OZXTWRKHLBDK5TCA9QPFFC1LORHREIFSQQ5UEPDV2HOLTJFPWMLJWQ8L87V70SHM43B0J3TJOXB96TWV8P797JU51E6J1USPKSPL39XWJ0439NBT7PCZGBEKNON3X9YJGIDY3PN8M2UY21BLUUKQUL7Q19AEMPSJ3LER7O9F4B26YTMJQO6X3HKL6KPD8PAT5OTS24T0JLYF45V8N52J2FLPUG3X2RZ31TCAD87BZXROGS0CGX3QNUKOYGV20FJS093CQKNJABV7QCEGRUCHKSJNZKBGOYXOCHR8H3F9R3T20GNXPGM9Z25FQ3X9PWW4MKXTMIJE943GHMGGUYCI10N52PS5HC9BY2AOUWLKAT46U1AOJE25YMMR1F962NYHAFLLFUPEPDL9V0X5FKHWTSQKPCYLZ1WF4V7FCNRKYNFS6SMD54DKBNOEV8LVPI2YDYN533V2PO7ORN028GLIWH55M7Z5T41ACPW6UHNQLPWTQO6O9AOYIOCSLEMNTMY0IJI9E0UFIOGOWQLH0H4KEUWFJ3DIP6UJBV47U71HY3PW0P8C9PRKPWOCC79X60GSOINZ3U6RVIRT5X3J2FW5YJG2W1Z6OQOXWJ3QSDTE3VZOS57RL1IO39XQM88XRSKZ6Z3U6A6B8ZWD32QLTSS9R82QYKJMG5LL95VTPW2OM5SQT4GZZWTH3CY53PC0PWKPQKHZ6SYWZ3KMY2L7WQVK4JVA94DFJWLTUU0TB28QKSPZTRGDQ5G9BFDLCOESM7E6M4Z7ITKAYX2JXWANQU5Q3T4O4ONFP7SS5HUIY7HD41D2B07BXYDH43BRGMRYWSTIP853EMYMDC3E90TCIEFHRGIO55UR8R1D6T6RSZ4626WGLAXLFNF1SR7P9BJW5MJRW65SZSP8Z3JRK6KVSW4TF29U8GATCYRV4KUU8QG9DHY1XD9NYE0PF0PLCAYWB5IKYGYMD3KWS8BT3ITA8MDE8HQMD8ER5MN9NM6G1SUYT8MVWE2OKQK4HYOP79T4MXR3H4FW4D8D8BC3NSX58HUXN99XU20YOFF1EDPJCE5KVAKZVPFA046KXP9YM91ZKLK2F04VUC2TRARP2M0TD6NTDOT24PCS4RFQHA64X2TZSXOBZ3PSEG9PAQ5F08HFV7NJXYM1VHTD6HONDBIGZR5XDNX3TTIBVTKIP9FC9VYPXOOIKPJWHFCJYUTDBWHR4YQYT4KLH59DW0ED0RNBCNTL6L1ZK0NPPN8VNNIJ34SLH248JY6BWX2PTMAUU4V9V436RVUT868660VAGEMC5GC403R0E9PYVG9VHTGTMAO1FJQTSNZP70YXNXQ6DJJG10MWVVMABBDISDYH6W61D3RNJWIBV7GH34894FBFRX6MU0Y4TB080AOJFEXM2KITB060K00NOTE80HZZ4B5PO1HFGD5XYDV07V0UCZ1HE448R6NFAILF5NBIEICJHJDOYTPCQSXV304K3MYXIRNY1C7LHB9YQ4RSM55EQUS4E2QLYAR756CN4M3GZAXOFH30T1NEINLG8BSEWYAF6UPK6404L3DX80CHRKH42M5R5YMAFF0WUDRJRTXSIDLFKPHYYTH0X6AUY4BXEHZRBUBXRCZJ92C7FISSOFTTLLXIGRFOZI7NSG1E2I2AQYBHKPWU5E2CVV2LYH7Y4XHZQ7C4P31KD5IRHZH0G5O16QAAYCM4S6DAYZOSSV5ORXS6HOPMCS4HM9AI9BE74ICGA3ON1Q1D2MID6XUZFNR09M3E84XZOJ60ZAKQXQ6OJRMZZ2ASA0HSF41XZPI70BYO89U3W0RXJHMCX72LNGKRPGRLQM688PUQHK4UFY1Q34GJ943C5RGFNPBN84SXUT4DJJ89V9IOC2GDLZTYW72USJWBUDLRBD7ZA1YECQTNF36RM4547VXW5PYSSR0BQP80525Y4I6HWL8UF7P44SS1TDA1SAIF73OBA7CGJ93QEPSKHFOJRONHFCZ1FZLNJHK8FYMRK4I1TU9RG4YH55AOWXC12WB82BRLJ5FVKCU4YWUBHK9ZLJHEI54LGSY8FR9A5AMA73UVZCO4KBDSYXIBI7L4SJ88S8D20VLYTF3UDKY04DNTL0P1Z67MSX803IQW79NL6Q1ZOD0CFHNHNS7V9BHFTD1P9Z7053OKHTZEYLS2HOD943CPJEIQP1ALE20UET65LIHDRV5E7WLFDHOQPED5BRN27NBFVX77Q2J7TVSBIP4WKHS9ODM9O6V7GKXDD9VU7KEVJZZ32GQQFH69B805JTIEG47QL8L3JU843KPVNC43VVG1GJMDJKTYGOTA9AXL510PVS9YC2DSBF413TKUBD9CGTL7KF6S3ZI84FYTRS1K1894PUVOGAIP4FKWPXL1GYTUSBQJZJXVDAW6G828FM7EYPP39MG4XD7N71P9RFYJKKMC0R596JL87Z3XV7SD38S1XBT5OHG0QLM6ZF4ZOX0H6XIFUFWSN70SJA2X0XTLLUPZRUNCBFXKT1IL68X851WA40AZDZLU9M1SJUS654ZQDOXZQ9LEJHX91IQKOOWMOU8L3MACX3QSA5W3OPOLCQ3TOAQ4OLIG9FDMR8KGXUUCO7TPPG8N48ZYU1XPIQTYKK1MTQDSC7IAS1UFX2AJ8VPM57DUS1U76X3H1WHH86R2WV08E8EEZRDRAVM4WIFB5GK3H670993WISY26CQIV6WCOJUOA2V6SFZ1HP33I6U5C6IIYLE2TG9WSIZ9NHHHIACLTQA3S2NSMENTVV888LHHQ9CCV8KKVI6WT714G1TZD38TRQFLJ4QQ0KGEDPRNG7O7KRKJJQ785LJEB1DET6EP2TSLEZ51VDOXYFYXXHQKDGMI5XTR0PL0D0WPHDOOIA9A7GO8HR4LLZ5K7SK9XL2VVODY6S7VMKAUFK9UJUIPBX5287OQI4YGF5XXWXZRN1EN40NHNH36E0S1KKG97ATG7PRUJMYTCFFYZ6XYB3I0D71CBJR4TEK9E8EF3LS7SJJRL1I6CYD47NMDIBAGS8EEB3TCDMII4STVKMLTH9ZYQHFZ0SV6DN7XJOZI1JQSIOZJD0O8F0QVNLZS22LG7YED2VYFX7R7XOPRSYDWAVP05ND3XK20JUMNVX01YTK1GVO42JMO0QIDLU6TEUS77OEEGZLCL4PC5ODRO6MTFQ8MGK3W4ZH9IW1F3IKXUE4OMSONWEOFRB61TDH2A30WLBL7YFG5VYC1V73IZTOY7P0N91MCZKGR4O6GEDTHE1GAP1ITTDYR5TKCT9JNGMS7I6E8O5VZ1PZ1GGR4MDYUM55F6CMDNKWLLTWOF3T1LM06I8EHS4LZ9IAWSTVA7GR2MK7C5WHJH3YPV35S05O33RLMMLUKW3NOAHSRLSOPTNMWQRKVF18RU1AQOBRK0X2IXWN2BILCN9678O566RI5BTBEG7M781CQA61DWD0XQ780W9UB8IKFKEGFAN2PLRQ05RG5RCFSH7MPEXN0X27OS0D124UQ5FN5SMKGPLSNFJYX1OAI63YSACPMKS7L5I4WSOMTF8ILTE7CW81E7OPLU7BJ2YG36RG9QLZ8REWL3K6VR0REO365HFEGG1VFJG5TY7625FYNEUC68MVSOPL2HO7XZS1GOTZVBXB7I1HLF0BGXK3NW3JSEVLYDP20AWXZTKI4C1JLX6WJM2X5YSKTNVANWOC7W85RUXY6IIAD5ZAMD3JWF60B4L49IFTLC95D91O4YH12174I6VGKJ94DNGBH83C9YMYZTSBKBL5JLUR7HF61Y7YTIQSQV2VBR7J5CXUTTJFG0L29LK4JLQ9V7W8IJ60X23INRFIP78V0MW5474X2HGDMV3I4NLZDIIS5SH3MJASPTHZ9JWXYDCFIZPSNBYI7LT34DS9WVEACY9A26OMBM4OU77YMPAT0GO676EW8655NRKFIETIACNY885ENB5HGA5ISA3Y8OW"}}] 2 | -------------------------------------------------------------------------------- /tests/dumps/keys_with_expiry.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/keys_with_expiry.rdb -------------------------------------------------------------------------------- /tests/dumps/linkedlist.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/linkedlist.rdb -------------------------------------------------------------------------------- /tests/dumps/multidb-skipping.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/multidb-skipping.rdb -------------------------------------------------------------------------------- /tests/dumps/multiple_databases.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/multiple_databases.rdb -------------------------------------------------------------------------------- /tests/dumps/parser_filters.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/parser_filters.rdb -------------------------------------------------------------------------------- /tests/dumps/plain/easily_compressible_string_key.plain: -------------------------------------------------------------------------------- 1 | db=0 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -> Key that redis should compress easily 2 | -------------------------------------------------------------------------------- /tests/dumps/plain/empty_database.plain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/plain/empty_database.plain -------------------------------------------------------------------------------- /tests/dumps/plain/hash_as_ziplist.plain: -------------------------------------------------------------------------------- 1 | db=0 zipmap_compresses_easily . a -> aa 2 | db=0 zipmap_compresses_easily . aa -> aaaa 3 | db=0 zipmap_compresses_easily . aaaaa -> aaaaaaaaaaaaaa 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/hash_list_pack.plain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/plain/hash_list_pack.plain -------------------------------------------------------------------------------- /tests/dumps/plain/integer_keys.plain: -------------------------------------------------------------------------------- 1 | db=0 183358245 -> Positive 32 bit integer 2 | db=0 125 -> Positive 8 bit integer 3 | db=0 -29477 -> Negative 16 bit integer 4 | db=0 -123 -> Negative 8 bit integer 5 | db=0 43947 -> Positive 16 bit integer 6 | db=0 -183358245 -> Negative 32 bit integer 7 | -------------------------------------------------------------------------------- /tests/dumps/plain/intset_16.plain: -------------------------------------------------------------------------------- 1 | db=0 intset_16 { 32764 } 2 | db=0 intset_16 { 32765 } 3 | db=0 intset_16 { 32766 } 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/intset_32.plain: -------------------------------------------------------------------------------- 1 | db=0 intset_32 { 2147418108 } 2 | db=0 intset_32 { 2147418109 } 3 | db=0 intset_32 { 2147418110 } 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/intset_64.plain: -------------------------------------------------------------------------------- 1 | db=0 intset_64 { 9223090557583032316 } 2 | db=0 intset_64 { 9223090557583032317 } 3 | db=0 intset_64 { 9223090557583032318 } 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/keys_with_expiry.plain: -------------------------------------------------------------------------------- 1 | db=0 expires_ms_precision -> 2022-12-25 10:11:12.573 UTC 2 | -------------------------------------------------------------------------------- /tests/dumps/plain/multidb-skipping.plain: -------------------------------------------------------------------------------- 1 | db=0 foo -> bar 2 | db=1 foo -> bar 3 | checksum 5cba6708a711f9fa 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/multiple_databases.plain: -------------------------------------------------------------------------------- 1 | db=0 key_in_zeroth_database -> zero 2 | db=2 key_in_second_database -> second 3 | -------------------------------------------------------------------------------- /tests/dumps/plain/parser_filters.plain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/plain/parser_filters.plain -------------------------------------------------------------------------------- /tests/dumps/plain/quicklist_with_multiple_nodes.plain: -------------------------------------------------------------------------------- 1 | aux redis-ver -> 2.9.999 2 | aux redis-bits -> 64 3 | aux ctime -> 1420740379 4 | aux used-mem -> 508200 5 | db=0 quicklist[0] -> baaaaaaaaaaaaaaam 6 | db=0 quicklist[1] -> baz 7 | db=0 quicklist[2] -> 3 8 | db=0 quicklist[3] -> 2 9 | db=0 quicklist[4] -> 1 10 | db=0 quicklist[5] -> bar 11 | db=0 quicklist[6] -> foo 12 | checksum d5f6c0d5173895d7 13 | -------------------------------------------------------------------------------- /tests/dumps/plain/quicklist_with_one_node.plain: -------------------------------------------------------------------------------- 1 | aux redis-ver -> 2.9.999 2 | aux redis-bits -> 64 3 | aux ctime -> 1420740165 4 | aux used-mem -> 508056 5 | db=0 quicklist[0] -> baaaaaaaaaaaaaaam 6 | db=0 quicklist[1] -> baz 7 | db=0 quicklist[2] -> 3 8 | db=0 quicklist[3] -> 2 9 | db=0 quicklist[4] -> 1 10 | db=0 quicklist[5] -> bar 11 | db=0 quicklist[6] -> foo 12 | checksum 8930e0642b87218e 13 | -------------------------------------------------------------------------------- /tests/dumps/plain/rdb_version_5_with_checksum.plain: -------------------------------------------------------------------------------- 1 | db=0 abcd -> efgh 2 | db=0 foo -> bar 3 | db=0 bar -> baz 4 | db=0 abcdef -> abcdef 5 | db=0 longerstring -> thisisalongerstring.idontknowwhatitmeans 6 | db=0 abc -> def 7 | checksum 187280c630952e79 8 | -------------------------------------------------------------------------------- /tests/dumps/plain/regular_set.plain: -------------------------------------------------------------------------------- 1 | db=0 regular_set { beta } 2 | db=0 regular_set { delta } 3 | db=0 regular_set { alpha } 4 | db=0 regular_set { phi } 5 | db=0 regular_set { gamma } 6 | db=0 regular_set { kappa } 7 | -------------------------------------------------------------------------------- /tests/dumps/plain/sorted_set_as_ziplist.plain: -------------------------------------------------------------------------------- 1 | db=0 sorted_set_as_ziplist[0] -> {8b6ba6718a786daefa69438148361901, score=1} 2 | db=0 sorted_set_as_ziplist[1] -> {cb7a24bb7528f934b841b34c3a73e0c7, score=2.37} 3 | db=0 sorted_set_as_ziplist[2] -> {523af537946b79c4f8369ed39ba78605, score=3.423} 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/ziplist_that_compresses_easily.plain: -------------------------------------------------------------------------------- 1 | db=0 ziplist_compresses_easily[0] -> aaaaaa 2 | db=0 ziplist_compresses_easily[1] -> aaaaaaaaaaaa 3 | db=0 ziplist_compresses_easily[2] -> aaaaaaaaaaaaaaaaaa 4 | db=0 ziplist_compresses_easily[3] -> aaaaaaaaaaaaaaaaaaaaaaaa 5 | db=0 ziplist_compresses_easily[4] -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 6 | db=0 ziplist_compresses_easily[5] -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 7 | -------------------------------------------------------------------------------- /tests/dumps/plain/ziplist_that_doesnt_compress.plain: -------------------------------------------------------------------------------- 1 | db=0 ziplist_doesnt_compress[0] -> aj2410 2 | db=0 ziplist_doesnt_compress[1] -> cc953a17a8e096e76a44169ad3f9ac87c5f8248a403274416179aa9fbd852344 3 | -------------------------------------------------------------------------------- /tests/dumps/plain/ziplist_with_integers.plain: -------------------------------------------------------------------------------- 1 | db=0 ziplist_with_integers[0] -> 0 2 | db=0 ziplist_with_integers[1] -> 1 3 | db=0 ziplist_with_integers[2] -> 2 4 | db=0 ziplist_with_integers[3] -> 3 5 | db=0 ziplist_with_integers[4] -> 4 6 | db=0 ziplist_with_integers[5] -> 5 7 | db=0 ziplist_with_integers[6] -> 6 8 | db=0 ziplist_with_integers[7] -> 7 9 | db=0 ziplist_with_integers[8] -> 8 10 | db=0 ziplist_with_integers[9] -> 9 11 | db=0 ziplist_with_integers[10] -> 10 12 | db=0 ziplist_with_integers[11] -> 11 13 | db=0 ziplist_with_integers[12] -> 12 14 | db=0 ziplist_with_integers[13] -> -2 15 | db=0 ziplist_with_integers[14] -> 13 16 | db=0 ziplist_with_integers[15] -> 25 17 | db=0 ziplist_with_integers[16] -> -61 18 | db=0 ziplist_with_integers[17] -> 63 19 | db=0 ziplist_with_integers[18] -> 16380 20 | db=0 ziplist_with_integers[19] -> -16000 21 | db=0 ziplist_with_integers[20] -> 65535 22 | db=0 ziplist_with_integers[21] -> -65523 23 | db=0 ziplist_with_integers[22] -> 4194304 24 | db=0 ziplist_with_integers[23] -> 9223372036854775807 25 | checksum 267297f45913d51a 26 | -------------------------------------------------------------------------------- /tests/dumps/plain/zipmap_that_compresses_easily.plain: -------------------------------------------------------------------------------- 1 | db=0 zipmap_compresses_easily . a -> aa 2 | db=0 zipmap_compresses_easily . aa -> aaaa 3 | db=0 zipmap_compresses_easily . aaaaa -> aaaaaaaaaaaaaa 4 | -------------------------------------------------------------------------------- /tests/dumps/plain/zipmap_that_doesnt_compress.plain: -------------------------------------------------------------------------------- 1 | db=0 zimap_doesnt_compress . MKD1G6 -> 2 2 | db=0 zimap_doesnt_compress . YNNXK -> F7TI 3 | -------------------------------------------------------------------------------- /tests/dumps/plain/zipmap_with_big_values.plain: -------------------------------------------------------------------------------- 1 | db=0 zipmap_with_big_values . 253bytes -> NYKK5QA4TDYJFZH0FCVT39DWI89IH7HV9HV162MULYY9S6H67MGS6YZJ54Q2NISW9U69VC6ZK3OJV6J095P0P5YNSEHGCBJGYNZ8BPK3GEFBB8ZMGPT2Y33WNSETHINMSZ4VKWUE8CXE0Y9FO7L5ZZ02EO26TLXF5NUQ0KMA98973QY62ZO1M1WDDZNS25F37KGBQ8W4R5V1YJRR2XNSQKZ4VY7GW6X038UYQG30ZM0JY1NNMJ12BKQPF2IDQ 2 | db=0 zipmap_with_big_values . 254bytes -> IZ3PNCQQV5RG4XOAXDN7IPWJKEK0LWRARBE3393UYD89PSQFC40AG4RCNW2M4YAVJR0WD8AVO2F8KFDGUV0TGU8GF8M2HZLZ9RDX6V0XKIOXJJ3EMWQGFEY7E56RAOPTA60G6SQRZ59ZBUKA6OMEW3K0LH464C7XKAX3K8AXDUX63VGX99JDCW1W2KTXPQRN1R1PY5LXNXPW7AAIYUM2PUKN2YN2MXWS5HR8TPMKYJIFTLK2DNQNGTVAWMULON 3 | db=0 zipmap_with_big_values . 255bytes -> 6EUW8XSNBHMEPY991GZVZH4ITUQVKXQYL7UBYS614RDQSE7BDRUW00M6Y4W6WUQBDFVHH6V2EIAEQGLV72K4UY7XXKL6K6XH6IN4QVS15GU1AAH9UI40UXEA8IZ5CZRRK6SAV3R3X283O2OO9KG4K0DG0HZX1MLFDQHXGCC96M9YUVKXOEC5X35Q4EKET0SDFDSBF1QKGAVS9202EL7MP2KPOYAUKU1SZJW5OP30WAPSM9OG97EBHW2XOWGICZG 4 | db=0 zipmap_with_big_values . 300bytes -> IJXP54329MQ96A2M28QF6SFX3XGNWGAII3M32MSIMR0O478AMZKNXDUYD5JGMHJRB9A85RZ3DC3AIS62YSDW2BDJ97IBSH7FKOVFWKJYS7XBMIBX0Z1WNLQRY7D27PFPBBGBDFDCKL0FIOBYEADX6G5UK3B0XYMGS0379GRY6F0FY5Q9JUCJLGOGDNNP8XW3SJX2L872UJZZL8G871G9THKYQ2WKPFEBIHOOTIGDNWC15NL5324W8FYDP97JHKCSMLWXNMSTYIUE7F22ZGR4NZK3T0UTBZ2AFRCT5LMT3P6B 5 | db=0 zipmap_with_big_values . 20kbytes -> TO29G8HV1EAC44Z6NZBLD06R6P6Q4271M6AOS702PSZ2NKWF6G011IXPU4CX0UCJ1V9HUDZVSXCX2GM1N9HT8FR4W5XUPWQDO0Q5Q6PI9H1J42RT5V0X8CYK80B9YYSIQBYWA2878W8QVCIM7U2YVEFXBKOQIKA1AEUX86JDRG6AWWA7GJFNWUD6T4T5TRICEG2S9LSBUCLCAEQ7JFET8DUFN1RN1PY7NJDM2YN5JWNZHHD9IZ1HN7NUMTBQZ4GU5MPOXY84BMDRH27MLNCTT8H817WUUCRMYGJHMRDVQQ3NDPSQOWPSS9JJYOFCZY0ZA6R7K9FRF7SAX2FOAKCK1Z3RR0QN8FT508WAWYAYOOPYC9S7CK9JSR6QF046495KTO0EINUDNWJKS0GWJEG62VRWS52IWPVSUL76CKG9HPYK2UZZBSDJIJB0QH0HDGJVYWOXM87LH8FK9BD1G4YW32PT5QPIZDGBC11A8BHIUX855X29B0E2D8AZ4EIGWENSCCAS3PE2VD59Z4OGULDIMVK33HI3ZU2BOSLGJQHSLCWQUKVK0QKNV0MZCU792T5MECW8JVME719MU56GFF4C51KVR0UWEIS887VBFC5MFXPZATDORK52PJSN1I1SYH48S65YWD10S5IDXD29MCTBDKKD3ZRGOG1RLXZ7AE7U3UKR3E1B57APIC2UGFNY8WI5K757QS0F529JMCW3M97YX9WPQ1BS3BCO1GOL9T4OGA6B132JZWI0G137V08S134E94LOFX9WCW0BXJ3OX4KLTAVLIBG8X3IBJK3AONMB7SR8QST6RNQF068QKGLGIWD70I1JSNA6JS50ESMGE4NUXY0783VY5LZZPTUG6B8GTQF5I2OCJSVS7VOSH0XB6L8JRP434HBAXZPBB52I95CTLGFBIIPNERNQQS4VWRXNK4KF0MEG8VBE9XFMYL4TQL3WMZDD88WZUK3D0YRM13Y83D72ER01IGE05MSYVY3W8ZWOCH046O8TER8EQ79UUO32BXVKSASMMSGUFVLTV6EIWVVJ8H9K37Z7BLEGA5FZ3BID0NC18J86XNGOCWALKM9PUZF57HGPG2OCVYPVWSO2904CL1JJOVE5DN3WQBS47QI7YWPKP9FKPA8CJHH6T5LC93OYE02OO3SP30ASXKOOI1MYNWQQRTKLFA6IRGEZZOH8IWNXC5FIXCOYJ2THG43OLZEWV7MES4JEH30BPRF6KG0SM4B1KXTTOQIK9HA3DXTTFLAGB9IJFFD2AAXO802SB68DVWI7OLMUWWUQCJZKIOJBXRYSS2NZXNEHM9SI4UVNT49CFHAAVXPAK6GYXTNOYR6156CM3CA5CLO6M7GNTK1AXCS1A6ODR75O9JO8BN76KGCTHR4IIGM7GTO0V3304G4HMEGQHOS1KXODD32F2X5RDXZZSDNKKC3JB2DVERRR837PL5GXS48YCNMU8VZMRL9NXAXTA3JP7OINI02UXLMK0MJUTG1HTRU2VMUBSWHP3V4HXZT2BZG2WNMCZI54ZS2ZOKBLLU7Z8HUUQ6VBK70QMVJA49XA59T55TVVBFGJ7X3CTHF39Y95E7TEODDLK7CJ6XJHTESI7AUA19KT9AFOMDNE0IVQVZID3EYA4QOEDNJ5Q1OFOANHC1XP1MHLFZQ3NL9LOQ1MQIO5OWXTZZ6WK1GBD6O4TY35HWN0FMS28Z4ZPAYC4RXOGCWDIIL7PSOESVKG53HGG1298MB195A0UZE36ID49KVOGEH3YS6VWOFTZEE6AFULWLRZK0KKI0PLQI3UV1DXUMHRD5982ZUJAQU2DGBQIZPFQJ53R0H3KECVYKEYAK81VND80Z1F6SN3BTYXFB0XK696QQH5ELQ6CHKWZFBZYL0HFA4TUHJ1FPQBLGVGD5CE5S0U030ET2HLC50LKQ87CCCRZAFLDHWB6SNUTP19LH4NY85HA4I4LK8GJNZ5Y89YHP9G4KTJ4OEZ8QY0FAAN5DBSMV6YDHTQ60OV26RUYOIQAT899IYTAI4RCV8DPG2WUM9FE78YRIIPEUA84OND8U33ZN7SRADEWN313OZWDM7LSKYSUIHG1DXKLRMZZCP869ZXMEA2P3F8J30UJCTAZLK5D3AZLGOWCFUL4ZC3YV9WE3FFER8POIVKURXV3TYF2PGUTF7Y977DM3D3DNBMZTPJW3Z0NKBSJAX7MM92D3T4Q73ZYJJCZNQZ3P4Y7IIWLTEI6W8KGY1Q53BL8W127ZBPGZGYYWC0ZI5PBGVXCQSWXLNOY0GSRBVK4NAZD12IHL6AKMFDESLE50G5EJT6L9RKTZOH87J0RD283CUTCE6A9231UVWDFQQLBQ5PL0R7DO4P0D9LLB1D5Q56AOQ747M9TWK7IZ6VM273HW4OJF2FLME8EQ8LD211MR4S1AF3F1XIZ2A9R43WNB88H0FTGL7UV18EGJ9TG3P56JA6M9G3724SGJIZTJYYSOH18GE7SKK30M6JH3TPH5Q62YTKQ5HEOPTRLMELN302DP3OPTBB8ETBI9CFZ2IHH6TD4Q7HZSCDBH1KLF9X1B3PRI9WM27G2AOSP7TSTF0JC0GSPPF98PH8WSNAB17DXZPHK4RAYC4XOEN1MJ9XVN8FZKIXNF8NW8IVVXUKJ6GS5NQLX5BQ2DLUW4T99XZ5H9VQ9D5JHU766RMYM0LG9OA3FK4CUGGX7JJ0OE9BYBDCPHNMB7494K2HIXKZMBHV6KW6KESHF9QQWDAM6I716OMTNA42HTH8LJW3DPCLCVWU25GHP6MHBYY3W0LFT7YJWGP0VTKOC6BEF2CA4IYSRLQVO7EXWJULD829TA6J9VFJWL5D271HTFYIN7YP8XX33VQ187UIV9KFVWO5YR4DVU7NYRT9RM39MRYK36IBWT45YY7F0U2V4SQBJTZLK3RV35EAFC3KFBY8LZYVUN8UR2D4U044XOIYGEMN3Z3VR7413TJEZO12IQ53E5MCMHBHNHZF8MNMELEUD6BKEONC3J15FVYBFY9F1HRR4QXORSZUYM02SLYDG9VSF6YIX98LBFIHYBONUFY8U5X2H28LT26IA8R845T4K3BMF95AR8LS8C9I88C5MXCQY71UQEM6UP9VC6RZRKT08OFL99I5MM51TZWQC7X5NQ6P1EJR7KTR197TMET2NQAAZIL0SJLOKMVE53WE01PRQFNSIR4HP9S2E14GRQE50HIODG1B8BG9BG4ZK8RUC390H0YZVH736WHE2O4819TRHO198LHBXCNP71HJSYRQJHZO0X7QP4070FU1ZXN5CLYKMFC37MM5G95WRC8Q63I2MEOC7PL6V7L3FQMCYXG52840ABODC188W0HTVZYVVXY6PX5PN029CY3ZWRQF7VDZUZ2UAQOI3ATJGRYNGTSLJGNUDM8GKXR9IG6C36LV1OPW2AJINUKJG7HNK7MRIEMNWF16GP3KIG72R5826SFFGQNFU7OQ9H2OIF8DPU93AOA7KQB01D5VETLO42P5XJT1O60PN83OWXMEZG5876U1C8RK6203JS2Y9CZGSWBSB1D543SOYXKYCOBQU04GBQQ1WZZVLNRWWMX9NIATY4L2OV99UFPMLKS1DY4EZT3RVZQ1QYKT4A7N4X0713PQ6YQO9N8AE5YLAOOT7IUR3VDOE0HCCAFG0VZ42HP6OK1R8MCRO37AS2QESD1CIZPNCAPMPUVCEZK1197TQ4A4EV9LGRJAGAGD7VKI7WXO0FS8504UQ2D3LINE2RO72O1FMQ8LMLU150OTUUXAA6PIBSOMZR1S3YLCRWVQ36SBTI9T08LZ6U9OI32VFJ19H3Y0PQJ3488ELUXVJFCDQ8D4WYBZW8MW9IFP270TEVU34TXI98UL9V67EK24FCG18SHMD075ABXMFI1PVGHQVJXSNGSJJSGY5YLBDYURG2FKV9BWX77M300IKSZTMA7A9SGQHL6ZTTZVBMOH0Y3T1PXUWK4IRJCD8BJ5WNU3P4394QSSM31JPA9WONSN1AUMNCFVKGPE32UJGNPSSLECZZYU4X1RKUH68PJD85IAM8AD2NEGQU71B5A1IETA89D6R2MLZFER6GLGR1OUCUZXRKDUXSB5WIUZSJW08LJNGWI0BCJEYCCXL8DAF2QQEWL0M1TAFDM2NTB3FXITJYBRTK9ZQXE7SB5CN5EK2U523M1NLWXF15J283T34WK2V7A9Y2FIGPGNLCVP6MZ5VD2ID91MNEEEJHGZHQFE9OVM48IDQGDBT1LAP2ZI61T7PMFA9QLLV14OUJ2I500RR59O1AIBTCGJM91XVE1RS6NZ7ZXT1OUHEGXWIXV8QC5XW51ZZKXN8M0UQ38NV5QQRA5HGM0W28GN971PMKQIXII69YYHY6E02PVBO2GFUIEAQEUUV9BQXCOY01OG9XME9PDPQU86CJY3E38OG3B149ZGESAYXASGZF69X0E7MR24OB97HQ31INT39TS5PR3ACQS7ZA3K3N8PZARGQYSACFGMN6MZKLVWV6K21ARY8OT5C8Y8FHRTGC3U1HJVRFB37HPUC2488E7HICYLZVYZWH3D7P1LTTMD1GDD1OL3IWL21PQ2TH5YTXN32N57564XQV13KRSWOUMPEY9A877RPIH14CY4KT4U3WRBG9LJ389Y00LSEOVUF3VCONXT1R0X4Y6TP64LX2VEWHIHWPPHUC04PY1VGV1ID5SYZPQ3XP6XLUP9U9MXBFBPGNZV6X43G6FAN92ZVP0NS81LO1ENJ2PGWKAJZY904WV0F6GPP0HU0YDHFEMESVCNJWYCF2REA9KK3Q95ZU34GUFBFFI2YBVPU1VQ2Q8YQFHQWQKCJSR2UACOQ3CMM3YBFX1YTBEJNDHGYV8XO3M8HEJQWI5UT5CYO7PFVUMQQ4H1P9ITTHERX7NRD4Y4A23OQACYAVVKVR5EUS3PSGBX370ATD2NLG8V99S5TYXXUBJXMC6QF80BAHV2Z2VRM2XQOTEZFTXXN9Q1ZHGKXK7X3OYT36DDH08C7AYFWTF0XQ0NVZUB7A09U64MQJ6MDLLZW0GTN8ROVNSTIE44UQYDSF5Z5WTCY3AC3DGV5RLC3HBCDHOIXMWIFLWOUGUGOC7YH87OUKLVFLABZH2HLLECDZIGN6CA9CWJ81JBIX6W6MWXEEUX4ROM0830B4QTOI9LD6S31QD8PP75VTMKTCA71XSXSK0K8W1VEWOP265N2MQFRMNCFI3HUSOH8Z0GEQ2UHDVPA7L2WIOVTDYD3QWNWI2GJFFKB9WT81IBO99VLNV01AL40AEEQDKORX77XPCCSOXQ6GK8DHFOC19OPO0G1SV9L3A8IMDWZX68SG55P3PUVZIGSWZULY62X1NVZCKGFA56BBFDE04U26SW4D2P8ZE9O0PNQBP5PQEI90VFUZJC2WAW8DX3GE61V2Z862D8SN99PZBV838GJF3C3VCVWTYVDK0Q5XTRUDNCHP006VZSH2AN8G4GBFT4R58UK4DAAIGTU7DZMVQO1VI27EU16BOW4QXDZNTAL93B6OY810LUOSYOCOIQFO6YW8J11G4OJAKHAGHY68O9MTKCI7242SJSWK7WWI82OWC1BNL537M3M364TR9QE53IKG64PV4VOYWJTQ360CAGAAN2316Q1L8EIUIUYQEE82TY4GI2K1ULBE6YSUGCYG67NWBTNXSH933YWY1GLLR2UCGF4M26SAP8NOD1X3THI9T3QIJCRA2OHMSTL9KBAU9G4397T0CE29W976METU1LGIUXF3AQWQIW05NJSD2HAY2RBEY96M1QL6SOOK5MPUE743YGED4G367ZV3DV6H7SAUSORS5N3UCND7HUNHFYBN5UIE7W99MB72OVXUXHSNEM7T3P4NA49ZMSJM1NP9UW57VSX9DS1DH2YK4HZ41G6OAM306R4MH3F2FCW02MP9IM51QA8FIBYW64V41QTPNNICGZA0NZGJ93ORXKC1VK392JUY2K9V7WPRJH0RMD03NZ84KMGEF9GJK0685XDEBGMC0YAKXUW4CSJTJG9XLQ0YUCZTPJ4EH49B5T5DNXZXBMYC5E5DC9SGJOP0SM9HASZMYMH8J8J0CL5I7HKIAWC1UZZA0H59VKN0PCWOWE9JDOW1MR1LK6F57TJ4M0MFJKFRZJE9W8RRXADHCFSKHDACXLJCSKHQUH2X9KZEPIFWNK699IUMUS18BLNWUPMJB3231V5I4JVSF5WYOSHDFPSVRDEEI9DTIIKXXEM7F0XHR6P8S7635AQT5B04QQEYSJYX6FVT63MKXAOPIXWPQ9Q2L6IY7JNBFKMIGA6QTBNVX2038P7FW6I57GEQP27P115CZ9NYFQH3RGE4RV6GB27PNAO63Q5FKYYSW8V9IIXLNDWAI8I3OIVKT6FBNYDQ70URYP1OJUZPG0HS07YKCOLOSYHU5C8F83YV2AGGNUZW0FAIXZ65W96V74EINI9M5F3GVR594US0WDCZUWH3OLQQWVF8ISV33KQU0JEW2RWCPYZKAOZYVNDN2A27EU8AME4PKZ4KF4ANOW82KX7H1JL038C2R1NUP80UHN9ECK8W8BEGCRVHH223W7GN1XD1EICRXHS7A9JXFUE3D6LWHIJ0ZDYLMIIBH4BFXO5RCDJE1R2E9V4JPYBC3QM53G8ULUR31WHZUUQAB862H4WUNIIH2II8IH7E8QULGGXK7P31L5CLL3K8GM88ELJ2KBQXZ8BVWVXVL08A1GRHTWXFBBVGCIBXK9Y4NLKCC2CCGLIQCKAZB1AYJGBXDH8A49XG5QMJVCKOUCKG5QDH84YMCICNXROIHK8Y9LZ4YEKQZLVW1O1WR2XDM8T04QNVP24IRAGME9743GY5WMDXHT66X3GL9BJTDIM7428B4IJ93TUZB2BSQB1SYXD7IZEWHG1U5L966KEEPY08QK4UONKT3ZUPCULWV9EY0EAMI6SJINWPUJ4DKF9U4HVEIU1UI924RDPYWKIML5CAA6FFZD4SHB1SXA9BQPVBN214NLDLRZ2A5GB5TTRNDSX917XGV5KOEQBSCESD7D0SFI22N3Z082PVQT73KLXL2IFGA6HI8PM08CEJCGIGYYSP8NM57MPT76RBQKGXHG2U4I2UR10MGP4K8E4C6IQAL1B5RWK45Q5NCMZY3HZ35YG0TCAB8WQCHK9WVTZBVMG7794FIEVYO0U1B359A89I33LD0LINGDKPNT6I4ZUKKF57WPEI13AW1MN0OLYM4UELKFUI3TWPH9I7J7E6SXO8ZLQP6DZN6M6FOQD99U4N8ILZA0Y9SZMMZOVA6PT5FV9RGK09IZ4XOICH1VQUU6N7XEHK4YKMXRX4YOFR5S5SY4PRAXME1VN07WQVAI3Z7XU0QD52J94EJ81GQEWX00UP3O95HUID0NZV1WAQNXR6TMDG04MU9Z93F0PI1XYNU6QIDN0PA6E5N9SCTPBN6EVMIP8HE1QH3B8CL8NF1TURI7DTGBX2MJMY7VZUXI62Z7SPXB43AQE7F22JAS06YF1QTA9G9Z8IXWVF3G87P847BLLTZ02ERH2XRP750OA1AD55ERSKQLJENHISJMCK2IDUEA79LAYNJICALTDYSSW4AKBX2OV1ZGU3EU4WAIJZZDOXHQAG1PTP7FJKZN1O84EAQVIKKF5H2108VAT1K7MB7F3TZLVSUHFHELQJ6VZUS015FJ8P9RFBNBQ15GPC64HAIBFXDHWFZ98XWDTTY0U6WBKZ4F8894DAFOI0I1VZQL8BQ749CWLLK89CBGIIPPXNMJTGX8IF2XK7D1NNSWIJYLEXBB3QK29IPZX9XPBJ6ZAUYS12RWV7EK856GG3DLO59B4I11T5XOXTRMFPXLF6E5RJIS1L28PAQ7VMAL66M14W3BLCA5Q8NB0W8A2T9UVJRVH35GVJ91TC717NVDFY1TAISWJQTESGUO2TKNZ3P3W1X8CD9CY3D8DV9DIKD1HLK0M7P46IZ95ULPAFALTUSGXVXDUZCQKWQ6AZ96D2HK4YQWX25V0HL20BQ5YHYHT04COICIHHFPJ6UTCK1NPR7VISTUMU3N5MNU2TE685PXSWHRC83YQ8ENN1RICJH2556GXK326OYZ7507Z0UJGOYKI4OE48O2SHXX3CWNLW62YZ33Y2Z4QB3RMXBX5RRN6LQFRUY7ZVIP4H8YGYEMAOB9JYVR3Q0220MLPNNTYSH2A3JLKJCFHIWFXR96R24VQY5JJRE8GOWMHAYHF7VOQU6FIWS2O3X28D29X1M0I1QJMUOXA8RBB5N3LFYMZ799RQX1EW93TPWWSZ7M0F4EOKJFIHQVSRR41H1CZ84VCZK14BAG8WZKNHBEHM1FNLFQ2VUD1NTP02R2HV2QJ4N9UT6AIAH11T5CH5PR1GUPR1C0UIMI9S2EZU7UMA1FVIG28FOG4PSPRHN9DDC803URCWCDLHNL30TAHS2IETNGBL9B2LM4EDQSR1E88VHD0L82WDFXC4GKZ32SPQVZ1XMPG08VQHZZWSQAQIW90ZAZESNKI67I6E1XFFMHIDR47KXUINHF2AOFWUVL10NTDG0PKWA5SAU5QE260RZ3OZSIXYNZYCX4FYIYUXVIHZAHGAM2QQB1B42ZLYT6MOHZVQD7T95MBR62RYIC28WNDPPP68RYQXGWHJCB3JTQ40B9ML1CLMM05F22LHBNL3Y0XFHKBC8QY72ZLNF54N4QDS0TSR770UGF3KA05NOWMWMADC6UXF0GPB6BJTGA0GZE9OEI1HJQ6L9HTW8C5WW900PDIC1MUJWPWSY4E41TRGGX8FSI7T2C9GWV29MDC2QSK99JE7U2HXFATQI7UT9CMFJSYW0E009N1IU700I3NHKJAZWDDL5T7MCLC9ZM6YB6QWZSGEGDC5SW8CQZVM6GWVJFPZX94GGY4SBIX6CLSKZKJTFOPFN9QPDV816W1L1HJQV280ER53EDEN46XEXF85UOB1HT6TLY3SKTYDXW8ACMDJTUR5HWU2I2W8KDW8G4KGUJWY7JO9NQMZPUS47MUZAEGE97Q9RD982GGJDPGBJXXG6UCMO3NNJRVN45L286F02RVI5BUX9L7GESVHB4HA71R6XN22PG2J4EIGJ476T5KX776J7UQ0LDN6ILSWXRYQ25VCHAWE50U2R0L6U8E3C8ULDNXFU747PKHFT21ND2277JMX3KIL578A7AKQX25JUA2Q9KBLI30XM41PCJXQ9QGA82C0XLN9EVRAMAQAJF8N6WJP1YATKRQA7KSVWAH9U0O0PCX58IQTHKQITWOJSM3LR4BI8E81MP8PMJBX521OJYCASPLBVNFLN9WSHLAMEE3GIO72A54EWPRDN6Y3FTSFKC6ILBSCZDNEZVK9VP8VSO4YMH6NR6Q6AX3G7TCGD50UCRY30WTU7PFQIYNZRFEVK7ABS85P6D5GW15NOP0R9ELG8COMPBGGYD4KZV601IV7FXTP8Q29OO4JPZX23NH43D0836DEOX8IU0GQUMEYSFDMIOAK1TZ3TZE9ZOOECZ00GNID3ZK74X9AI75W8WDVYS931CGXSW0JYPJUX528OXH8W0I3LGCK68B0LEA05QX95TWBVXY68S26I5QNTERZ83XUZ1Y9N2KZIJ1LV88Y01X64R803ZFKGGMABVA07CN1ZE1LAXFEPNRDYS8XU98TENE5MBQZMQ826G79UELNRXA77GYPG7NZA17H5BIKW6UX4OZ394WTJD85OGFK9IEBI2T71RTGE6GZD2FUXX0J8P1C8JX66MS3Z1MKBOY92OOZM815UOTTPYE96SLZUQE15RG1PMPY7NJDZU5HH0Y13PNY6BF9ND2SR6LF8BC8IG8GRM0AS5HFSU5XUVD73PAHMABXEQ9YPV1GE8BG1S5BB8YMAUJXV1DTKJBTNIZ3QJ9NPF889FLDQ1COEFNGWSKF3CV1OLV1QUMSEGQR7HBDNTBG1OGZU06AVM0YPFM4W0LM00VMRY469S99Y1NLX5R5UXNWZEV264TWDHC2POKXUGX9MCV6ROCCT325SZ0318WAKOR30QWE347PSWUN4MLV3WSPRLXVHE67IQT2F2BC5I009I4F2EXA9U9LD85AY8D5XVWNFG1JUOSOBB5K7XLEH0234RELJDQA1EHOPZQ5DBDXQGQ9OCJX1HH2DZS1HQGNMKKX1JNT522SA48O1TYSOCOBPTDMGCI10WUYQY1Q3OP2BYDOPILQLPQE08K5E3AUMYID8NOH0IFOQMNO3HE93PB69P3LER8CIGCERV8SC161HK0WGSL1KQJ5EC6F9UCLZQQLAXJQO4M1VRRL5EN7WEG6NKM9937Z84RIET0DR6D97E8FC1OJ3C5BTT18IDGK7OQFANFWT8W3H86P8CMHFAWY1MMG6XGG34KMLGJPF9WXSWLW6OS4321B94U792YZA6FEJXKWHQK2ZW6MACJ09F3A50EAAXPQ2E5F05QEZNBIOBXT0L69511UFY3A5L34TVM2IUWYWERU8WSZ36YQ2O720NCAKT6B5FR1VB1ARCBBKTS5C2Q352JRHF7S1A74SF6LE56USIB3I1UCVQ13OYC0OJ622KNPKUI78R8URG4HSU1MQ0OT3K5BX6A9RILPGZH41M5PIHJZJY59TKUAIYJJLLZR1Z2EKO92XH7TL7RPYL3HP05T8XEFE5C4Q8TWZU94PVAOZQCBMQRXJZT6W5SBSS1PALJMF1MLPPHJKC93X8L16X8WGYG3OXEFHXXK79ME3OVA735YAP5POMKUGZOYT5BV2ER9V8B1L64L61VMQ43Y1QEX1EPIS7W8MW5Q80YI917DXUV6OP5LND9KQ7PD669DO9UZZTH91SQ9MG9E43J5FC1ANJYYQDWE3A76WVJ7LZ3EA1G9I4L4MU5XMC2RCUYE5KHXJ4PZ0SHMDS0UFBJ481QKTJ7P48WWUOP691RYXU1M3ESZVTDX4PD6IB11Y8NEHIJF5JDKSJXETD09B3HNMA7AUGH3YXK5YQFDHRRAI52L4U877FAKDA10J0FL7QAZ6VO3OLB7CNFHXXOVQ12T6XXKRW8S7JRBQXD6DNV1XNM3G4D93DGOGBIP8UNWBBIITLKAOFZJGF1G1K0W6IH796B22HGZ2QDA14UZG7XCV9ZTMUDBJKB4HVS5ACFY0XIPN2BZNYS7SG1WAS2113AMUWPTPY4SK5GGK0WZKB2RT1TOUDQSPHRFOQWFEXM30PL1WPGDQGC638CDDGON5SOZU3NG2V2MI58WEUMCPNXIUVMYX6IIP28JFP55K9FW40E0XLU9SZL6BABN18S0HOXKY8SZ2CRI5K84TFYEKZU9V0G8OTXHFJ91GS4C20B988799SLTFYRKGDNVA86P0N6KTDOL30W9MO38RX819QHSUKTBT395VXTD5VTUGEDCEJBGKEMWS53QZJBIKS9VFY4KOCHO666NATRV3LK3N5JV2S7SD1F7W51EEPU4XWV8YHZ3GQ8T8EOML065YQYD737VSKS9ITS8L27XMS60AWFQC2XCGTDHFWEPLJHO0JPOEV6MWVTE9BKK9EXZS26AHC3EDAVK56NAW0S7991ODAKF56IOGONHAIWQQ492LZCU2AWLKO22G1GLUP8EMR1O42WRMHTAA1IFX9T6FWPKZYZF7YWNW8VAF4ZES4DRQH3L41V3W3LOHH9QFXLNJ7YP4BDO34Z1IP82UDBQ34GTB0EEHH0HBDGL9HH7ZQYW27NL052U19E9QIYTJ2RDQIPDAFXCJD7ICRQMJDFWFEV80Y0ZUIBJ241BY5NPQ0RR6P8FPHM4VLU4KRR682ALONKCFPZ3CAP6GZFPAOM0CXBAEJHHK3OH8IXOLIOFA5T4NIGG2ESUT65FKELTSXCWCHQX5DDRLJVBAWP1QITRI81HINAA6C9QTAJ7OB8E4L6BDZUB9RRB9GC4F7NQW54DBA1BY1S8Y74TRHCXKSEN9P67LJNR6GA4YKK3V675CFK2OIPAD1FOAYIUBXN0AUM8XLRR9DKOF62VQ2R9OX2BUK1C3VDVL4T5B7WU4V4Q30952E5QDYV36C1J375FS5PR9S7FVYYP7BTSS3NSWHG1IL0LOPYXL8DK5T85005AJKLV7LZQX4J1P6KMYCR5VHZRKZBNL0BEWPVR3KKKXUUHP2D798YQS803Y74N40PP2O3A2F2Z5YNE7AHVRB0BQHKB92QE27PKIRJESGRZACGUHKDVKPD9V67GRTAD3ZEREMLL2J9GQXQIFG5UXZQK3N0VCWO967PGSR3S6EJI23ZYUDVEOOLM80UUJLDDXR1FROZKH4Y7GMFF6FU1HE0JXXWMRR7LC9T17GFXEPFJSEF37C1EJYLE11WZCY7GC9NIDYDQVYQQYE9WUK7ZQ26TY91S6W10A67MYGCOFWCFNA2XQ53N2U5C4T6INLQDKHCA5E34WTF467Z61LFE22RGZM07KDUNVQVZ5ICMVGP8HOTZ34K1TW1O05DJNYQPHU0A2E5K5M2XXAHMZREOUMHCOK10U7WK8O91JXVOFAKAG4Z1J7TEN42XZJDG6L8MKB7QOXVZPNX4P9GTTGXZ7MRBHZ63PCKXXLBCHZ7ETINC5AX8CRZ701YKRH2NVN6FWPI5AF9328735I69POY5IBFVGUV9DTV98CNDN617IWE4PE7LML4ERVFUKEJRGHSL6WHH8YMWVP8G2NWQ6AHT5F8XA0BRT42IKRDUXDSC0ZJHHMQERDWR5OBLXITBKG4OD40GD7GI428SHM06JRLAHWBVGOFM133T8K3YE9ORQ7FTG9Z1UYPG16SVS3545BI85P027FW8UKE29H6VS29ZHB6SP30OXQDGT2UH2LXU3IBJQUOTL1DK8ZNMUFXVN0QDD17W8Q4F7HQKNVVBDKAILOYDXVMPY9CVWA3JDNSLI3H4IX939W2CW66RIGFH78O1EAQZUV4GHKQIR06G5OB3Y3KIACEQIFQHK6QA7N2UWY8MXGWUVXQREU9YS1SJ9GI6MKK0CXTJTFVEDG198MYJRDZ0ATIQMF8TMYVXZWS36R2GW5AQ09AYRPGTDDXZ016V2N82V43QTA6FMC1O5RMFK1X3IHFLKK1VH03XYI7SGGI29XVPDDAE645ZMU3MJF2600Z1BJV599P3EAK3T6ZYBWF3XTB4SZ9K0SNL5FVDQ0UOI5ODCMPSB69HTCF9OQ5FR99UV2ZGPRCSCK1JTU98GCEWSIVBQ7WIK0VDGRHT1BSRJKZWESK82DE4OB4E2FVAQ744XPWFWAKND6ZWPB2XSGGYFOV9VCD0BRHCLSRBNBTZFPQTHCJHMZH1JMXTRFZIOJKO0B86IQHP0ZE0UH7MPHPN4X5ZJEZDCSN8XU8RTJV7DX05WKHKW3NKCPY5MP7XHQOV1Z7HN5NIDS0C3CIBE0KEHX9LLF04CWONIX150M0KTQVA8TKRYB9UHX8CDRF6J7X6U44AYOMQIT4SU0F1YF1HS9UR3BIDY42324LST8PDC3GXPAI3OPD8OGO75OUL72KP5ZP4DKRQG6WGOVH4T77FSXHSN89M44YKL12603U9RXLJA0N98HNSWXF638U3TANA6TB14YHAT5WDDHK1KM2CTCSZQTQYUE6TC78QXSAEDD3V0JK07521O0UVBB0V6D22II1506X1F0DOIJASN88V6EKHLS6KB4WU53XGMWWR2CDDOCBJSJCMA4TAIRG7SY3LD2QKJBDQ6N4ERS2ZEN1I2SHO6HMLCYILG7AEY9TPRAKBADFYB7UKWL2E2CZVJ71TVAMWE3ADK0HNLMV5V6EQND1IM1H8S4M6LFOPFHS9DJIFC28RON9F9YZ9VVDIZ82OC1TTI91GGO0UDVEQR499G3EY4D0ZF8R40XOEL4RRAK0XSWNADLCWO0HV6X7W53NHIVKMJQF229SBRQYVIS5R2QYXPBR6LD1HIC45QVCNXJR7EF5LKF32UMSY0W3XMX374LWBFBGQEK31XDC7V0VT4ITKRIF4JRQUGTJTG358HNYXUBYDEXWBOV7ORSWDC316QNM95V0BAUGYF9RPJXZT90TEUK0OD1OOLY7M3ZPVC8N2Z25GSZHAKMLLASBS167K27XY31NHGC7RTBGIQVO4ZJB8WOWKHSWVSL9503D61S08GVB8WGIGQCO5OWIJV5TAUANVOGYTQ0J6F010VZA1T49C0PZ36UO7EK1Q4E8D31R1O3A644TTQ64JUWUB43DN1SI1KO569SE13GUQSTYI7M92E72O9B2JW7NSYF8Q99IIR9RI4CCI8XLK57D3FXKZS95EHQ813XAAEUU51XM0BWCQGGKYC15GIR51RUS6N1O1KR8NVUVPLHY667XFT6S9Z1RUUGF6KMTBS2750HQ822SI79R1GTLR3TN7P1X6KKA7OF0FFT8OQ8M98RCNIJDRMTPZM3GZURYRJ2HRQFEGX14RYWR3PONHEQWTAHW8Q4X0MYFG5G35QYCNHBRKGPR8QUH8QUZOJQOLDJ8AP8G4ARDXL6XCYK6OGRUHH2A3POBKH2W9AJ04TLZ7FNOSL0767AF5W76UYYZYHCQPUFKNRU4I4NO0BPB65H9BGUUB5IK1MJYCUEITSRRXOHAU3HU2X3ZQ3DIXCEABGC5YIKN031AO5OFWCP3274C5NC48G1Q3NC1VPGF182RQ5ZH7RZX4L9GEHUJ9NBXKWRYEF05HJ4GB387XR7HUJGYQT6GD6RM6SRPAJNI5VEU1QD3JGMWY1I0ZVG97WP01F0HPV1DO8RK4MWGDFZ2FK5O9FMJGZXJM1AESB6TUGJO704Z1M0PAOWN27OZ0R8N74S7LGVMFBUMEXQRQHQ1Q4L4PKYVVPF9B46A94HG9O5F7VT18PP6WFYEXAJS5AIBUQVPQJS2R5UM7VDJAH559QZA4R7C5IX1SKUDABA1XFUZGNR34HKQLH64PKWZPJJLT60T9B490II42WZEYGAXBFKZCCRRR5WO5A9G82BO8WDQFO70YO2G739I7RFCJ73Y5TY1FU1F65NHDD2FOFO1BUM9LXAUAZWFVIXOGGHPGVYB5LDCXBDWFXTMBPG2DHZM4GHRKAZDGTCYS9CJMRXL6GLPI6PGLF0GFMX7KRZ3ZGIBPY3HDX1EXAOYT0G61MKXM02H4Q0TICA9GZOQBIOIH2OUXDWBK22CX0GW5KHVDRNLMH9UN6ZSGDBFVABVQYCM09SIJV5IY6S37BXWDJETJTENFDKYK8KIUMBZTXJ6C7EX4KKA6VQTLMQ3ZU74K5OJ3YWT0MTYRZPFH8NY07FOFVXEYOK34LTLLD18ECXKAFF97ISXE4T6UM131EW8X5TMUNIKMPW8LOHSFSCS8DXQAE64BWCKRW62PAZ43FHV9BQSNHLJ6TN7C3GW5VOPP9SU805F2M56MQGZ67AGPT770V7PKSUK7JL4P3UWVJZ843T86DLDFXS32NFB6M59598DCUHXNMXKPRRI2UPGPJNV8G3FFD7L6ZTECYX791GX7IYABA7FHQIIDTOR7TDRSKFCJZ5HE870DCTOMD4FFCHRIDEMY81RME3NKMADE8TCDNKEES8WDLLYNDYDLTU7W31WS3KPU4BB7QBA2EO67LILEUJHR9EUOEUVKZKSI0GJAU855XF8BA8IK95DJ1WR5AVZOEQT7LGPBTQL28ZJI0CTCTQJFGRZ1DJQHEDW8JP4U5916RAHY5OHHI6RF4D5M6BB1BWHHFCNJM9G8XRJN8UVB3BXPOHHMDCBYJP58Q2WVHJRN4JC9FM1P10WZQSY53BLBOLL8KW6YSZKL60ZVN38GP6OYNCKC2I2EPK7TWKDVCHNFY279JOJOOCAV9HQAYVZAFC268A0B6JX44SI5SCUZAYY71NDW1H2G3IM64W43KIH284U2M9UAO68XB6AQKY8544X3Q0B6LHPPNSHM13F09QF2C8PVXN90M5LUA71CVV0L5FYOQP7AB2X0PFBJXY9O2NKBVGAYNDJ36GLLJWQUA4ZI77O3W9EMO2N3FVJS00OX4U04126QPQL06DKAKCXWLORJJP5R182OC2A9OEJ0OZXTWRKHLBDK5TCA9QPFFC1LORHREIFSQQ5UEPDV2HOLTJFPWMLJWQ8L87V70SHM43B0J3TJOXB96TWV8P797JU51E6J1USPKSPL39XWJ0439NBT7PCZGBEKNON3X9YJGIDY3PN8M2UY21BLUUKQUL7Q19AEMPSJ3LER7O9F4B26YTMJQO6X3HKL6KPD8PAT5OTS24T0JLYF45V8N52J2FLPUG3X2RZ31TCAD87BZXROGS0CGX3QNUKOYGV20FJS093CQKNJABV7QCEGRUCHKSJNZKBGOYXOCHR8H3F9R3T20GNXPGM9Z25FQ3X9PWW4MKXTMIJE943GHMGGUYCI10N52PS5HC9BY2AOUWLKAT46U1AOJE25YMMR1F962NYHAFLLFUPEPDL9V0X5FKHWTSQKPCYLZ1WF4V7FCNRKYNFS6SMD54DKBNOEV8LVPI2YDYN533V2PO7ORN028GLIWH55M7Z5T41ACPW6UHNQLPWTQO6O9AOYIOCSLEMNTMY0IJI9E0UFIOGOWQLH0H4KEUWFJ3DIP6UJBV47U71HY3PW0P8C9PRKPWOCC79X60GSOINZ3U6RVIRT5X3J2FW5YJG2W1Z6OQOXWJ3QSDTE3VZOS57RL1IO39XQM88XRSKZ6Z3U6A6B8ZWD32QLTSS9R82QYKJMG5LL95VTPW2OM5SQT4GZZWTH3CY53PC0PWKPQKHZ6SYWZ3KMY2L7WQVK4JVA94DFJWLTUU0TB28QKSPZTRGDQ5G9BFDLCOESM7E6M4Z7ITKAYX2JXWANQU5Q3T4O4ONFP7SS5HUIY7HD41D2B07BXYDH43BRGMRYWSTIP853EMYMDC3E90TCIEFHRGIO55UR8R1D6T6RSZ4626WGLAXLFNF1SR7P9BJW5MJRW65SZSP8Z3JRK6KVSW4TF29U8GATCYRV4KUU8QG9DHY1XD9NYE0PF0PLCAYWB5IKYGYMD3KWS8BT3ITA8MDE8HQMD8ER5MN9NM6G1SUYT8MVWE2OKQK4HYOP79T4MXR3H4FW4D8D8BC3NSX58HUXN99XU20YOFF1EDPJCE5KVAKZVPFA046KXP9YM91ZKLK2F04VUC2TRARP2M0TD6NTDOT24PCS4RFQHA64X2TZSXOBZ3PSEG9PAQ5F08HFV7NJXYM1VHTD6HONDBIGZR5XDNX3TTIBVTKIP9FC9VYPXOOIKPJWHFCJYUTDBWHR4YQYT4KLH59DW0ED0RNBCNTL6L1ZK0NPPN8VNNIJ34SLH248JY6BWX2PTMAUU4V9V436RVUT868660VAGEMC5GC403R0E9PYVG9VHTGTMAO1FJQTSNZP70YXNXQ6DJJG10MWVVMABBDISDYH6W61D3RNJWIBV7GH34894FBFRX6MU0Y4TB080AOJFEXM2KITB060K00NOTE80HZZ4B5PO1HFGD5XYDV07V0UCZ1HE448R6NFAILF5NBIEICJHJDOYTPCQSXV304K3MYXIRNY1C7LHB9YQ4RSM55EQUS4E2QLYAR756CN4M3GZAXOFH30T1NEINLG8BSEWYAF6UPK6404L3DX80CHRKH42M5R5YMAFF0WUDRJRTXSIDLFKPHYYTH0X6AUY4BXEHZRBUBXRCZJ92C7FISSOFTTLLXIGRFOZI7NSG1E2I2AQYBHKPWU5E2CVV2LYH7Y4XHZQ7C4P31KD5IRHZH0G5O16QAAYCM4S6DAYZOSSV5ORXS6HOPMCS4HM9AI9BE74ICGA3ON1Q1D2MID6XUZFNR09M3E84XZOJ60ZAKQXQ6OJRMZZ2ASA0HSF41XZPI70BYO89U3W0RXJHMCX72LNGKRPGRLQM688PUQHK4UFY1Q34GJ943C5RGFNPBN84SXUT4DJJ89V9IOC2GDLZTYW72USJWBUDLRBD7ZA1YECQTNF36RM4547VXW5PYSSR0BQP80525Y4I6HWL8UF7P44SS1TDA1SAIF73OBA7CGJ93QEPSKHFOJRONHFCZ1FZLNJHK8FYMRK4I1TU9RG4YH55AOWXC12WB82BRLJ5FVKCU4YWUBHK9ZLJHEI54LGSY8FR9A5AMA73UVZCO4KBDSYXIBI7L4SJ88S8D20VLYTF3UDKY04DNTL0P1Z67MSX803IQW79NL6Q1ZOD0CFHNHNS7V9BHFTD1P9Z7053OKHTZEYLS2HOD943CPJEIQP1ALE20UET65LIHDRV5E7WLFDHOQPED5BRN27NBFVX77Q2J7TVSBIP4WKHS9ODM9O6V7GKXDD9VU7KEVJZZ32GQQFH69B805JTIEG47QL8L3JU843KPVNC43VVG1GJMDJKTYGOTA9AXL510PVS9YC2DSBF413TKUBD9CGTL7KF6S3ZI84FYTRS1K1894PUVOGAIP4FKWPXL1GYTUSBQJZJXVDAW6G828FM7EYPP39MG4XD7N71P9RFYJKKMC0R596JL87Z3XV7SD38S1XBT5OHG0QLM6ZF4ZOX0H6XIFUFWSN70SJA2X0XTLLUPZRUNCBFXKT1IL68X851WA40AZDZLU9M1SJUS654ZQDOXZQ9LEJHX91IQKOOWMOU8L3MACX3QSA5W3OPOLCQ3TOAQ4OLIG9FDMR8KGXUUCO7TPPG8N48ZYU1XPIQTYKK1MTQDSC7IAS1UFX2AJ8VPM57DUS1U76X3H1WHH86R2WV08E8EEZRDRAVM4WIFB5GK3H670993WISY26CQIV6WCOJUOA2V6SFZ1HP33I6U5C6IIYLE2TG9WSIZ9NHHHIACLTQA3S2NSMENTVV888LHHQ9CCV8KKVI6WT714G1TZD38TRQFLJ4QQ0KGEDPRNG7O7KRKJJQ785LJEB1DET6EP2TSLEZ51VDOXYFYXXHQKDGMI5XTR0PL0D0WPHDOOIA9A7GO8HR4LLZ5K7SK9XL2VVODY6S7VMKAUFK9UJUIPBX5287OQI4YGF5XXWXZRN1EN40NHNH36E0S1KKG97ATG7PRUJMYTCFFYZ6XYB3I0D71CBJR4TEK9E8EF3LS7SJJRL1I6CYD47NMDIBAGS8EEB3TCDMII4STVKMLTH9ZYQHFZ0SV6DN7XJOZI1JQSIOZJD0O8F0QVNLZS22LG7YED2VYFX7R7XOPRSYDWAVP05ND3XK20JUMNVX01YTK1GVO42JMO0QIDLU6TEUS77OEEGZLCL4PC5ODRO6MTFQ8MGK3W4ZH9IW1F3IKXUE4OMSONWEOFRB61TDH2A30WLBL7YFG5VYC1V73IZTOY7P0N91MCZKGR4O6GEDTHE1GAP1ITTDYR5TKCT9JNGMS7I6E8O5VZ1PZ1GGR4MDYUM55F6CMDNKWLLTWOF3T1LM06I8EHS4LZ9IAWSTVA7GR2MK7C5WHJH3YPV35S05O33RLMMLUKW3NOAHSRLSOPTNMWQRKVF18RU1AQOBRK0X2IXWN2BILCN9678O566RI5BTBEG7M781CQA61DWD0XQ780W9UB8IKFKEGFAN2PLRQ05RG5RCFSH7MPEXN0X27OS0D124UQ5FN5SMKGPLSNFJYX1OAI63YSACPMKS7L5I4WSOMTF8ILTE7CW81E7OPLU7BJ2YG36RG9QLZ8REWL3K6VR0REO365HFEGG1VFJG5TY7625FYNEUC68MVSOPL2HO7XZS1GOTZVBXB7I1HLF0BGXK3NW3JSEVLYDP20AWXZTKI4C1JLX6WJM2X5YSKTNVANWOC7W85RUXY6IIAD5ZAMD3JWF60B4L49IFTLC95D91O4YH12174I6VGKJ94DNGBH83C9YMYZTSBKBL5JLUR7HF61Y7YTIQSQV2VBR7J5CXUTTJFG0L29LK4JLQ9V7W8IJ60X23INRFIP78V0MW5474X2HGDMV3I4NLZDIIS5SH3MJASPTHZ9JWXYDCFIZPSNBYI7LT34DS9WVEACY9A26OMBM4OU77YMPAT0GO676EW8655NRKFIETIACNY885ENB5HGA5ISA3Y8OW 6 | checksum 6d8241224796b997 7 | -------------------------------------------------------------------------------- /tests/dumps/protocol/easily_compressible_string_key.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $200 10 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 11 | $37 12 | Key that redis should compress easily 13 | -------------------------------------------------------------------------------- /tests/dumps/protocol/empty_database.protocol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/protocol/empty_database.protocol -------------------------------------------------------------------------------- /tests/dumps/protocol/hash_as_ziplist.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *4 7 | $4 8 | HSET 9 | $24 10 | zipmap_compresses_easily 11 | $1 12 | a 13 | $2 14 | aa 15 | *4 16 | $4 17 | HSET 18 | $24 19 | zipmap_compresses_easily 20 | $2 21 | aa 22 | $4 23 | aaaa 24 | *4 25 | $4 26 | HSET 27 | $24 28 | zipmap_compresses_easily 29 | $5 30 | aaaaa 31 | $14 32 | aaaaaaaaaaaaaa 33 | -------------------------------------------------------------------------------- /tests/dumps/protocol/hash_list_pack.protocol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/protocol/hash_list_pack.protocol -------------------------------------------------------------------------------- /tests/dumps/protocol/integer_keys.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $9 10 | 183358245 11 | $23 12 | Positive 32 bit integer 13 | *3 14 | $3 15 | SET 16 | $3 17 | 125 18 | $22 19 | Positive 8 bit integer 20 | *3 21 | $3 22 | SET 23 | $6 24 | -29477 25 | $23 26 | Negative 16 bit integer 27 | *3 28 | $3 29 | SET 30 | $4 31 | -123 32 | $22 33 | Negative 8 bit integer 34 | *3 35 | $3 36 | SET 37 | $5 38 | 43947 39 | $23 40 | Positive 16 bit integer 41 | *3 42 | $3 43 | SET 44 | $10 45 | -183358245 46 | $23 47 | Negative 32 bit integer 48 | -------------------------------------------------------------------------------- /tests/dumps/protocol/intset_16.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $4 8 | SADD 9 | $9 10 | intset_16 11 | $5 12 | 32764 13 | *3 14 | $4 15 | SADD 16 | $9 17 | intset_16 18 | $5 19 | 32765 20 | *3 21 | $4 22 | SADD 23 | $9 24 | intset_16 25 | $5 26 | 32766 27 | -------------------------------------------------------------------------------- /tests/dumps/protocol/intset_32.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $4 8 | SADD 9 | $9 10 | intset_32 11 | $10 12 | 2147418108 13 | *3 14 | $4 15 | SADD 16 | $9 17 | intset_32 18 | $10 19 | 2147418109 20 | *3 21 | $4 22 | SADD 23 | $9 24 | intset_32 25 | $10 26 | 2147418110 27 | -------------------------------------------------------------------------------- /tests/dumps/protocol/intset_64.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $4 8 | SADD 9 | $9 10 | intset_64 11 | $19 12 | 9223090557583032316 13 | *3 14 | $4 15 | SADD 16 | $9 17 | intset_64 18 | $19 19 | 9223090557583032317 20 | *3 21 | $4 22 | SADD 23 | $9 24 | intset_64 25 | $19 26 | 9223090557583032318 27 | -------------------------------------------------------------------------------- /tests/dumps/protocol/keys_with_expiry.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $20 10 | expires_ms_precision 11 | $27 12 | 2022-12-25 10:11:12.573 UTC 13 | *3 14 | $9 15 | PEXPIREAT 16 | $20 17 | expires_ms_precision 18 | $13 19 | 1671963072573 20 | -------------------------------------------------------------------------------- /tests/dumps/protocol/multidb-skipping.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $3 10 | foo 11 | $3 12 | bar 13 | *2 14 | $6 15 | SELECT 16 | $1 17 | 1 18 | *3 19 | $3 20 | SET 21 | $3 22 | foo 23 | $3 24 | bar 25 | -------------------------------------------------------------------------------- /tests/dumps/protocol/multiple_databases.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $22 10 | key_in_zeroth_database 11 | $4 12 | zero 13 | *2 14 | $6 15 | SELECT 16 | $1 17 | 2 18 | *3 19 | $3 20 | SET 21 | $22 22 | key_in_second_database 23 | $6 24 | second 25 | -------------------------------------------------------------------------------- /tests/dumps/protocol/parser_filters.protocol: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/protocol/parser_filters.protocol -------------------------------------------------------------------------------- /tests/dumps/protocol/quicklist_with_multiple_nodes.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $5 8 | RPUSH 9 | $9 10 | quicklist 11 | $17 12 | baaaaaaaaaaaaaaam 13 | *3 14 | $5 15 | RPUSH 16 | $9 17 | quicklist 18 | $3 19 | baz 20 | *3 21 | $5 22 | RPUSH 23 | $9 24 | quicklist 25 | $1 26 | 3 27 | *3 28 | $5 29 | RPUSH 30 | $9 31 | quicklist 32 | $1 33 | 2 34 | *3 35 | $5 36 | RPUSH 37 | $9 38 | quicklist 39 | $1 40 | 1 41 | *3 42 | $5 43 | RPUSH 44 | $9 45 | quicklist 46 | $3 47 | bar 48 | *3 49 | $5 50 | RPUSH 51 | $9 52 | quicklist 53 | $3 54 | foo 55 | -------------------------------------------------------------------------------- /tests/dumps/protocol/quicklist_with_one_node.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $5 8 | RPUSH 9 | $9 10 | quicklist 11 | $17 12 | baaaaaaaaaaaaaaam 13 | *3 14 | $5 15 | RPUSH 16 | $9 17 | quicklist 18 | $3 19 | baz 20 | *3 21 | $5 22 | RPUSH 23 | $9 24 | quicklist 25 | $1 26 | 3 27 | *3 28 | $5 29 | RPUSH 30 | $9 31 | quicklist 32 | $1 33 | 2 34 | *3 35 | $5 36 | RPUSH 37 | $9 38 | quicklist 39 | $1 40 | 1 41 | *3 42 | $5 43 | RPUSH 44 | $9 45 | quicklist 46 | $3 47 | bar 48 | *3 49 | $5 50 | RPUSH 51 | $9 52 | quicklist 53 | $3 54 | foo 55 | -------------------------------------------------------------------------------- /tests/dumps/protocol/rdb_version_5_with_checksum.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $3 8 | SET 9 | $4 10 | abcd 11 | $4 12 | efgh 13 | *3 14 | $3 15 | SET 16 | $3 17 | foo 18 | $3 19 | bar 20 | *3 21 | $3 22 | SET 23 | $3 24 | bar 25 | $3 26 | baz 27 | *3 28 | $3 29 | SET 30 | $6 31 | abcdef 32 | $6 33 | abcdef 34 | *3 35 | $3 36 | SET 37 | $12 38 | longerstring 39 | $40 40 | thisisalongerstring.idontknowwhatitmeans 41 | *3 42 | $3 43 | SET 44 | $3 45 | abc 46 | $3 47 | def 48 | -------------------------------------------------------------------------------- /tests/dumps/protocol/regular_set.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $4 8 | SADD 9 | $11 10 | regular_set 11 | $4 12 | beta 13 | *3 14 | $4 15 | SADD 16 | $11 17 | regular_set 18 | $5 19 | delta 20 | *3 21 | $4 22 | SADD 23 | $11 24 | regular_set 25 | $5 26 | alpha 27 | *3 28 | $4 29 | SADD 30 | $11 31 | regular_set 32 | $3 33 | phi 34 | *3 35 | $4 36 | SADD 37 | $11 38 | regular_set 39 | $5 40 | gamma 41 | *3 42 | $4 43 | SADD 44 | $11 45 | regular_set 46 | $5 47 | kappa 48 | -------------------------------------------------------------------------------- /tests/dumps/protocol/sorted_set_as_ziplist.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *4 7 | $4 8 | ZADD 9 | $21 10 | sorted_set_as_ziplist 11 | $1 12 | 1 13 | $32 14 | 8b6ba6718a786daefa69438148361901 15 | *4 16 | $4 17 | ZADD 18 | $21 19 | sorted_set_as_ziplist 20 | $4 21 | 2.37 22 | $32 23 | cb7a24bb7528f934b841b34c3a73e0c7 24 | *4 25 | $4 26 | ZADD 27 | $21 28 | sorted_set_as_ziplist 29 | $5 30 | 3.423 31 | $32 32 | 523af537946b79c4f8369ed39ba78605 33 | -------------------------------------------------------------------------------- /tests/dumps/protocol/ziplist_that_compresses_easily.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $5 8 | RPUSH 9 | $25 10 | ziplist_compresses_easily 11 | $6 12 | aaaaaa 13 | *3 14 | $5 15 | RPUSH 16 | $25 17 | ziplist_compresses_easily 18 | $12 19 | aaaaaaaaaaaa 20 | *3 21 | $5 22 | RPUSH 23 | $25 24 | ziplist_compresses_easily 25 | $18 26 | aaaaaaaaaaaaaaaaaa 27 | *3 28 | $5 29 | RPUSH 30 | $25 31 | ziplist_compresses_easily 32 | $24 33 | aaaaaaaaaaaaaaaaaaaaaaaa 34 | *3 35 | $5 36 | RPUSH 37 | $25 38 | ziplist_compresses_easily 39 | $30 40 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 41 | *3 42 | $5 43 | RPUSH 44 | $25 45 | ziplist_compresses_easily 46 | $36 47 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 48 | -------------------------------------------------------------------------------- /tests/dumps/protocol/ziplist_that_doesnt_compress.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $5 8 | RPUSH 9 | $23 10 | ziplist_doesnt_compress 11 | $6 12 | aj2410 13 | *3 14 | $5 15 | RPUSH 16 | $23 17 | ziplist_doesnt_compress 18 | $64 19 | cc953a17a8e096e76a44169ad3f9ac87c5f8248a403274416179aa9fbd852344 20 | -------------------------------------------------------------------------------- /tests/dumps/protocol/ziplist_with_integers.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *3 7 | $5 8 | RPUSH 9 | $21 10 | ziplist_with_integers 11 | $1 12 | 0 13 | *3 14 | $5 15 | RPUSH 16 | $21 17 | ziplist_with_integers 18 | $1 19 | 1 20 | *3 21 | $5 22 | RPUSH 23 | $21 24 | ziplist_with_integers 25 | $1 26 | 2 27 | *3 28 | $5 29 | RPUSH 30 | $21 31 | ziplist_with_integers 32 | $1 33 | 3 34 | *3 35 | $5 36 | RPUSH 37 | $21 38 | ziplist_with_integers 39 | $1 40 | 4 41 | *3 42 | $5 43 | RPUSH 44 | $21 45 | ziplist_with_integers 46 | $1 47 | 5 48 | *3 49 | $5 50 | RPUSH 51 | $21 52 | ziplist_with_integers 53 | $1 54 | 6 55 | *3 56 | $5 57 | RPUSH 58 | $21 59 | ziplist_with_integers 60 | $1 61 | 7 62 | *3 63 | $5 64 | RPUSH 65 | $21 66 | ziplist_with_integers 67 | $1 68 | 8 69 | *3 70 | $5 71 | RPUSH 72 | $21 73 | ziplist_with_integers 74 | $1 75 | 9 76 | *3 77 | $5 78 | RPUSH 79 | $21 80 | ziplist_with_integers 81 | $2 82 | 10 83 | *3 84 | $5 85 | RPUSH 86 | $21 87 | ziplist_with_integers 88 | $2 89 | 11 90 | *3 91 | $5 92 | RPUSH 93 | $21 94 | ziplist_with_integers 95 | $2 96 | 12 97 | *3 98 | $5 99 | RPUSH 100 | $21 101 | ziplist_with_integers 102 | $2 103 | -2 104 | *3 105 | $5 106 | RPUSH 107 | $21 108 | ziplist_with_integers 109 | $2 110 | 13 111 | *3 112 | $5 113 | RPUSH 114 | $21 115 | ziplist_with_integers 116 | $2 117 | 25 118 | *3 119 | $5 120 | RPUSH 121 | $21 122 | ziplist_with_integers 123 | $3 124 | -61 125 | *3 126 | $5 127 | RPUSH 128 | $21 129 | ziplist_with_integers 130 | $2 131 | 63 132 | *3 133 | $5 134 | RPUSH 135 | $21 136 | ziplist_with_integers 137 | $5 138 | 16380 139 | *3 140 | $5 141 | RPUSH 142 | $21 143 | ziplist_with_integers 144 | $6 145 | -16000 146 | *3 147 | $5 148 | RPUSH 149 | $21 150 | ziplist_with_integers 151 | $5 152 | 65535 153 | *3 154 | $5 155 | RPUSH 156 | $21 157 | ziplist_with_integers 158 | $6 159 | -65523 160 | *3 161 | $5 162 | RPUSH 163 | $21 164 | ziplist_with_integers 165 | $7 166 | 4194304 167 | *3 168 | $5 169 | RPUSH 170 | $21 171 | ziplist_with_integers 172 | $19 173 | 9223372036854775807 174 | -------------------------------------------------------------------------------- /tests/dumps/protocol/zipmap_that_compresses_easily.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *4 7 | $4 8 | HSET 9 | $24 10 | zipmap_compresses_easily 11 | $1 12 | a 13 | $2 14 | aa 15 | *4 16 | $4 17 | HSET 18 | $24 19 | zipmap_compresses_easily 20 | $2 21 | aa 22 | $4 23 | aaaa 24 | *4 25 | $4 26 | HSET 27 | $24 28 | zipmap_compresses_easily 29 | $5 30 | aaaaa 31 | $14 32 | aaaaaaaaaaaaaa 33 | -------------------------------------------------------------------------------- /tests/dumps/protocol/zipmap_that_doesnt_compress.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *4 7 | $4 8 | HSET 9 | $21 10 | zimap_doesnt_compress 11 | $6 12 | MKD1G6 13 | $1 14 | 2 15 | *4 16 | $4 17 | HSET 18 | $21 19 | zimap_doesnt_compress 20 | $5 21 | YNNXK 22 | $4 23 | F7TI 24 | -------------------------------------------------------------------------------- /tests/dumps/protocol/zipmap_with_big_values.protocol: -------------------------------------------------------------------------------- 1 | *2 2 | $6 3 | SELECT 4 | $1 5 | 0 6 | *4 7 | $4 8 | HSET 9 | $22 10 | zipmap_with_big_values 11 | $8 12 | 253bytes 13 | $253 14 | NYKK5QA4TDYJFZH0FCVT39DWI89IH7HV9HV162MULYY9S6H67MGS6YZJ54Q2NISW9U69VC6ZK3OJV6J095P0P5YNSEHGCBJGYNZ8BPK3GEFBB8ZMGPT2Y33WNSETHINMSZ4VKWUE8CXE0Y9FO7L5ZZ02EO26TLXF5NUQ0KMA98973QY62ZO1M1WDDZNS25F37KGBQ8W4R5V1YJRR2XNSQKZ4VY7GW6X038UYQG30ZM0JY1NNMJ12BKQPF2IDQ 15 | *4 16 | $4 17 | HSET 18 | $22 19 | zipmap_with_big_values 20 | $8 21 | 254bytes 22 | $254 23 | IZ3PNCQQV5RG4XOAXDN7IPWJKEK0LWRARBE3393UYD89PSQFC40AG4RCNW2M4YAVJR0WD8AVO2F8KFDGUV0TGU8GF8M2HZLZ9RDX6V0XKIOXJJ3EMWQGFEY7E56RAOPTA60G6SQRZ59ZBUKA6OMEW3K0LH464C7XKAX3K8AXDUX63VGX99JDCW1W2KTXPQRN1R1PY5LXNXPW7AAIYUM2PUKN2YN2MXWS5HR8TPMKYJIFTLK2DNQNGTVAWMULON 24 | *4 25 | $4 26 | HSET 27 | $22 28 | zipmap_with_big_values 29 | $8 30 | 255bytes 31 | $255 32 | 6EUW8XSNBHMEPY991GZVZH4ITUQVKXQYL7UBYS614RDQSE7BDRUW00M6Y4W6WUQBDFVHH6V2EIAEQGLV72K4UY7XXKL6K6XH6IN4QVS15GU1AAH9UI40UXEA8IZ5CZRRK6SAV3R3X283O2OO9KG4K0DG0HZX1MLFDQHXGCC96M9YUVKXOEC5X35Q4EKET0SDFDSBF1QKGAVS9202EL7MP2KPOYAUKU1SZJW5OP30WAPSM9OG97EBHW2XOWGICZG 33 | *4 34 | $4 35 | HSET 36 | $22 37 | zipmap_with_big_values 38 | $8 39 | 300bytes 40 | $300 41 | IJXP54329MQ96A2M28QF6SFX3XGNWGAII3M32MSIMR0O478AMZKNXDUYD5JGMHJRB9A85RZ3DC3AIS62YSDW2BDJ97IBSH7FKOVFWKJYS7XBMIBX0Z1WNLQRY7D27PFPBBGBDFDCKL0FIOBYEADX6G5UK3B0XYMGS0379GRY6F0FY5Q9JUCJLGOGDNNP8XW3SJX2L872UJZZL8G871G9THKYQ2WKPFEBIHOOTIGDNWC15NL5324W8FYDP97JHKCSMLWXNMSTYIUE7F22ZGR4NZK3T0UTBZ2AFRCT5LMT3P6B 42 | *4 43 | $4 44 | HSET 45 | $22 46 | zipmap_with_big_values 47 | $8 48 | 20kbytes 49 | $20000 50 | TO29G8HV1EAC44Z6NZBLD06R6P6Q4271M6AOS702PSZ2NKWF6G011IXPU4CX0UCJ1V9HUDZVSXCX2GM1N9HT8FR4W5XUPWQDO0Q5Q6PI9H1J42RT5V0X8CYK80B9YYSIQBYWA2878W8QVCIM7U2YVEFXBKOQIKA1AEUX86JDRG6AWWA7GJFNWUD6T4T5TRICEG2S9LSBUCLCAEQ7JFET8DUFN1RN1PY7NJDM2YN5JWNZHHD9IZ1HN7NUMTBQZ4GU5MPOXY84BMDRH27MLNCTT8H817WUUCRMYGJHMRDVQQ3NDPSQOWPSS9JJYOFCZY0ZA6R7K9FRF7SAX2FOAKCK1Z3RR0QN8FT508WAWYAYOOPYC9S7CK9JSR6QF046495KTO0EINUDNWJKS0GWJEG62VRWS52IWPVSUL76CKG9HPYK2UZZBSDJIJB0QH0HDGJVYWOXM87LH8FK9BD1G4YW32PT5QPIZDGBC11A8BHIUX855X29B0E2D8AZ4EIGWENSCCAS3PE2VD59Z4OGULDIMVK33HI3ZU2BOSLGJQHSLCWQUKVK0QKNV0MZCU792T5MECW8JVME719MU56GFF4C51KVR0UWEIS887VBFC5MFXPZATDORK52PJSN1I1SYH48S65YWD10S5IDXD29MCTBDKKD3ZRGOG1RLXZ7AE7U3UKR3E1B57APIC2UGFNY8WI5K757QS0F529JMCW3M97YX9WPQ1BS3BCO1GOL9T4OGA6B132JZWI0G137V08S134E94LOFX9WCW0BXJ3OX4KLTAVLIBG8X3IBJK3AONMB7SR8QST6RNQF068QKGLGIWD70I1JSNA6JS50ESMGE4NUXY0783VY5LZZPTUG6B8GTQF5I2OCJSVS7VOSH0XB6L8JRP434HBAXZPBB52I95CTLGFBIIPNERNQQS4VWRXNK4KF0MEG8VBE9XFMYL4TQL3WMZDD88WZUK3D0YRM13Y83D72ER01IGE05MSYVY3W8ZWOCH046O8TER8EQ79UUO32BXVKSASMMSGUFVLTV6EIWVVJ8H9K37Z7BLEGA5FZ3BID0NC18J86XNGOCWALKM9PUZF57HGPG2OCVYPVWSO2904CL1JJOVE5DN3WQBS47QI7YWPKP9FKPA8CJHH6T5LC93OYE02OO3SP30ASXKOOI1MYNWQQRTKLFA6IRGEZZOH8IWNXC5FIXCOYJ2THG43OLZEWV7MES4JEH30BPRF6KG0SM4B1KXTTOQIK9HA3DXTTFLAGB9IJFFD2AAXO802SB68DVWI7OLMUWWUQCJZKIOJBXRYSS2NZXNEHM9SI4UVNT49CFHAAVXPAK6GYXTNOYR6156CM3CA5CLO6M7GNTK1AXCS1A6ODR75O9JO8BN76KGCTHR4IIGM7GTO0V3304G4HMEGQHOS1KXODD32F2X5RDXZZSDNKKC3JB2DVERRR837PL5GXS48YCNMU8VZMRL9NXAXTA3JP7OINI02UXLMK0MJUTG1HTRU2VMUBSWHP3V4HXZT2BZG2WNMCZI54ZS2ZOKBLLU7Z8HUUQ6VBK70QMVJA49XA59T55TVVBFGJ7X3CTHF39Y95E7TEODDLK7CJ6XJHTESI7AUA19KT9AFOMDNE0IVQVZID3EYA4QOEDNJ5Q1OFOANHC1XP1MHLFZQ3NL9LOQ1MQIO5OWXTZZ6WK1GBD6O4TY35HWN0FMS28Z4ZPAYC4RXOGCWDIIL7PSOESVKG53HGG1298MB195A0UZE36ID49KVOGEH3YS6VWOFTZEE6AFULWLRZK0KKI0PLQI3UV1DXUMHRD5982ZUJAQU2DGBQIZPFQJ53R0H3KECVYKEYAK81VND80Z1F6SN3BTYXFB0XK696QQH5ELQ6CHKWZFBZYL0HFA4TUHJ1FPQBLGVGD5CE5S0U030ET2HLC50LKQ87CCCRZAFLDHWB6SNUTP19LH4NY85HA4I4LK8GJNZ5Y89YHP9G4KTJ4OEZ8QY0FAAN5DBSMV6YDHTQ60OV26RUYOIQAT899IYTAI4RCV8DPG2WUM9FE78YRIIPEUA84OND8U33ZN7SRADEWN313OZWDM7LSKYSUIHG1DXKLRMZZCP869ZXMEA2P3F8J30UJCTAZLK5D3AZLGOWCFUL4ZC3YV9WE3FFER8POIVKURXV3TYF2PGUTF7Y977DM3D3DNBMZTPJW3Z0NKBSJAX7MM92D3T4Q73ZYJJCZNQZ3P4Y7IIWLTEI6W8KGY1Q53BL8W127ZBPGZGYYWC0ZI5PBGVXCQSWXLNOY0GSRBVK4NAZD12IHL6AKMFDESLE50G5EJT6L9RKTZOH87J0RD283CUTCE6A9231UVWDFQQLBQ5PL0R7DO4P0D9LLB1D5Q56AOQ747M9TWK7IZ6VM273HW4OJF2FLME8EQ8LD211MR4S1AF3F1XIZ2A9R43WNB88H0FTGL7UV18EGJ9TG3P56JA6M9G3724SGJIZTJYYSOH18GE7SKK30M6JH3TPH5Q62YTKQ5HEOPTRLMELN302DP3OPTBB8ETBI9CFZ2IHH6TD4Q7HZSCDBH1KLF9X1B3PRI9WM27G2AOSP7TSTF0JC0GSPPF98PH8WSNAB17DXZPHK4RAYC4XOEN1MJ9XVN8FZKIXNF8NW8IVVXUKJ6GS5NQLX5BQ2DLUW4T99XZ5H9VQ9D5JHU766RMYM0LG9OA3FK4CUGGX7JJ0OE9BYBDCPHNMB7494K2HIXKZMBHV6KW6KESHF9QQWDAM6I716OMTNA42HTH8LJW3DPCLCVWU25GHP6MHBYY3W0LFT7YJWGP0VTKOC6BEF2CA4IYSRLQVO7EXWJULD829TA6J9VFJWL5D271HTFYIN7YP8XX33VQ187UIV9KFVWO5YR4DVU7NYRT9RM39MRYK36IBWT45YY7F0U2V4SQBJTZLK3RV35EAFC3KFBY8LZYVUN8UR2D4U044XOIYGEMN3Z3VR7413TJEZO12IQ53E5MCMHBHNHZF8MNMELEUD6BKEONC3J15FVYBFY9F1HRR4QXORSZUYM02SLYDG9VSF6YIX98LBFIHYBONUFY8U5X2H28LT26IA8R845T4K3BMF95AR8LS8C9I88C5MXCQY71UQEM6UP9VC6RZRKT08OFL99I5MM51TZWQC7X5NQ6P1EJR7KTR197TMET2NQAAZIL0SJLOKMVE53WE01PRQFNSIR4HP9S2E14GRQE50HIODG1B8BG9BG4ZK8RUC390H0YZVH736WHE2O4819TRHO198LHBXCNP71HJSYRQJHZO0X7QP4070FU1ZXN5CLYKMFC37MM5G95WRC8Q63I2MEOC7PL6V7L3FQMCYXG52840ABODC188W0HTVZYVVXY6PX5PN029CY3ZWRQF7VDZUZ2UAQOI3ATJGRYNGTSLJGNUDM8GKXR9IG6C36LV1OPW2AJINUKJG7HNK7MRIEMNWF16GP3KIG72R5826SFFGQNFU7OQ9H2OIF8DPU93AOA7KQB01D5VETLO42P5XJT1O60PN83OWXMEZG5876U1C8RK6203JS2Y9CZGSWBSB1D543SOYXKYCOBQU04GBQQ1WZZVLNRWWMX9NIATY4L2OV99UFPMLKS1DY4EZT3RVZQ1QYKT4A7N4X0713PQ6YQO9N8AE5YLAOOT7IUR3VDOE0HCCAFG0VZ42HP6OK1R8MCRO37AS2QESD1CIZPNCAPMPUVCEZK1197TQ4A4EV9LGRJAGAGD7VKI7WXO0FS8504UQ2D3LINE2RO72O1FMQ8LMLU150OTUUXAA6PIBSOMZR1S3YLCRWVQ36SBTI9T08LZ6U9OI32VFJ19H3Y0PQJ3488ELUXVJFCDQ8D4WYBZW8MW9IFP270TEVU34TXI98UL9V67EK24FCG18SHMD075ABXMFI1PVGHQVJXSNGSJJSGY5YLBDYURG2FKV9BWX77M300IKSZTMA7A9SGQHL6ZTTZVBMOH0Y3T1PXUWK4IRJCD8BJ5WNU3P4394QSSM31JPA9WONSN1AUMNCFVKGPE32UJGNPSSLECZZYU4X1RKUH68PJD85IAM8AD2NEGQU71B5A1IETA89D6R2MLZFER6GLGR1OUCUZXRKDUXSB5WIUZSJW08LJNGWI0BCJEYCCXL8DAF2QQEWL0M1TAFDM2NTB3FXITJYBRTK9ZQXE7SB5CN5EK2U523M1NLWXF15J283T34WK2V7A9Y2FIGPGNLCVP6MZ5VD2ID91MNEEEJHGZHQFE9OVM48IDQGDBT1LAP2ZI61T7PMFA9QLLV14OUJ2I500RR59O1AIBTCGJM91XVE1RS6NZ7ZXT1OUHEGXWIXV8QC5XW51ZZKXN8M0UQ38NV5QQRA5HGM0W28GN971PMKQIXII69YYHY6E02PVBO2GFUIEAQEUUV9BQXCOY01OG9XME9PDPQU86CJY3E38OG3B149ZGESAYXASGZF69X0E7MR24OB97HQ31INT39TS5PR3ACQS7ZA3K3N8PZARGQYSACFGMN6MZKLVWV6K21ARY8OT5C8Y8FHRTGC3U1HJVRFB37HPUC2488E7HICYLZVYZWH3D7P1LTTMD1GDD1OL3IWL21PQ2TH5YTXN32N57564XQV13KRSWOUMPEY9A877RPIH14CY4KT4U3WRBG9LJ389Y00LSEOVUF3VCONXT1R0X4Y6TP64LX2VEWHIHWPPHUC04PY1VGV1ID5SYZPQ3XP6XLUP9U9MXBFBPGNZV6X43G6FAN92ZVP0NS81LO1ENJ2PGWKAJZY904WV0F6GPP0HU0YDHFEMESVCNJWYCF2REA9KK3Q95ZU34GUFBFFI2YBVPU1VQ2Q8YQFHQWQKCJSR2UACOQ3CMM3YBFX1YTBEJNDHGYV8XO3M8HEJQWI5UT5CYO7PFVUMQQ4H1P9ITTHERX7NRD4Y4A23OQACYAVVKVR5EUS3PSGBX370ATD2NLG8V99S5TYXXUBJXMC6QF80BAHV2Z2VRM2XQOTEZFTXXN9Q1ZHGKXK7X3OYT36DDH08C7AYFWTF0XQ0NVZUB7A09U64MQJ6MDLLZW0GTN8ROVNSTIE44UQYDSF5Z5WTCY3AC3DGV5RLC3HBCDHOIXMWIFLWOUGUGOC7YH87OUKLVFLABZH2HLLECDZIGN6CA9CWJ81JBIX6W6MWXEEUX4ROM0830B4QTOI9LD6S31QD8PP75VTMKTCA71XSXSK0K8W1VEWOP265N2MQFRMNCFI3HUSOH8Z0GEQ2UHDVPA7L2WIOVTDYD3QWNWI2GJFFKB9WT81IBO99VLNV01AL40AEEQDKORX77XPCCSOXQ6GK8DHFOC19OPO0G1SV9L3A8IMDWZX68SG55P3PUVZIGSWZULY62X1NVZCKGFA56BBFDE04U26SW4D2P8ZE9O0PNQBP5PQEI90VFUZJC2WAW8DX3GE61V2Z862D8SN99PZBV838GJF3C3VCVWTYVDK0Q5XTRUDNCHP006VZSH2AN8G4GBFT4R58UK4DAAIGTU7DZMVQO1VI27EU16BOW4QXDZNTAL93B6OY810LUOSYOCOIQFO6YW8J11G4OJAKHAGHY68O9MTKCI7242SJSWK7WWI82OWC1BNL537M3M364TR9QE53IKG64PV4VOYWJTQ360CAGAAN2316Q1L8EIUIUYQEE82TY4GI2K1ULBE6YSUGCYG67NWBTNXSH933YWY1GLLR2UCGF4M26SAP8NOD1X3THI9T3QIJCRA2OHMSTL9KBAU9G4397T0CE29W976METU1LGIUXF3AQWQIW05NJSD2HAY2RBEY96M1QL6SOOK5MPUE743YGED4G367ZV3DV6H7SAUSORS5N3UCND7HUNHFYBN5UIE7W99MB72OVXUXHSNEM7T3P4NA49ZMSJM1NP9UW57VSX9DS1DH2YK4HZ41G6OAM306R4MH3F2FCW02MP9IM51QA8FIBYW64V41QTPNNICGZA0NZGJ93ORXKC1VK392JUY2K9V7WPRJH0RMD03NZ84KMGEF9GJK0685XDEBGMC0YAKXUW4CSJTJG9XLQ0YUCZTPJ4EH49B5T5DNXZXBMYC5E5DC9SGJOP0SM9HASZMYMH8J8J0CL5I7HKIAWC1UZZA0H59VKN0PCWOWE9JDOW1MR1LK6F57TJ4M0MFJKFRZJE9W8RRXADHCFSKHDACXLJCSKHQUH2X9KZEPIFWNK699IUMUS18BLNWUPMJB3231V5I4JVSF5WYOSHDFPSVRDEEI9DTIIKXXEM7F0XHR6P8S7635AQT5B04QQEYSJYX6FVT63MKXAOPIXWPQ9Q2L6IY7JNBFKMIGA6QTBNVX2038P7FW6I57GEQP27P115CZ9NYFQH3RGE4RV6GB27PNAO63Q5FKYYSW8V9IIXLNDWAI8I3OIVKT6FBNYDQ70URYP1OJUZPG0HS07YKCOLOSYHU5C8F83YV2AGGNUZW0FAIXZ65W96V74EINI9M5F3GVR594US0WDCZUWH3OLQQWVF8ISV33KQU0JEW2RWCPYZKAOZYVNDN2A27EU8AME4PKZ4KF4ANOW82KX7H1JL038C2R1NUP80UHN9ECK8W8BEGCRVHH223W7GN1XD1EICRXHS7A9JXFUE3D6LWHIJ0ZDYLMIIBH4BFXO5RCDJE1R2E9V4JPYBC3QM53G8ULUR31WHZUUQAB862H4WUNIIH2II8IH7E8QULGGXK7P31L5CLL3K8GM88ELJ2KBQXZ8BVWVXVL08A1GRHTWXFBBVGCIBXK9Y4NLKCC2CCGLIQCKAZB1AYJGBXDH8A49XG5QMJVCKOUCKG5QDH84YMCICNXROIHK8Y9LZ4YEKQZLVW1O1WR2XDM8T04QNVP24IRAGME9743GY5WMDXHT66X3GL9BJTDIM7428B4IJ93TUZB2BSQB1SYXD7IZEWHG1U5L966KEEPY08QK4UONKT3ZUPCULWV9EY0EAMI6SJINWPUJ4DKF9U4HVEIU1UI924RDPYWKIML5CAA6FFZD4SHB1SXA9BQPVBN214NLDLRZ2A5GB5TTRNDSX917XGV5KOEQBSCESD7D0SFI22N3Z082PVQT73KLXL2IFGA6HI8PM08CEJCGIGYYSP8NM57MPT76RBQKGXHG2U4I2UR10MGP4K8E4C6IQAL1B5RWK45Q5NCMZY3HZ35YG0TCAB8WQCHK9WVTZBVMG7794FIEVYO0U1B359A89I33LD0LINGDKPNT6I4ZUKKF57WPEI13AW1MN0OLYM4UELKFUI3TWPH9I7J7E6SXO8ZLQP6DZN6M6FOQD99U4N8ILZA0Y9SZMMZOVA6PT5FV9RGK09IZ4XOICH1VQUU6N7XEHK4YKMXRX4YOFR5S5SY4PRAXME1VN07WQVAI3Z7XU0QD52J94EJ81GQEWX00UP3O95HUID0NZV1WAQNXR6TMDG04MU9Z93F0PI1XYNU6QIDN0PA6E5N9SCTPBN6EVMIP8HE1QH3B8CL8NF1TURI7DTGBX2MJMY7VZUXI62Z7SPXB43AQE7F22JAS06YF1QTA9G9Z8IXWVF3G87P847BLLTZ02ERH2XRP750OA1AD55ERSKQLJENHISJMCK2IDUEA79LAYNJICALTDYSSW4AKBX2OV1ZGU3EU4WAIJZZDOXHQAG1PTP7FJKZN1O84EAQVIKKF5H2108VAT1K7MB7F3TZLVSUHFHELQJ6VZUS015FJ8P9RFBNBQ15GPC64HAIBFXDHWFZ98XWDTTY0U6WBKZ4F8894DAFOI0I1VZQL8BQ749CWLLK89CBGIIPPXNMJTGX8IF2XK7D1NNSWIJYLEXBB3QK29IPZX9XPBJ6ZAUYS12RWV7EK856GG3DLO59B4I11T5XOXTRMFPXLF6E5RJIS1L28PAQ7VMAL66M14W3BLCA5Q8NB0W8A2T9UVJRVH35GVJ91TC717NVDFY1TAISWJQTESGUO2TKNZ3P3W1X8CD9CY3D8DV9DIKD1HLK0M7P46IZ95ULPAFALTUSGXVXDUZCQKWQ6AZ96D2HK4YQWX25V0HL20BQ5YHYHT04COICIHHFPJ6UTCK1NPR7VISTUMU3N5MNU2TE685PXSWHRC83YQ8ENN1RICJH2556GXK326OYZ7507Z0UJGOYKI4OE48O2SHXX3CWNLW62YZ33Y2Z4QB3RMXBX5RRN6LQFRUY7ZVIP4H8YGYEMAOB9JYVR3Q0220MLPNNTYSH2A3JLKJCFHIWFXR96R24VQY5JJRE8GOWMHAYHF7VOQU6FIWS2O3X28D29X1M0I1QJMUOXA8RBB5N3LFYMZ799RQX1EW93TPWWSZ7M0F4EOKJFIHQVSRR41H1CZ84VCZK14BAG8WZKNHBEHM1FNLFQ2VUD1NTP02R2HV2QJ4N9UT6AIAH11T5CH5PR1GUPR1C0UIMI9S2EZU7UMA1FVIG28FOG4PSPRHN9DDC803URCWCDLHNL30TAHS2IETNGBL9B2LM4EDQSR1E88VHD0L82WDFXC4GKZ32SPQVZ1XMPG08VQHZZWSQAQIW90ZAZESNKI67I6E1XFFMHIDR47KXUINHF2AOFWUVL10NTDG0PKWA5SAU5QE260RZ3OZSIXYNZYCX4FYIYUXVIHZAHGAM2QQB1B42ZLYT6MOHZVQD7T95MBR62RYIC28WNDPPP68RYQXGWHJCB3JTQ40B9ML1CLMM05F22LHBNL3Y0XFHKBC8QY72ZLNF54N4QDS0TSR770UGF3KA05NOWMWMADC6UXF0GPB6BJTGA0GZE9OEI1HJQ6L9HTW8C5WW900PDIC1MUJWPWSY4E41TRGGX8FSI7T2C9GWV29MDC2QSK99JE7U2HXFATQI7UT9CMFJSYW0E009N1IU700I3NHKJAZWDDL5T7MCLC9ZM6YB6QWZSGEGDC5SW8CQZVM6GWVJFPZX94GGY4SBIX6CLSKZKJTFOPFN9QPDV816W1L1HJQV280ER53EDEN46XEXF85UOB1HT6TLY3SKTYDXW8ACMDJTUR5HWU2I2W8KDW8G4KGUJWY7JO9NQMZPUS47MUZAEGE97Q9RD982GGJDPGBJXXG6UCMO3NNJRVN45L286F02RVI5BUX9L7GESVHB4HA71R6XN22PG2J4EIGJ476T5KX776J7UQ0LDN6ILSWXRYQ25VCHAWE50U2R0L6U8E3C8ULDNXFU747PKHFT21ND2277JMX3KIL578A7AKQX25JUA2Q9KBLI30XM41PCJXQ9QGA82C0XLN9EVRAMAQAJF8N6WJP1YATKRQA7KSVWAH9U0O0PCX58IQTHKQITWOJSM3LR4BI8E81MP8PMJBX521OJYCASPLBVNFLN9WSHLAMEE3GIO72A54EWPRDN6Y3FTSFKC6ILBSCZDNEZVK9VP8VSO4YMH6NR6Q6AX3G7TCGD50UCRY30WTU7PFQIYNZRFEVK7ABS85P6D5GW15NOP0R9ELG8COMPBGGYD4KZV601IV7FXTP8Q29OO4JPZX23NH43D0836DEOX8IU0GQUMEYSFDMIOAK1TZ3TZE9ZOOECZ00GNID3ZK74X9AI75W8WDVYS931CGXSW0JYPJUX528OXH8W0I3LGCK68B0LEA05QX95TWBVXY68S26I5QNTERZ83XUZ1Y9N2KZIJ1LV88Y01X64R803ZFKGGMABVA07CN1ZE1LAXFEPNRDYS8XU98TENE5MBQZMQ826G79UELNRXA77GYPG7NZA17H5BIKW6UX4OZ394WTJD85OGFK9IEBI2T71RTGE6GZD2FUXX0J8P1C8JX66MS3Z1MKBOY92OOZM815UOTTPYE96SLZUQE15RG1PMPY7NJDZU5HH0Y13PNY6BF9ND2SR6LF8BC8IG8GRM0AS5HFSU5XUVD73PAHMABXEQ9YPV1GE8BG1S5BB8YMAUJXV1DTKJBTNIZ3QJ9NPF889FLDQ1COEFNGWSKF3CV1OLV1QUMSEGQR7HBDNTBG1OGZU06AVM0YPFM4W0LM00VMRY469S99Y1NLX5R5UXNWZEV264TWDHC2POKXUGX9MCV6ROCCT325SZ0318WAKOR30QWE347PSWUN4MLV3WSPRLXVHE67IQT2F2BC5I009I4F2EXA9U9LD85AY8D5XVWNFG1JUOSOBB5K7XLEH0234RELJDQA1EHOPZQ5DBDXQGQ9OCJX1HH2DZS1HQGNMKKX1JNT522SA48O1TYSOCOBPTDMGCI10WUYQY1Q3OP2BYDOPILQLPQE08K5E3AUMYID8NOH0IFOQMNO3HE93PB69P3LER8CIGCERV8SC161HK0WGSL1KQJ5EC6F9UCLZQQLAXJQO4M1VRRL5EN7WEG6NKM9937Z84RIET0DR6D97E8FC1OJ3C5BTT18IDGK7OQFANFWT8W3H86P8CMHFAWY1MMG6XGG34KMLGJPF9WXSWLW6OS4321B94U792YZA6FEJXKWHQK2ZW6MACJ09F3A50EAAXPQ2E5F05QEZNBIOBXT0L69511UFY3A5L34TVM2IUWYWERU8WSZ36YQ2O720NCAKT6B5FR1VB1ARCBBKTS5C2Q352JRHF7S1A74SF6LE56USIB3I1UCVQ13OYC0OJ622KNPKUI78R8URG4HSU1MQ0OT3K5BX6A9RILPGZH41M5PIHJZJY59TKUAIYJJLLZR1Z2EKO92XH7TL7RPYL3HP05T8XEFE5C4Q8TWZU94PVAOZQCBMQRXJZT6W5SBSS1PALJMF1MLPPHJKC93X8L16X8WGYG3OXEFHXXK79ME3OVA735YAP5POMKUGZOYT5BV2ER9V8B1L64L61VMQ43Y1QEX1EPIS7W8MW5Q80YI917DXUV6OP5LND9KQ7PD669DO9UZZTH91SQ9MG9E43J5FC1ANJYYQDWE3A76WVJ7LZ3EA1G9I4L4MU5XMC2RCUYE5KHXJ4PZ0SHMDS0UFBJ481QKTJ7P48WWUOP691RYXU1M3ESZVTDX4PD6IB11Y8NEHIJF5JDKSJXETD09B3HNMA7AUGH3YXK5YQFDHRRAI52L4U877FAKDA10J0FL7QAZ6VO3OLB7CNFHXXOVQ12T6XXKRW8S7JRBQXD6DNV1XNM3G4D93DGOGBIP8UNWBBIITLKAOFZJGF1G1K0W6IH796B22HGZ2QDA14UZG7XCV9ZTMUDBJKB4HVS5ACFY0XIPN2BZNYS7SG1WAS2113AMUWPTPY4SK5GGK0WZKB2RT1TOUDQSPHRFOQWFEXM30PL1WPGDQGC638CDDGON5SOZU3NG2V2MI58WEUMCPNXIUVMYX6IIP28JFP55K9FW40E0XLU9SZL6BABN18S0HOXKY8SZ2CRI5K84TFYEKZU9V0G8OTXHFJ91GS4C20B988799SLTFYRKGDNVA86P0N6KTDOL30W9MO38RX819QHSUKTBT395VXTD5VTUGEDCEJBGKEMWS53QZJBIKS9VFY4KOCHO666NATRV3LK3N5JV2S7SD1F7W51EEPU4XWV8YHZ3GQ8T8EOML065YQYD737VSKS9ITS8L27XMS60AWFQC2XCGTDHFWEPLJHO0JPOEV6MWVTE9BKK9EXZS26AHC3EDAVK56NAW0S7991ODAKF56IOGONHAIWQQ492LZCU2AWLKO22G1GLUP8EMR1O42WRMHTAA1IFX9T6FWPKZYZF7YWNW8VAF4ZES4DRQH3L41V3W3LOHH9QFXLNJ7YP4BDO34Z1IP82UDBQ34GTB0EEHH0HBDGL9HH7ZQYW27NL052U19E9QIYTJ2RDQIPDAFXCJD7ICRQMJDFWFEV80Y0ZUIBJ241BY5NPQ0RR6P8FPHM4VLU4KRR682ALONKCFPZ3CAP6GZFPAOM0CXBAEJHHK3OH8IXOLIOFA5T4NIGG2ESUT65FKELTSXCWCHQX5DDRLJVBAWP1QITRI81HINAA6C9QTAJ7OB8E4L6BDZUB9RRB9GC4F7NQW54DBA1BY1S8Y74TRHCXKSEN9P67LJNR6GA4YKK3V675CFK2OIPAD1FOAYIUBXN0AUM8XLRR9DKOF62VQ2R9OX2BUK1C3VDVL4T5B7WU4V4Q30952E5QDYV36C1J375FS5PR9S7FVYYP7BTSS3NSWHG1IL0LOPYXL8DK5T85005AJKLV7LZQX4J1P6KMYCR5VHZRKZBNL0BEWPVR3KKKXUUHP2D798YQS803Y74N40PP2O3A2F2Z5YNE7AHVRB0BQHKB92QE27PKIRJESGRZACGUHKDVKPD9V67GRTAD3ZEREMLL2J9GQXQIFG5UXZQK3N0VCWO967PGSR3S6EJI23ZYUDVEOOLM80UUJLDDXR1FROZKH4Y7GMFF6FU1HE0JXXWMRR7LC9T17GFXEPFJSEF37C1EJYLE11WZCY7GC9NIDYDQVYQQYE9WUK7ZQ26TY91S6W10A67MYGCOFWCFNA2XQ53N2U5C4T6INLQDKHCA5E34WTF467Z61LFE22RGZM07KDUNVQVZ5ICMVGP8HOTZ34K1TW1O05DJNYQPHU0A2E5K5M2XXAHMZREOUMHCOK10U7WK8O91JXVOFAKAG4Z1J7TEN42XZJDG6L8MKB7QOXVZPNX4P9GTTGXZ7MRBHZ63PCKXXLBCHZ7ETINC5AX8CRZ701YKRH2NVN6FWPI5AF9328735I69POY5IBFVGUV9DTV98CNDN617IWE4PE7LML4ERVFUKEJRGHSL6WHH8YMWVP8G2NWQ6AHT5F8XA0BRT42IKRDUXDSC0ZJHHMQERDWR5OBLXITBKG4OD40GD7GI428SHM06JRLAHWBVGOFM133T8K3YE9ORQ7FTG9Z1UYPG16SVS3545BI85P027FW8UKE29H6VS29ZHB6SP30OXQDGT2UH2LXU3IBJQUOTL1DK8ZNMUFXVN0QDD17W8Q4F7HQKNVVBDKAILOYDXVMPY9CVWA3JDNSLI3H4IX939W2CW66RIGFH78O1EAQZUV4GHKQIR06G5OB3Y3KIACEQIFQHK6QA7N2UWY8MXGWUVXQREU9YS1SJ9GI6MKK0CXTJTFVEDG198MYJRDZ0ATIQMF8TMYVXZWS36R2GW5AQ09AYRPGTDDXZ016V2N82V43QTA6FMC1O5RMFK1X3IHFLKK1VH03XYI7SGGI29XVPDDAE645ZMU3MJF2600Z1BJV599P3EAK3T6ZYBWF3XTB4SZ9K0SNL5FVDQ0UOI5ODCMPSB69HTCF9OQ5FR99UV2ZGPRCSCK1JTU98GCEWSIVBQ7WIK0VDGRHT1BSRJKZWESK82DE4OB4E2FVAQ744XPWFWAKND6ZWPB2XSGGYFOV9VCD0BRHCLSRBNBTZFPQTHCJHMZH1JMXTRFZIOJKO0B86IQHP0ZE0UH7MPHPN4X5ZJEZDCSN8XU8RTJV7DX05WKHKW3NKCPY5MP7XHQOV1Z7HN5NIDS0C3CIBE0KEHX9LLF04CWONIX150M0KTQVA8TKRYB9UHX8CDRF6J7X6U44AYOMQIT4SU0F1YF1HS9UR3BIDY42324LST8PDC3GXPAI3OPD8OGO75OUL72KP5ZP4DKRQG6WGOVH4T77FSXHSN89M44YKL12603U9RXLJA0N98HNSWXF638U3TANA6TB14YHAT5WDDHK1KM2CTCSZQTQYUE6TC78QXSAEDD3V0JK07521O0UVBB0V6D22II1506X1F0DOIJASN88V6EKHLS6KB4WU53XGMWWR2CDDOCBJSJCMA4TAIRG7SY3LD2QKJBDQ6N4ERS2ZEN1I2SHO6HMLCYILG7AEY9TPRAKBADFYB7UKWL2E2CZVJ71TVAMWE3ADK0HNLMV5V6EQND1IM1H8S4M6LFOPFHS9DJIFC28RON9F9YZ9VVDIZ82OC1TTI91GGO0UDVEQR499G3EY4D0ZF8R40XOEL4RRAK0XSWNADLCWO0HV6X7W53NHIVKMJQF229SBRQYVIS5R2QYXPBR6LD1HIC45QVCNXJR7EF5LKF32UMSY0W3XMX374LWBFBGQEK31XDC7V0VT4ITKRIF4JRQUGTJTG358HNYXUBYDEXWBOV7ORSWDC316QNM95V0BAUGYF9RPJXZT90TEUK0OD1OOLY7M3ZPVC8N2Z25GSZHAKMLLASBS167K27XY31NHGC7RTBGIQVO4ZJB8WOWKHSWVSL9503D61S08GVB8WGIGQCO5OWIJV5TAUANVOGYTQ0J6F010VZA1T49C0PZ36UO7EK1Q4E8D31R1O3A644TTQ64JUWUB43DN1SI1KO569SE13GUQSTYI7M92E72O9B2JW7NSYF8Q99IIR9RI4CCI8XLK57D3FXKZS95EHQ813XAAEUU51XM0BWCQGGKYC15GIR51RUS6N1O1KR8NVUVPLHY667XFT6S9Z1RUUGF6KMTBS2750HQ822SI79R1GTLR3TN7P1X6KKA7OF0FFT8OQ8M98RCNIJDRMTPZM3GZURYRJ2HRQFEGX14RYWR3PONHEQWTAHW8Q4X0MYFG5G35QYCNHBRKGPR8QUH8QUZOJQOLDJ8AP8G4ARDXL6XCYK6OGRUHH2A3POBKH2W9AJ04TLZ7FNOSL0767AF5W76UYYZYHCQPUFKNRU4I4NO0BPB65H9BGUUB5IK1MJYCUEITSRRXOHAU3HU2X3ZQ3DIXCEABGC5YIKN031AO5OFWCP3274C5NC48G1Q3NC1VPGF182RQ5ZH7RZX4L9GEHUJ9NBXKWRYEF05HJ4GB387XR7HUJGYQT6GD6RM6SRPAJNI5VEU1QD3JGMWY1I0ZVG97WP01F0HPV1DO8RK4MWGDFZ2FK5O9FMJGZXJM1AESB6TUGJO704Z1M0PAOWN27OZ0R8N74S7LGVMFBUMEXQRQHQ1Q4L4PKYVVPF9B46A94HG9O5F7VT18PP6WFYEXAJS5AIBUQVPQJS2R5UM7VDJAH559QZA4R7C5IX1SKUDABA1XFUZGNR34HKQLH64PKWZPJJLT60T9B490II42WZEYGAXBFKZCCRRR5WO5A9G82BO8WDQFO70YO2G739I7RFCJ73Y5TY1FU1F65NHDD2FOFO1BUM9LXAUAZWFVIXOGGHPGVYB5LDCXBDWFXTMBPG2DHZM4GHRKAZDGTCYS9CJMRXL6GLPI6PGLF0GFMX7KRZ3ZGIBPY3HDX1EXAOYT0G61MKXM02H4Q0TICA9GZOQBIOIH2OUXDWBK22CX0GW5KHVDRNLMH9UN6ZSGDBFVABVQYCM09SIJV5IY6S37BXWDJETJTENFDKYK8KIUMBZTXJ6C7EX4KKA6VQTLMQ3ZU74K5OJ3YWT0MTYRZPFH8NY07FOFVXEYOK34LTLLD18ECXKAFF97ISXE4T6UM131EW8X5TMUNIKMPW8LOHSFSCS8DXQAE64BWCKRW62PAZ43FHV9BQSNHLJ6TN7C3GW5VOPP9SU805F2M56MQGZ67AGPT770V7PKSUK7JL4P3UWVJZ843T86DLDFXS32NFB6M59598DCUHXNMXKPRRI2UPGPJNV8G3FFD7L6ZTECYX791GX7IYABA7FHQIIDTOR7TDRSKFCJZ5HE870DCTOMD4FFCHRIDEMY81RME3NKMADE8TCDNKEES8WDLLYNDYDLTU7W31WS3KPU4BB7QBA2EO67LILEUJHR9EUOEUVKZKSI0GJAU855XF8BA8IK95DJ1WR5AVZOEQT7LGPBTQL28ZJI0CTCTQJFGRZ1DJQHEDW8JP4U5916RAHY5OHHI6RF4D5M6BB1BWHHFCNJM9G8XRJN8UVB3BXPOHHMDCBYJP58Q2WVHJRN4JC9FM1P10WZQSY53BLBOLL8KW6YSZKL60ZVN38GP6OYNCKC2I2EPK7TWKDVCHNFY279JOJOOCAV9HQAYVZAFC268A0B6JX44SI5SCUZAYY71NDW1H2G3IM64W43KIH284U2M9UAO68XB6AQKY8544X3Q0B6LHPPNSHM13F09QF2C8PVXN90M5LUA71CVV0L5FYOQP7AB2X0PFBJXY9O2NKBVGAYNDJ36GLLJWQUA4ZI77O3W9EMO2N3FVJS00OX4U04126QPQL06DKAKCXWLORJJP5R182OC2A9OEJ0OZXTWRKHLBDK5TCA9QPFFC1LORHREIFSQQ5UEPDV2HOLTJFPWMLJWQ8L87V70SHM43B0J3TJOXB96TWV8P797JU51E6J1USPKSPL39XWJ0439NBT7PCZGBEKNON3X9YJGIDY3PN8M2UY21BLUUKQUL7Q19AEMPSJ3LER7O9F4B26YTMJQO6X3HKL6KPD8PAT5OTS24T0JLYF45V8N52J2FLPUG3X2RZ31TCAD87BZXROGS0CGX3QNUKOYGV20FJS093CQKNJABV7QCEGRUCHKSJNZKBGOYXOCHR8H3F9R3T20GNXPGM9Z25FQ3X9PWW4MKXTMIJE943GHMGGUYCI10N52PS5HC9BY2AOUWLKAT46U1AOJE25YMMR1F962NYHAFLLFUPEPDL9V0X5FKHWTSQKPCYLZ1WF4V7FCNRKYNFS6SMD54DKBNOEV8LVPI2YDYN533V2PO7ORN028GLIWH55M7Z5T41ACPW6UHNQLPWTQO6O9AOYIOCSLEMNTMY0IJI9E0UFIOGOWQLH0H4KEUWFJ3DIP6UJBV47U71HY3PW0P8C9PRKPWOCC79X60GSOINZ3U6RVIRT5X3J2FW5YJG2W1Z6OQOXWJ3QSDTE3VZOS57RL1IO39XQM88XRSKZ6Z3U6A6B8ZWD32QLTSS9R82QYKJMG5LL95VTPW2OM5SQT4GZZWTH3CY53PC0PWKPQKHZ6SYWZ3KMY2L7WQVK4JVA94DFJWLTUU0TB28QKSPZTRGDQ5G9BFDLCOESM7E6M4Z7ITKAYX2JXWANQU5Q3T4O4ONFP7SS5HUIY7HD41D2B07BXYDH43BRGMRYWSTIP853EMYMDC3E90TCIEFHRGIO55UR8R1D6T6RSZ4626WGLAXLFNF1SR7P9BJW5MJRW65SZSP8Z3JRK6KVSW4TF29U8GATCYRV4KUU8QG9DHY1XD9NYE0PF0PLCAYWB5IKYGYMD3KWS8BT3ITA8MDE8HQMD8ER5MN9NM6G1SUYT8MVWE2OKQK4HYOP79T4MXR3H4FW4D8D8BC3NSX58HUXN99XU20YOFF1EDPJCE5KVAKZVPFA046KXP9YM91ZKLK2F04VUC2TRARP2M0TD6NTDOT24PCS4RFQHA64X2TZSXOBZ3PSEG9PAQ5F08HFV7NJXYM1VHTD6HONDBIGZR5XDNX3TTIBVTKIP9FC9VYPXOOIKPJWHFCJYUTDBWHR4YQYT4KLH59DW0ED0RNBCNTL6L1ZK0NPPN8VNNIJ34SLH248JY6BWX2PTMAUU4V9V436RVUT868660VAGEMC5GC403R0E9PYVG9VHTGTMAO1FJQTSNZP70YXNXQ6DJJG10MWVVMABBDISDYH6W61D3RNJWIBV7GH34894FBFRX6MU0Y4TB080AOJFEXM2KITB060K00NOTE80HZZ4B5PO1HFGD5XYDV07V0UCZ1HE448R6NFAILF5NBIEICJHJDOYTPCQSXV304K3MYXIRNY1C7LHB9YQ4RSM55EQUS4E2QLYAR756CN4M3GZAXOFH30T1NEINLG8BSEWYAF6UPK6404L3DX80CHRKH42M5R5YMAFF0WUDRJRTXSIDLFKPHYYTH0X6AUY4BXEHZRBUBXRCZJ92C7FISSOFTTLLXIGRFOZI7NSG1E2I2AQYBHKPWU5E2CVV2LYH7Y4XHZQ7C4P31KD5IRHZH0G5O16QAAYCM4S6DAYZOSSV5ORXS6HOPMCS4HM9AI9BE74ICGA3ON1Q1D2MID6XUZFNR09M3E84XZOJ60ZAKQXQ6OJRMZZ2ASA0HSF41XZPI70BYO89U3W0RXJHMCX72LNGKRPGRLQM688PUQHK4UFY1Q34GJ943C5RGFNPBN84SXUT4DJJ89V9IOC2GDLZTYW72USJWBUDLRBD7ZA1YECQTNF36RM4547VXW5PYSSR0BQP80525Y4I6HWL8UF7P44SS1TDA1SAIF73OBA7CGJ93QEPSKHFOJRONHFCZ1FZLNJHK8FYMRK4I1TU9RG4YH55AOWXC12WB82BRLJ5FVKCU4YWUBHK9ZLJHEI54LGSY8FR9A5AMA73UVZCO4KBDSYXIBI7L4SJ88S8D20VLYTF3UDKY04DNTL0P1Z67MSX803IQW79NL6Q1ZOD0CFHNHNS7V9BHFTD1P9Z7053OKHTZEYLS2HOD943CPJEIQP1ALE20UET65LIHDRV5E7WLFDHOQPED5BRN27NBFVX77Q2J7TVSBIP4WKHS9ODM9O6V7GKXDD9VU7KEVJZZ32GQQFH69B805JTIEG47QL8L3JU843KPVNC43VVG1GJMDJKTYGOTA9AXL510PVS9YC2DSBF413TKUBD9CGTL7KF6S3ZI84FYTRS1K1894PUVOGAIP4FKWPXL1GYTUSBQJZJXVDAW6G828FM7EYPP39MG4XD7N71P9RFYJKKMC0R596JL87Z3XV7SD38S1XBT5OHG0QLM6ZF4ZOX0H6XIFUFWSN70SJA2X0XTLLUPZRUNCBFXKT1IL68X851WA40AZDZLU9M1SJUS654ZQDOXZQ9LEJHX91IQKOOWMOU8L3MACX3QSA5W3OPOLCQ3TOAQ4OLIG9FDMR8KGXUUCO7TPPG8N48ZYU1XPIQTYKK1MTQDSC7IAS1UFX2AJ8VPM57DUS1U76X3H1WHH86R2WV08E8EEZRDRAVM4WIFB5GK3H670993WISY26CQIV6WCOJUOA2V6SFZ1HP33I6U5C6IIYLE2TG9WSIZ9NHHHIACLTQA3S2NSMENTVV888LHHQ9CCV8KKVI6WT714G1TZD38TRQFLJ4QQ0KGEDPRNG7O7KRKJJQ785LJEB1DET6EP2TSLEZ51VDOXYFYXXHQKDGMI5XTR0PL0D0WPHDOOIA9A7GO8HR4LLZ5K7SK9XL2VVODY6S7VMKAUFK9UJUIPBX5287OQI4YGF5XXWXZRN1EN40NHNH36E0S1KKG97ATG7PRUJMYTCFFYZ6XYB3I0D71CBJR4TEK9E8EF3LS7SJJRL1I6CYD47NMDIBAGS8EEB3TCDMII4STVKMLTH9ZYQHFZ0SV6DN7XJOZI1JQSIOZJD0O8F0QVNLZS22LG7YED2VYFX7R7XOPRSYDWAVP05ND3XK20JUMNVX01YTK1GVO42JMO0QIDLU6TEUS77OEEGZLCL4PC5ODRO6MTFQ8MGK3W4ZH9IW1F3IKXUE4OMSONWEOFRB61TDH2A30WLBL7YFG5VYC1V73IZTOY7P0N91MCZKGR4O6GEDTHE1GAP1ITTDYR5TKCT9JNGMS7I6E8O5VZ1PZ1GGR4MDYUM55F6CMDNKWLLTWOF3T1LM06I8EHS4LZ9IAWSTVA7GR2MK7C5WHJH3YPV35S05O33RLMMLUKW3NOAHSRLSOPTNMWQRKVF18RU1AQOBRK0X2IXWN2BILCN9678O566RI5BTBEG7M781CQA61DWD0XQ780W9UB8IKFKEGFAN2PLRQ05RG5RCFSH7MPEXN0X27OS0D124UQ5FN5SMKGPLSNFJYX1OAI63YSACPMKS7L5I4WSOMTF8ILTE7CW81E7OPLU7BJ2YG36RG9QLZ8REWL3K6VR0REO365HFEGG1VFJG5TY7625FYNEUC68MVSOPL2HO7XZS1GOTZVBXB7I1HLF0BGXK3NW3JSEVLYDP20AWXZTKI4C1JLX6WJM2X5YSKTNVANWOC7W85RUXY6IIAD5ZAMD3JWF60B4L49IFTLC95D91O4YH12174I6VGKJ94DNGBH83C9YMYZTSBKBL5JLUR7HF61Y7YTIQSQV2VBR7J5CXUTTJFG0L29LK4JLQ9V7W8IJ60X23INRFIP78V0MW5474X2HGDMV3I4NLZDIIS5SH3MJASPTHZ9JWXYDCFIZPSNBYI7LT34DS9WVEACY9A26OMBM4OU77YMPAT0GO676EW8655NRKFIETIACNY885ENB5HGA5ISA3Y8OW 51 | -------------------------------------------------------------------------------- /tests/dumps/quicklist_with_multiple_nodes.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/quicklist_with_multiple_nodes.rdb -------------------------------------------------------------------------------- /tests/dumps/quicklist_with_one_node.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/quicklist_with_one_node.rdb -------------------------------------------------------------------------------- /tests/dumps/rdb_version_5_with_checksum.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/rdb_version_5_with_checksum.rdb -------------------------------------------------------------------------------- /tests/dumps/regular_set.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/regular_set.rdb -------------------------------------------------------------------------------- /tests/dumps/regular_sorted_set.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/regular_sorted_set.rdb -------------------------------------------------------------------------------- /tests/dumps/sorted_set_as_ziplist.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/sorted_set_as_ziplist.rdb -------------------------------------------------------------------------------- /tests/dumps/uncompressible_string_keys.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/uncompressible_string_keys.rdb -------------------------------------------------------------------------------- /tests/dumps/ziplist_that_compresses_easily.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/ziplist_that_compresses_easily.rdb -------------------------------------------------------------------------------- /tests/dumps/ziplist_that_doesnt_compress.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/ziplist_that_doesnt_compress.rdb -------------------------------------------------------------------------------- /tests/dumps/ziplist_with_integers.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/ziplist_with_integers.rdb -------------------------------------------------------------------------------- /tests/dumps/zipmap_that_compresses_easily.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/zipmap_that_compresses_easily.rdb -------------------------------------------------------------------------------- /tests/dumps/zipmap_that_doesnt_compress.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/zipmap_that_doesnt_compress.rdb -------------------------------------------------------------------------------- /tests/dumps/zipmap_with_big_values.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/badboy/rdb-rs/16f6c652793ead44681e0aedacbf73c5dd891cf0/tests/dumps/zipmap_with_big_values.rdb -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | use pretty_assertions::assert_eq; 3 | use rdb::{self, filter, formatter}; 4 | use redis::Client; 5 | use rstest::rstest; 6 | use std::fs; 7 | use std::fs::File; 8 | use std::io::BufReader; 9 | use std::path::Path; 10 | use std::path::PathBuf; 11 | use testcontainers::ContainerAsync; 12 | use testcontainers_modules::{ 13 | redis::Redis, testcontainers::runners::AsyncRunner, testcontainers::ImageExt, 14 | }; 15 | 16 | fn run_dump_test(input: PathBuf, format: &str) -> String { 17 | let file_stem = input 18 | .file_stem() 19 | .expect("File should have a name") 20 | .to_string_lossy(); 21 | let temp_output = PathBuf::from(format!("/tmp/rdb_test_{}.{}", file_stem, format)); 22 | 23 | let file = fs::File::open(&input).expect("Failed to open dump file"); 24 | let reader = BufReader::new(file); 25 | match format { 26 | "json" => { 27 | let formatter = formatter::JSON::new(Some(temp_output.clone())); 28 | let filter = filter::Simple::new(); 29 | rdb::parse(reader, formatter, filter).expect("Failed to parse RDB file"); 30 | } 31 | "protocol" => { 32 | let formatter = formatter::Protocol::new(Some(temp_output.clone())); 33 | let filter = filter::Simple::new(); 34 | rdb::parse(reader, formatter, filter).expect("Failed to parse RDB file"); 35 | } 36 | "plain" => { 37 | let formatter = formatter::Plain::new(Some(temp_output.clone())); 38 | let filter = filter::Simple::new(); 39 | rdb::parse(reader, formatter, filter).expect("Failed to parse RDB file"); 40 | } 41 | _ => { 42 | panic!("Invalid format: {}", format); 43 | } 44 | } 45 | 46 | let actual = 47 | String::from_utf8_lossy(&fs::read(&temp_output).expect("Failed to read output file")) 48 | .into_owned(); 49 | 50 | fs::remove_file(&temp_output).ok(); 51 | 52 | actual 53 | } 54 | 55 | fn load_expected(path: PathBuf, format: &str) -> String { 56 | let file_stem = path 57 | .file_stem() 58 | .expect("File should have a name") 59 | .to_string_lossy(); 60 | let expected_json_path = format!("tests/dumps/{}/{}.{}", format, file_stem, format); 61 | fs::read_to_string(&expected_json_path).unwrap_or_else(|_| { 62 | String::from_utf8_lossy(&fs::read(&expected_json_path).expect("Failed to read file")) 63 | .into_owned() 64 | }) 65 | } 66 | 67 | #[rstest] 68 | #[case::json("json")] 69 | #[case::plain("plain")] 70 | #[case::protocol("protocol")] 71 | fn test_dump_matches_expected(#[files("tests/dumps/*.rdb")] path: PathBuf, #[case] format: &str) { 72 | let actual = run_dump_test(path.clone(), format); 73 | 74 | let expected = load_expected(path.clone(), format); 75 | 76 | assert_eq!( 77 | actual, 78 | expected, 79 | "Output doesn't match for {}", 80 | path.display() 81 | ); 82 | } 83 | 84 | async fn redis_client(major_version: u8, minor_version: u8) -> (Client, ContainerAsync) { 85 | let container = Redis::default() 86 | .with_tag(format!("{}.{}-alpine", major_version, minor_version)) 87 | .start() 88 | .await 89 | .expect("Failed to start Redis container"); 90 | 91 | let host_ip = container.get_host().await.unwrap(); 92 | let host_port = container.get_host_port_ipv4(6379).await.unwrap(); 93 | let url = format!("redis://{}:{}", host_ip, host_port); 94 | let client = Client::open(url).expect("Failed to create Redis client"); 95 | 96 | (client, container) 97 | } 98 | 99 | fn to_resp_format(command: &str, args: &[&str]) -> String { 100 | let mut resp = format!("*{}\r\n", args.len() + 1); // +1 for command itself 101 | resp.push_str(&format!("${}\r\n{}\r\n", command.len(), command)); 102 | for arg in args { 103 | resp.push_str(&format!("${}\r\n{}\r\n", arg.len(), arg)); 104 | } 105 | resp 106 | } 107 | 108 | async fn execute_commands(conn: &mut redis::Connection, commands: &[(&str, Vec<&str>)]) -> String { 109 | let mut resp = String::from("*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n"); // Initial SELECT command 110 | 111 | for (cmd, args) in commands { 112 | // Execute the command 113 | let mut redis_cmd = redis::cmd(cmd); 114 | for arg in args { 115 | redis_cmd.arg(arg); 116 | } 117 | redis_cmd.exec(conn).unwrap(); 118 | 119 | // Add RESP representation 120 | resp.push_str(&to_resp_format(cmd, args)); 121 | } 122 | 123 | resp 124 | } 125 | 126 | fn parse_rdb_to_resp(rdb_path: &Path) -> String { 127 | let rdb_file = File::open(rdb_path).unwrap(); 128 | let rdb_reader = BufReader::new(rdb_file); 129 | let tmp_file = tempfile::NamedTempFile::new().unwrap(); 130 | 131 | rdb::parse( 132 | rdb_reader, 133 | rdb::formatter::Protocol::new(Some(tmp_file.path().to_path_buf())), 134 | rdb::filter::Simple::new(), 135 | ) 136 | .unwrap(); 137 | 138 | let output = std::fs::read(tmp_file.path()).unwrap(); 139 | 140 | String::from_utf8(output).unwrap() 141 | } 142 | 143 | fn split_resp_commands(resp: &str) -> Vec { 144 | // Skip the initial SELECT command 145 | let mut commands: Vec = resp 146 | .split("*") 147 | .filter(|s| !s.is_empty()) 148 | .map(|s| format!("*{}", s)) 149 | .collect(); 150 | 151 | // Remove the SELECT command if it exists 152 | if !commands.is_empty() && commands[0].contains("SELECT") { 153 | commands.remove(0); 154 | } 155 | 156 | commands 157 | } 158 | 159 | #[rstest] 160 | #[case::redis_6_2(6, 2)] 161 | #[case::redis_7_0(7, 0)] 162 | #[case::redis_7_2(7, 2)] 163 | #[case::redis_7_4(7, 4)] 164 | #[tokio::test] 165 | async fn test_redis_protocol_reproducibility(#[case] major_version: u8, #[case] minor_version: u8) { 166 | let (client, container) = redis_client(major_version, minor_version).await; 167 | let mut conn = client.get_connection().unwrap(); 168 | 169 | let commands = vec![ 170 | ("SET", vec!["string", "bar"]), 171 | ("HSET", vec!["hash", "name", "John"]), 172 | ("HSET", vec!["hash", "age", "25"]), 173 | ("SADD", vec!["set_strings", "Earth"]), 174 | ("SADD", vec!["set_strings", "Mars"]), 175 | ("SADD", vec!["set_strings", "Jupiter"]), 176 | ("SADD", vec!["set_numbers", "1"]), 177 | ("SADD", vec!["set_numbers", "2"]), 178 | ("SADD", vec!["set_numbers", "3"]), 179 | ("ZADD", vec!["sorted_set", "1", "a"]), 180 | ("ZADD", vec!["sorted_set", "2", "b"]), 181 | ("ZADD", vec!["sorted_set", "3", "c"]), 182 | ("RPUSH", vec!["list_strings", "Rex"]), 183 | ("RPUSH", vec!["list_strings", "Bella"]), 184 | ("RPUSH", vec!["list_strings", "Max"]), 185 | ("RPUSH", vec!["list_numbers", "1"]), 186 | ("RPUSH", vec!["list_numbers", "2"]), 187 | ("RPUSH", vec!["list_numbers", "3"]), 188 | ]; 189 | 190 | let expected_resp = execute_commands(&mut conn, &commands).await; 191 | redis::cmd("SAVE").exec(&mut conn).unwrap(); 192 | tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; 193 | 194 | let container_id = container.id(); 195 | let temp_file = tempfile::NamedTempFile::new().unwrap(); 196 | 197 | let status = std::process::Command::new("docker") 198 | .args([ 199 | "cp", 200 | "-q", 201 | &format!("{}:/data/dump.rdb", container_id), 202 | temp_file.path().to_str().unwrap(), 203 | ]) 204 | .status() 205 | .expect("Failed to execute docker cp"); 206 | 207 | assert!(status.success(), "docker cp command failed"); 208 | 209 | let actual_resp = parse_rdb_to_resp(temp_file.path()); 210 | 211 | let expected_commands: std::collections::HashSet<_> = 212 | split_resp_commands(&expected_resp).into_iter().collect(); 213 | let actual_commands: std::collections::HashSet<_> = 214 | split_resp_commands(&actual_resp).into_iter().collect(); 215 | 216 | assert_eq!(actual_commands, expected_commands); 217 | } 218 | 219 | #[rstest] 220 | fn test_cli_commands_succeed( 221 | #[files("tests/dumps/*.rdb")] path: PathBuf, 222 | #[values("json", "plain", "protocol")] format: &str, 223 | #[values("", "1")] databases: &str, 224 | #[values("", "hash", "set", "list", "sortedset", "string")] types: &str, 225 | ) { 226 | let mut cmd = Command::cargo_bin("rdb").unwrap(); 227 | 228 | cmd.args(["--format", format]); 229 | 230 | if !databases.is_empty() { 231 | cmd.args(["--databases", databases]); 232 | } 233 | 234 | if !types.is_empty() { 235 | cmd.args(["--type", types]); 236 | } 237 | 238 | cmd.arg(&path).assert().success(); 239 | } 240 | -------------------------------------------------------------------------------- /utils/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Jan-Erik Rediger 2 | 3 | pkgname=rdb-rs-git 4 | pkgver=r109.9c9c292 5 | pkgrel=1 6 | pkgdesc='fast and efficient RDB parsing utility' 7 | arch=('i686' 'x86_64') 8 | url='http://rdb.fnordig.de/' 9 | license=('BSD') 10 | provides=('rdb') 11 | depends=('gcc-libs') 12 | makedepends=('git' 'rust') 13 | options=('docs' '!strip' 'debug') 14 | source=('git+https://github.com/badboy/rdb-rs') 15 | sha1sums=('SKIP') 16 | 17 | _gitname='rdb-rs' 18 | 19 | pkgver() { 20 | cd "$_gitname" 21 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 22 | } 23 | 24 | build() { 25 | cd "$_gitname" 26 | make build-release 27 | } 28 | 29 | package() { 30 | cd "$_gitname" 31 | 32 | make PREFIX="$pkgdir/usr" install 33 | 34 | install -Dm644 LICENSE \ 35 | ${pkgdir}/usr/share/licenses/${pkgname}/LICENSE 36 | } 37 | 38 | # vim:set ts=2 sw=2 et: 39 | 40 | --------------------------------------------------------------------------------