Repository: krojew/cdrs-tokio
Branch: master
Commit: 27d38d2ad9bb
Files: 155
Total size: 988.6 KB
Directory structure:
gitextract_xk4fwcia/
├── .github/
│ ├── FUNDING.yml
│ ├── dependabot.yml
│ ├── stale.yml
│ └── workflows/
│ └── rust.yml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── cassandra-ports.txt
├── cassandra-protocol/
│ ├── Cargo.toml
│ ├── README.md
│ └── src/
│ ├── authenticators.rs
│ ├── compression.rs
│ ├── consistency.rs
│ ├── crc.rs
│ ├── error.rs
│ ├── events.rs
│ ├── frame/
│ │ ├── events.rs
│ │ ├── frame_decoder.rs
│ │ ├── frame_encoder.rs
│ │ ├── message_auth_challenge.rs
│ │ ├── message_auth_response.rs
│ │ ├── message_auth_success.rs
│ │ ├── message_authenticate.rs
│ │ ├── message_batch.rs
│ │ ├── message_error.rs
│ │ ├── message_event.rs
│ │ ├── message_execute.rs
│ │ ├── message_options.rs
│ │ ├── message_prepare.rs
│ │ ├── message_query.rs
│ │ ├── message_ready.rs
│ │ ├── message_register.rs
│ │ ├── message_request.rs
│ │ ├── message_response.rs
│ │ ├── message_result.rs
│ │ ├── message_startup.rs
│ │ ├── message_supported.rs
│ │ └── traits.rs
│ ├── frame.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── query/
│ │ ├── batch_query_builder.rs
│ │ ├── prepare_flags.rs
│ │ ├── prepared_query.rs
│ │ ├── query_flags.rs
│ │ ├── query_params.rs
│ │ ├── query_params_builder.rs
│ │ ├── query_values.rs
│ │ └── utils.rs
│ ├── query.rs
│ ├── token.rs
│ ├── types/
│ │ ├── blob.rs
│ │ ├── cassandra_type.rs
│ │ ├── data_serialization_types.rs
│ │ ├── decimal.rs
│ │ ├── duration.rs
│ │ ├── from_cdrs.rs
│ │ ├── list.rs
│ │ ├── map.rs
│ │ ├── rows.rs
│ │ ├── tuple.rs
│ │ ├── udt.rs
│ │ ├── value.rs
│ │ └── vector.rs
│ └── types.rs
├── cdrs-tokio/
│ ├── Cargo.toml
│ ├── examples/
│ │ ├── README.md
│ │ ├── crud_operations.rs
│ │ ├── generic_connection.rs
│ │ ├── insert_collection.rs
│ │ ├── multiple_thread.rs
│ │ ├── paged_query.rs
│ │ └── prepare_batch_execute.rs
│ ├── src/
│ │ ├── cluster/
│ │ │ ├── cluster_metadata_manager.rs
│ │ │ ├── config_proxy.rs
│ │ │ ├── config_rustls.rs
│ │ │ ├── config_tcp.rs
│ │ │ ├── connection_manager.rs
│ │ │ ├── connection_pool.rs
│ │ │ ├── control_connection.rs
│ │ │ ├── keyspace_holder.rs
│ │ │ ├── metadata_builder.rs
│ │ │ ├── node_address.rs
│ │ │ ├── node_info.rs
│ │ │ ├── pager.rs
│ │ │ ├── rustls_connection_manager.rs
│ │ │ ├── send_envelope.rs
│ │ │ ├── session.rs
│ │ │ ├── session_context.rs
│ │ │ ├── tcp_connection_manager.rs
│ │ │ ├── token_map.rs
│ │ │ ├── topology/
│ │ │ │ ├── cluster_metadata.rs
│ │ │ │ ├── datacenter_metadata.rs
│ │ │ │ ├── keyspace_metadata.rs
│ │ │ │ ├── node.rs
│ │ │ │ ├── node_distance.rs
│ │ │ │ ├── node_state.rs
│ │ │ │ └── replication_strategy.rs
│ │ │ └── topology.rs
│ │ ├── cluster.rs
│ │ ├── envelope_parser.rs
│ │ ├── frame_encoding.rs
│ │ ├── future.rs
│ │ ├── lib.rs
│ │ ├── load_balancing/
│ │ │ ├── initializing_wrapper.rs
│ │ │ ├── node_distance_evaluator.rs
│ │ │ ├── random.rs
│ │ │ ├── request.rs
│ │ │ ├── round_robin.rs
│ │ │ └── topology_aware.rs
│ │ ├── load_balancing.rs
│ │ ├── macros.rs
│ │ ├── retry/
│ │ │ ├── reconnection_policy.rs
│ │ │ └── retry_policy.rs
│ │ ├── retry.rs
│ │ ├── speculative_execution.rs
│ │ ├── statement/
│ │ │ ├── statement_params.rs
│ │ │ └── statement_params_builder.rs
│ │ ├── statement.rs
│ │ └── transport.rs
│ └── tests/
│ ├── collection_types.rs
│ ├── common.rs
│ ├── compression.rs
│ ├── derive_traits.rs
│ ├── keyspace.rs
│ ├── multi_node_speculative_execution.rs
│ ├── multithread.rs
│ ├── native_types.rs
│ ├── paged_query.rs
│ ├── query_values.rs
│ ├── single_node_speculative_execution.rs
│ ├── topology_aware.rs
│ ├── tuple_types.rs
│ └── user_defined_types.rs
├── cdrs-tokio-helpers-derive/
│ ├── Cargo.toml
│ ├── README.md
│ └── src/
│ ├── common.rs
│ ├── db_mirror.rs
│ ├── into_cdrs_value.rs
│ ├── lib.rs
│ ├── try_from_row.rs
│ └── try_from_udt.rs
├── changelog.md
├── clippy.toml
├── documentation/
│ ├── README.md
│ ├── batching-multiple-queries.md
│ ├── cdrs-session.md
│ ├── cluster-configuration.md
│ ├── deserialization.md
│ ├── preparing-and-executing-queries.md
│ ├── query-values.md
│ └── type-mapping.md
└── rustfmt.toml
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: krojew
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
================================================
FILE: .github/workflows/rust.yml
================================================
name: Continuous integration
on: [ push, pull_request ]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test Suite
runs-on: ubuntu-latest
services:
cassandra:
image: cassandra
ports:
- 9042:9042
steps:
- uses: actions/checkout@v2
- name: Install minimal toolchain with clippy and rustfmt
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt, clippy
- name: Run tests
# test threads must be one because else database tests will run in parallel and will result in flaky tests
run: cargo test --all-features --verbose -- --test-threads=1
- name: Format check
run: cargo fmt --all -- --check
# Ensure that all targets compile and pass clippy checks under every possible combination of features
- name: Clippy check
run: cargo install cargo-hack && cargo hack --feature-powerset clippy --locked --release
================================================
FILE: .gitignore
================================================
Cargo.lock
target
*.bk
.idea/
cdrs.iml
.vscode/
================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
"cassandra-protocol",
"cdrs-tokio",
"cdrs-tokio-helpers-derive"
]
[workspace.dependencies]
arc-swap = "1.7.1"
uuid = "1.19.0"
derivative = "2.2.0"
derive_more = { version = "2.1.0", features = ["constructor", "display"] }
itertools = "0.14.0"
thiserror = "2.0.17"
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 CDRS Project Developers
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LICENSE-MIT
================================================
The MIT License (MIT)
Copyright (c) 2016 CDRS Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# CDRS tokio
[](https://crates.io/crates/cdrs-tokio) 

CDRS is production-ready Apache **C**assandra **d**river written in pure **R**u*
*s**t. Focuses on providing high
level of configurability to suit most use cases at any scale, as its Java
counterpart, while also leveraging the
safety and performance of Rust.
## Features
- Asynchronous API;
- TCP/TLS connection (rustls);
- Topology-aware dynamic and configurable load balancing;
- Configurable connection strategies and pools;
- Configurable speculative execution;
- LZ4, Snappy compression;
- Cassandra-to-Rust data serialization/deserialization with custom type support;
- Pluggable authentication strategies;
- [ScyllaDB](https://www.scylladb.com/) support;
- Server events listening;
- Multiple CQL version support (3, 4, 5), full spec implementation;
- Query tracing information;
- Prepared statements;
- Query paging;
- Batch statements;
- Configurable retry and reconnection policy;
- Support for interleaved queries;
- Support for Yugabyte YCQL JSONB;
- Support for beta protocol usage;
## Performance
Due to high configurability of **CDRS**, the performance will vary depending on
use case. The following benchmarks
have been made against the latest (master as of 03-12-2021) versions of
respective libraries (except
cassandra-cpp: 2.16.0) and protocol version 4.
- `cdrs-tokio-large-pool` - **CDRS** with node connection pool equal to double
of physical CPU cores
- `cdrs-tokio-small-pool` - **CDRS** with a single connection per node
- `scylladb-rust-large-pool` - `scylla` crate with node connection pool equal to
double of physical CPU cores
- `scylladb-rust-small-pool` - `scylla` crate with a single connection per node
- `cassandra-cpp` - Rust bindings for Datastax C++ Driver, running on multiple
threads using Tokio
- `gocql` - a driver written in Go
Knowing given use case, CDRS can be optimized for peak performance.
## Documentation and examples
- [User guide](./documentation).
- [Examples](./cdrs-tokio/examples).
- [API docs](https://docs.rs/cdrs-tokio/latest/cdrs_tokio/).
- Using ScyllaDB with
RUST [lesson](https://university.scylladb.com/courses/using-scylla-drivers/lessons/rust-and-scylla/).
## Getting started
This example configures a cluster consisting of a single node without
authentication, and uses round-robin
load balancing. Other options are kept as default.
```rust
use cdrs_tokio::cluster::session::{TcpSessionBuilder, SessionBuilder};
use cdrs_tokio::cluster::NodeTcpConfigBuilder;
use cdrs_tokio::load_balancing::RoundRobinLoadBalancingStrategy;
use cdrs_tokio::query::*;
#[tokio::main]
async fn main() {
let cluster_config = NodeTcpConfigBuilder::new()
.with_contact_point("127.0.0.1:9042".into())
.build()
.await
.unwrap();
let session = TcpSessionBuilder::new(RoundRobinLoadBalancingStrategy::new(), cluster_config)
.build()
.await
.unwrap();
let create_ks = "CREATE KEYSPACE IF NOT EXISTS test_ks WITH REPLICATION = { \
'class' : 'SimpleStrategy', 'replication_factor' : 1 };";
session
.query(create_ks)
.await
.expect("Keyspace create error");
}
```
## License
This project is licensed under either of
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)
or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
- MIT license ([LICENSE-MIT](LICENSE-MIT)
or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
at your option.
================================================
FILE: cassandra-ports.txt
================================================
Cassandra ports:
* 7199 - JMX (was 8080 pre Cassandra 0.8.xx)
* 7000 - Internode communication (not used if TLS enabled)
* 7001 - TLS Internode communication (used if TLS enabled)
* 9160 - Thrift client API
* 9042 - CQL native transport port
================================================
FILE: cassandra-protocol/Cargo.toml
================================================
[package]
name = "cassandra-protocol"
version = "4.0.0"
authors = ["Alex Pikalov ", "Kamil Rojewski "]
edition = "2018"
description = "Cassandra protocol implementation"
documentation = "https://docs.rs/cassandra-protocol"
homepage = "https://github.com/krojew/cdrs-tokio"
repository = "https://github.com/krojew/cdrs-tokio"
keywords = ["cassandra", "client", "cassandradb"]
license = "MIT/Apache-2.0"
categories = ["asynchronous", "database"]
rust-version = "1.74"
[features]
e2e-tests = []
[dependencies]
arc-swap.workspace = true
bitflags = "2.10.0"
bytes = "1.11.0"
chrono = { version = "0.4.31", default-features = false, features = ["std"] }
crc32fast = "1.5.0"
derivative.workspace = true
derive_more.workspace = true
float_eq = "1.0.1"
integer-encoding = "4.1.0"
itertools.workspace = true
num-bigint = "0.4.1"
lz4_flex = "0.13.0"
snap = "1.1.0"
thiserror.workspace = true
time = { version = "0.3.29", features = ["macros"] }
uuid.workspace = true
================================================
FILE: cassandra-protocol/README.md
================================================
# Cassandra protocol [](https://crates.io/crates/cassandra-protocol) 
**Cassandra** low-level protocol implementation, written in Rust.
If you wish to use **Cassandra** without dealing with protocol-level details, consider a high-level crate
like **[cdrs-tokio](https://crates.io/crates/cdrs-tokio)**.
================================================
FILE: cassandra-protocol/src/authenticators.rs
================================================
use crate::error::Result;
use crate::types::CBytes;
/// Handles SASL authentication.
///
/// The lifecycle of an authenticator consists of:
/// - The `initial_response` function will be called. The initial return value will be sent to the
/// server to initiate the handshake.
/// - The server will respond to each client response by either issuing a challenge or indicating
/// that the authentication is complete (successfully or not). If a new challenge is issued,
/// the authenticator's `evaluate_challenge` function will be called to produce a response
/// that will be sent to the server. This challenge/response negotiation will continue until
/// the server responds that authentication is successful or an error is raised.
/// - On success, the `handle_success` will be called with data returned by the server.
pub trait SaslAuthenticator {
fn initial_response(&self) -> CBytes;
fn evaluate_challenge(&self, challenge: CBytes) -> Result;
fn handle_success(&self, data: CBytes) -> Result<()>;
}
/// Provides authenticators per new connection.
pub trait SaslAuthenticatorProvider {
fn name(&self) -> Option<&str>;
fn create_authenticator(&self) -> Box;
}
#[derive(Debug, Clone)]
pub struct StaticPasswordAuthenticator {
username: String,
password: String,
}
impl StaticPasswordAuthenticator {
pub fn new(username: S, password: S) -> StaticPasswordAuthenticator {
StaticPasswordAuthenticator {
username: username.to_string(),
password: password.to_string(),
}
}
}
impl SaslAuthenticator for StaticPasswordAuthenticator {
fn initial_response(&self) -> CBytes {
let mut token = vec![0];
token.extend_from_slice(self.username.as_bytes());
token.push(0);
token.extend_from_slice(self.password.as_bytes());
CBytes::new(token)
}
fn evaluate_challenge(&self, _challenge: CBytes) -> Result {
Err("Server challenge is not supported for StaticPasswordAuthenticator!".into())
}
fn handle_success(&self, _data: CBytes) -> Result<()> {
Ok(())
}
}
/// Authentication provider with a username and password.
#[derive(Debug, Clone)]
pub struct StaticPasswordAuthenticatorProvider {
username: String,
password: String,
}
impl SaslAuthenticatorProvider for StaticPasswordAuthenticatorProvider {
fn name(&self) -> Option<&str> {
Some("org.apache.cassandra.auth.PasswordAuthenticator")
}
fn create_authenticator(&self) -> Box {
Box::new(StaticPasswordAuthenticator::new(
self.username.clone(),
self.password.clone(),
))
}
}
impl StaticPasswordAuthenticatorProvider {
pub fn new(username: S, password: S) -> Self {
StaticPasswordAuthenticatorProvider {
username: username.to_string(),
password: password.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct NoneAuthenticator;
impl SaslAuthenticator for NoneAuthenticator {
fn initial_response(&self) -> CBytes {
CBytes::new(vec![0])
}
fn evaluate_challenge(&self, _challenge: CBytes) -> Result {
Err("Server challenge is not supported for NoneAuthenticator!".into())
}
fn handle_success(&self, _data: CBytes) -> Result<()> {
Ok(())
}
}
/// Provider for no authentication.
#[derive(Debug, Clone)]
pub struct NoneAuthenticatorProvider;
impl SaslAuthenticatorProvider for NoneAuthenticatorProvider {
fn name(&self) -> Option<&str> {
None
}
fn create_authenticator(&self) -> Box {
Box::new(NoneAuthenticator)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_static_password_authenticator_new() {
StaticPasswordAuthenticator::new("foo", "bar");
}
#[test]
fn test_static_password_authenticator_cassandra_name() {
let auth = StaticPasswordAuthenticatorProvider::new("foo", "bar");
assert_eq!(
auth.name(),
Some("org.apache.cassandra.auth.PasswordAuthenticator")
);
}
#[test]
fn test_authenticator_none_cassandra_name() {
let auth = NoneAuthenticator;
let provider = NoneAuthenticatorProvider;
assert_eq!(provider.name(), None);
assert_eq!(auth.initial_response().into_bytes().unwrap(), vec![0]);
}
}
================================================
FILE: cassandra-protocol/src/compression.rs
================================================
/// CDRS support traffic compression as it is described in [Apache
/// Cassandra protocol](
/// https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v4.spec#L790)
///
/// Before being used, client and server must agree on a compression algorithm to
/// use, which is done in the STARTUP message. As a consequence, a STARTUP message
/// must never be compressed. However, once the STARTUP envelope has been received
/// by the server, messages can be compressed (including the response to the STARTUP
/// request).
use derive_more::Display;
use snap::raw::{Decoder, Encoder};
use std::convert::{From, TryInto};
use std::error::Error;
use std::fmt;
use std::io;
use std::result;
type Result = result::Result;
pub const LZ4: &str = "lz4";
pub const SNAPPY: &str = "snappy";
/// An error which may occur during encoding or decoding frame body. As there are only two types
/// of compressors it contains two related enum options.
#[derive(Debug)]
pub enum CompressionError {
/// Snappy error.
Snappy(snap::Error),
/// Lz4 error.
Lz4(io::Error),
}
impl fmt::Display for CompressionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CompressionError::Snappy(ref err) => write!(f, "Snappy Error: {err:?}"),
CompressionError::Lz4(ref err) => write!(f, "Lz4 Error: {err:?}"),
}
}
}
impl Error for CompressionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
CompressionError::Snappy(ref err) => Some(err),
CompressionError::Lz4(ref err) => Some(err),
}
}
}
impl Clone for CompressionError {
fn clone(&self) -> Self {
match self {
CompressionError::Snappy(error) => CompressionError::Snappy(error.clone()),
CompressionError::Lz4(error) => CompressionError::Lz4(io::Error::new(
error.kind(),
error
.get_ref()
.map(|error| error.to_string())
.unwrap_or_default(),
)),
}
}
}
/// Enum which represents a type of compression. Only non-startup frame's body can be compressed.
#[derive(Debug, PartialEq, Clone, Copy, Eq, Ord, PartialOrd, Hash, Display)]
pub enum Compression {
/// [lz4](https://code.google.com/p/lz4/) compression
Lz4,
/// [snappy](https://code.google.com/p/snappy/) compression
Snappy,
/// No compression
None,
}
impl Compression {
/// It encodes `bytes` basing on type of `Compression`..
///
/// # Examples
///
/// ```
/// use cassandra_protocol::compression::Compression;
///
/// let snappy_compression = Compression::Snappy;
/// let bytes = String::from("Hello World").into_bytes().to_vec();
/// let encoded = snappy_compression.encode(&bytes).unwrap();
/// assert_eq!(snappy_compression.decode(encoded).unwrap(), bytes);
///
/// ```
pub fn encode(&self, bytes: &[u8]) -> Result> {
match *self {
Compression::Lz4 => Compression::encode_lz4(bytes),
Compression::Snappy => Compression::encode_snappy(bytes),
Compression::None => Ok(bytes.into()),
}
}
/// Checks if current compression actually compresses data.
#[inline]
pub fn is_compressed(self) -> bool {
self != Compression::None
}
/// It decodes `bytes` basing on type of compression.
pub fn decode(&self, bytes: Vec) -> Result> {
match *self {
Compression::Lz4 => Compression::decode_lz4(bytes),
Compression::Snappy => Compression::decode_snappy(bytes),
Compression::None => Ok(bytes),
}
}
/// It transforms compression method into a `&str`.
pub fn as_str(&self) -> Option<&'static str> {
match *self {
Compression::Lz4 => Some(LZ4),
Compression::Snappy => Some(SNAPPY),
Compression::None => None,
}
}
fn encode_snappy(bytes: &[u8]) -> Result> {
let mut encoder = Encoder::new();
encoder
.compress_vec(bytes)
.map_err(CompressionError::Snappy)
}
fn decode_snappy(bytes: Vec) -> Result> {
let mut decoder = Decoder::new();
decoder
.decompress_vec(bytes.as_slice())
.map_err(CompressionError::Snappy)
}
fn encode_lz4(bytes: &[u8]) -> Result> {
let len = 4 + lz4_flex::block::get_maximum_output_size(bytes.len());
assert!(len <= i32::MAX as usize);
let mut result = vec![0; len];
let len = bytes.len() as i32;
result[..4].copy_from_slice(&len.to_be_bytes());
let compressed_len = lz4_flex::compress_into(bytes, &mut result[4..])
.map_err(|error| CompressionError::Lz4(io::Error::other(error)))?;
result.truncate(4 + compressed_len);
Ok(result)
}
fn decode_lz4(bytes: Vec) -> Result> {
// lz4 wire format prepends a 4-byte big-endian uncompressed length so
// the decoder knows how much memory to allocate. Validate length before
// slicing to avoid panics on truncated input.
if bytes.len() < 4 {
return Err(CompressionError::Lz4(io::Error::new(
io::ErrorKind::UnexpectedEof,
"lz4 payload missing 4-byte uncompressed length header",
)));
}
let uncompressed_size = i32::from_be_bytes(
bytes[..4]
.try_into()
.map_err(|error| CompressionError::Lz4(io::Error::other(error)))?,
);
// a negative size is impossible for a real payload; without this check
// the `as usize` cast would silently turn it into ~2 GB+ and ask
// lz4_flex to allocate a buffer that size before any decoding begins.
if uncompressed_size < 0 {
return Err(CompressionError::Lz4(io::Error::new(
io::ErrorKind::InvalidData,
format!("negative uncompressed size {uncompressed_size}"),
)));
}
lz4_flex::decompress(&bytes[4..], uncompressed_size as usize)
.map_err(|error| CompressionError::Lz4(io::Error::other(error)))
}
}
impl From for Compression {
/// It converts `String` into `Compression`. If string is neither `lz4` nor `snappy` then
/// `Compression::None` will be returned
fn from(compression_string: String) -> Compression {
Compression::from(compression_string.as_str())
}
}
impl Compression {
/// It converts `Compression` into `String`. If compression is `None` then empty string will be
/// returned
pub fn to_protocol_string(self) -> String {
match self {
Compression::Lz4 => "LZ4".to_string(),
Compression::Snappy => "SNAPPY".to_string(),
Compression::None => "NONE".to_string(),
}
}
pub fn from_protocol_string(protocol_string: &str) -> std::result::Result {
match protocol_string {
"lz4" | "LZ4" => Ok(Compression::Lz4),
"snappy" | "SNAPPY" => Ok(Compression::Snappy),
"none" | "NONE" => Ok(Compression::None),
_ => Err("Unknown compression".to_string()),
}
}
}
impl<'a> From<&'a str> for Compression {
/// It converts `str` into `Compression`. If string is neither `lz4` nor `snappy` then
/// `Compression::None` will be returned
fn from(compression_str: &'a str) -> Compression {
match compression_str {
LZ4 => Compression::Lz4,
SNAPPY => Compression::Snappy,
_ => Compression::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compression_to_protocol_string() {
let lz4 = Compression::Lz4;
assert_eq!("LZ4", lz4.to_protocol_string());
let snappy = Compression::Snappy;
assert_eq!("SNAPPY", snappy.to_protocol_string());
let none = Compression::None;
assert_eq!("NONE", none.to_protocol_string());
}
#[test]
fn test_compression_from_protocol_str() {
let lz4 = "lz4";
assert_eq!(
Compression::from_protocol_string(lz4).unwrap(),
Compression::Lz4
);
let lz4 = "LZ4";
assert_eq!(
Compression::from_protocol_string(lz4).unwrap(),
Compression::Lz4
);
let snappy = "snappy";
assert_eq!(
Compression::from_protocol_string(snappy).unwrap(),
Compression::Snappy
);
let snappy = "SNAPPY";
assert_eq!(
Compression::from_protocol_string(snappy).unwrap(),
Compression::Snappy
);
let none = "none";
assert_eq!(
Compression::from_protocol_string(none).unwrap(),
Compression::None
);
let none = "NONE";
assert_eq!(
Compression::from_protocol_string(none).unwrap(),
Compression::None
);
}
#[test]
fn test_compression_from_string() {
let lz4 = "lz4".to_string();
assert_eq!(Compression::from(lz4), Compression::Lz4);
let snappy = "snappy".to_string();
assert_eq!(Compression::from(snappy), Compression::Snappy);
let none = "x".to_string();
assert_eq!(Compression::from(none), Compression::None);
}
#[test]
fn test_compression_encode_snappy() {
let snappy_compression = Compression::Snappy;
let bytes = String::from("Hello World").into_bytes().to_vec();
snappy_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_snappy() {
let snappy_compression = Compression::Snappy;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = snappy_compression.encode(&bytes).unwrap();
assert_eq!(snappy_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_encode_lz4() {
let snappy_compression = Compression::Lz4;
let bytes = String::from("Hello World").into_bytes().to_vec();
snappy_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_lz4() {
let lz4_compression = Compression::Lz4;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = lz4_compression.encode(&bytes).unwrap();
assert_eq!(lz4_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_encode_none() {
let none_compression = Compression::None;
let bytes = String::from("Hello World").into_bytes().to_vec();
none_compression
.encode(&bytes)
.expect("Should work without exceptions");
}
#[test]
fn test_compression_decode_none() {
let none_compression = Compression::None;
let bytes = String::from("Hello World").into_bytes().to_vec();
let encoded = none_compression.encode(&bytes).unwrap();
assert_eq!(none_compression.decode(encoded).unwrap(), bytes);
}
#[test]
fn test_compression_decode_lz4_with_invalid_input() {
let lz4_compression = Compression::Lz4;
let decode = lz4_compression.decode(vec![0, 0, 0, 0x7f]);
assert!(decode.is_err());
}
#[test]
fn test_compression_decode_lz4_short_input_is_error_not_panic() {
// the lz4 wire format prepends a 4-byte big-endian uncompressed size;
// a payload shorter than that header must surface as an error rather
// than panicking on `bytes[..4]` slicing.
let lz4_compression = Compression::Lz4;
assert!(lz4_compression.decode(vec![]).is_err());
assert!(lz4_compression.decode(vec![1, 2, 3]).is_err());
}
#[test]
fn test_compression_decode_lz4_negative_size_is_error_not_oom() {
// a negative i32 uncompressed length cast through `as usize` becomes
// a huge value (~2 GB+) and would otherwise hand lz4_flex an absurd
// allocation request - guard against that.
let lz4_compression = Compression::Lz4;
// -1 in big-endian i32 followed by a dummy compressed byte
let bytes = vec![0xff, 0xff, 0xff, 0xff, 0];
assert!(lz4_compression.decode(bytes).is_err());
}
#[test]
fn test_compression_encode_snappy_with_non_utf8() {
let snappy_compression = Compression::Snappy;
let v = vec![0xff, 0xff];
let encoded = snappy_compression
.encode(&v)
.expect("Should work without exceptions");
assert_eq!(snappy_compression.decode(encoded).unwrap(), v);
}
}
================================================
FILE: cassandra-protocol/src/consistency.rs
================================================
#![warn(missing_docs)]
//! The module contains Rust representation of Cassandra consistency levels.
use crate::error;
use crate::frame::{FromBytes, FromCursor, Serialize, Version};
use crate::types::*;
use derive_more::Display;
use std::convert::{From, TryFrom, TryInto};
use std::default::Default;
use std::io;
use std::str::FromStr;
/// `Consistency` is an enum which represents Cassandra's consistency levels.
/// To find more details about each consistency level please refer to the following documentation:
///
#[derive(Debug, PartialEq, Clone, Copy, Display, Ord, PartialOrd, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Consistency {
/// Closest replica, as determined by the snitch.
/// If all replica nodes are down, write succeeds after a hinted handoff.
/// Provides low latency, guarantees writes never fail.
/// Note: this consistency level can only be used for writes.
/// It provides the lowest consistency and the highest availability.
Any,
///
/// A write must be written to the commit log and memtable of at least one replica node.
/// Satisfies the needs of most users because consistency requirements are not stringent.
#[default]
One,
/// A write must be written to the commit log and memtable of at least two replica nodes.
/// Similar to ONE.
Two,
/// A write must be written to the commit log and memtable of at least three replica nodes.
/// Similar to TWO.
Three,
/// A write must be written to the commit log and memtable on a quorum of replica nodes.
/// Provides strong consistency if you can tolerate some level of failure.
Quorum,
/// A write must be written to the commit log and memtable on all replica nodes in the cluster
/// for that partition key.
/// Provides the highest consistency and the lowest availability of any other level.
All,
/// Strong consistency. A write must be written to the commit log and memtable on a quorum
/// of replica nodes in the same data center as thecoordinator node.
/// Avoids latency of inter-data center communication.
/// Used in multiple data center clusters with a rack-aware replica placement strategy,
/// such as NetworkTopologyStrategy, and a properly configured snitch.
/// Use to maintain consistency locally (within the single data center).
/// Can be used with SimpleStrategy.
LocalQuorum,
/// Strong consistency. A write must be written to the commit log and memtable on a quorum of
/// replica nodes in all data center.
/// Used in multiple data center clusters to strictly maintain consistency at the same level
/// in each data center. For example, choose this level
/// if you want a read to fail when a data center is down and the QUORUM
/// cannot be reached on that data center.
EachQuorum,
/// Achieves linearizable consistency for lightweight transactions by preventing unconditional
/// updates. You cannot configure this level as a normal consistency level,
/// configured at the driver level using the consistency level field.
/// You configure this level using the serial consistency field
/// as part of the native protocol operation. See failure scenarios.
Serial,
/// Same as SERIAL but confined to the data center. A write must be written conditionally
/// to the commit log and memtable on a quorum of replica nodes in the same data center.
/// Same as SERIAL. Used for disaster recovery. See failure scenarios.
LocalSerial,
/// A write must be sent to, and successfully acknowledged by,
/// at least one replica node in the local data center.
/// In a multiple data center clusters, a consistency level of ONE is often desirable,
/// but cross-DC traffic is not. LOCAL_ONE accomplishes this.
/// For security and quality reasons, you can use this consistency level
/// in an offline datacenter to prevent automatic connection
/// to online nodes in other data centers if an offline node goes down.
LocalOne,
}
impl FromStr for Consistency {
type Err = error::Error;
fn from_str(s: &str) -> Result {
let consistency = match s {
"Any" => Consistency::Any,
"One" => Consistency::One,
"Two" => Consistency::Two,
"Three" => Consistency::Three,
"Quorum" => Consistency::Quorum,
"All" => Consistency::All,
"LocalQuorum" => Consistency::LocalQuorum,
"EachQuorum" => Consistency::EachQuorum,
"Serial" => Consistency::Serial,
"LocalSerial" => Consistency::LocalSerial,
"LocalOne" => Consistency::LocalOne,
_ => {
return Err(error::Error::General(format!(
"Invalid consistency provided: {s}"
)))
}
};
Ok(consistency)
}
}
impl Serialize for Consistency {
fn serialize(&self, cursor: &mut io::Cursor<&mut Vec>, version: Version) {
let value: i16 = (*self).into();
value.serialize(cursor, version)
}
}
impl TryFrom for Consistency {
type Error = error::Error;
fn try_from(value: CIntShort) -> Result {
match value {
0x0000 => Ok(Consistency::Any),
0x0001 => Ok(Consistency::One),
0x0002 => Ok(Consistency::Two),
0x0003 => Ok(Consistency::Three),
0x0004 => Ok(Consistency::Quorum),
0x0005 => Ok(Consistency::All),
0x0006 => Ok(Consistency::LocalQuorum),
0x0007 => Ok(Consistency::EachQuorum),
0x0008 => Ok(Consistency::Serial),
0x0009 => Ok(Consistency::LocalSerial),
0x000A => Ok(Consistency::LocalOne),
_ => Err(Self::Error::UnknownConsistency(value)),
}
}
}
impl From for CIntShort {
fn from(value: Consistency) -> Self {
match value {
Consistency::Any => 0x0000,
Consistency::One => 0x0001,
Consistency::Two => 0x0002,
Consistency::Three => 0x0003,
Consistency::Quorum => 0x0004,
Consistency::All => 0x0005,
Consistency::LocalQuorum => 0x0006,
Consistency::EachQuorum => 0x0007,
Consistency::Serial => 0x0008,
Consistency::LocalSerial => 0x0009,
Consistency::LocalOne => 0x000A,
}
}
}
impl FromBytes for Consistency {
fn from_bytes(bytes: &[u8]) -> error::Result {
try_i16_from_bytes(bytes)
.map_err(Into::into)
.and_then(TryInto::try_into)
}
}
impl FromCursor for Consistency {
fn from_cursor(cursor: &mut io::Cursor<&[u8]>, version: Version) -> error::Result {
CIntShort::from_cursor(cursor, version).and_then(TryInto::try_into)
}
}
impl Consistency {
/// Does this consistency require local dc.
#[inline]
pub fn is_dc_local(self) -> bool {
matches!(
self,
Consistency::LocalOne | Consistency::LocalQuorum | Consistency::LocalSerial
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frame::traits::{FromBytes, FromCursor};
use std::io::Cursor;
#[test]
fn test_consistency_serialize() {
assert_eq!(Consistency::Any.serialize_to_vec(Version::V4), &[0, 0]);
assert_eq!(Consistency::One.serialize_to_vec(Version::V4), &[0, 1]);
assert_eq!(Consistency::Two.serialize_to_vec(Version::V4), &[0, 2]);
assert_eq!(Consistency::Three.serialize_to_vec(Version::V4), &[0, 3]);
assert_eq!(Consistency::Quorum.serialize_to_vec(Version::V4), &[0, 4]);
assert_eq!(Consistency::All.serialize_to_vec(Version::V4), &[0, 5]);
assert_eq!(
Consistency::LocalQuorum.serialize_to_vec(Version::V4),
&[0, 6]
);
assert_eq!(
Consistency::EachQuorum.serialize_to_vec(Version::V4),
&[0, 7]
);
assert_eq!(Consistency::Serial.serialize_to_vec(Version::V4), &[0, 8]);
assert_eq!(
Consistency::LocalSerial.serialize_to_vec(Version::V4),
&[0, 9]
);
assert_eq!(
Consistency::LocalOne.serialize_to_vec(Version::V4),
&[0, 10]
);
}
#[test]
fn test_consistency_from() {
assert_eq!(Consistency::try_from(0).unwrap(), Consistency::Any);
assert_eq!(Consistency::try_from(1).unwrap(), Consistency::One);
assert_eq!(Consistency::try_from(2).unwrap(), Consistency::Two);
assert_eq!(Consistency::try_from(3).unwrap(), Consistency::Three);
assert_eq!(Consistency::try_from(4).unwrap(), Consistency::Quorum);
assert_eq!(Consistency::try_from(5).unwrap(), Consistency::All);
assert_eq!(Consistency::try_from(6).unwrap(), Consistency::LocalQuorum);
assert_eq!(Consistency::try_from(7).unwrap(), Consistency::EachQuorum);
assert_eq!(Consistency::try_from(8).unwrap(), Consistency::Serial);
assert_eq!(Consistency::try_from(9).unwrap(), Consistency::LocalSerial);
assert_eq!(Consistency::try_from(10).unwrap(), Consistency::LocalOne);
}
#[test]
fn test_consistency_from_bytes() {
assert_eq!(Consistency::from_bytes(&[0, 0]).unwrap(), Consistency::Any);
assert_eq!(Consistency::from_bytes(&[0, 1]).unwrap(), Consistency::One);
assert_eq!(Consistency::from_bytes(&[0, 2]).unwrap(), Consistency::Two);
assert_eq!(
Consistency::from_bytes(&[0, 3]).unwrap(),
Consistency::Three
);
assert_eq!(
Consistency::from_bytes(&[0, 4]).unwrap(),
Consistency::Quorum
);
assert_eq!(Consistency::from_bytes(&[0, 5]).unwrap(), Consistency::All);
assert_eq!(
Consistency::from_bytes(&[0, 6]).unwrap(),
Consistency::LocalQuorum
);
assert_eq!(
Consistency::from_bytes(&[0, 7]).unwrap(),
Consistency::EachQuorum
);
assert_eq!(
Consistency::from_bytes(&[0, 8]).unwrap(),
Consistency::Serial
);
assert_eq!(
Consistency::from_bytes(&[0, 9]).unwrap(),
Consistency::LocalSerial
);
assert_eq!(
Consistency::from_bytes(&[0, 10]).unwrap(),
Consistency::LocalOne
);
assert!(Consistency::from_bytes(&[0, 11]).is_err());
}
#[test]
fn test_consistency_from_cursor() {
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 0]), Version::V4).unwrap(),
Consistency::Any
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 1]), Version::V4).unwrap(),
Consistency::One
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 2]), Version::V4).unwrap(),
Consistency::Two
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 3]), Version::V4).unwrap(),
Consistency::Three
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 4]), Version::V4).unwrap(),
Consistency::Quorum
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 5]), Version::V4).unwrap(),
Consistency::All
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 6]), Version::V4).unwrap(),
Consistency::LocalQuorum
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 7]), Version::V4).unwrap(),
Consistency::EachQuorum
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 8]), Version::V4).unwrap(),
Consistency::Serial
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 9]), Version::V4).unwrap(),
Consistency::LocalSerial
);
assert_eq!(
Consistency::from_cursor(&mut Cursor::new(&[0, 10]), Version::V4).unwrap(),
Consistency::LocalOne
);
}
}
================================================
FILE: cassandra-protocol/src/crc.rs
================================================
use crc32fast::Hasher;
const CRC24_POLY: i32 = 0x1974f0b;
const CRC24_INIT: i32 = 0x875060;
/// Computes crc24 value of `bytes`.
pub fn crc24(bytes: &[u8]) -> i32 {
bytes.iter().fold(CRC24_INIT, |mut crc, byte| {
crc ^= (*byte as i32) << 16;
for _ in 0..8 {
crc <<= 1;
if (crc & 0x1000000) != 0 {
crc ^= CRC24_POLY;
}
}
crc
})
}
/// Computes crc32 value of `bytes`.
pub fn crc32(bytes: &[u8]) -> u32 {
let mut hasher = Hasher::new();
hasher.update(&[0xfa, 0x2d, 0x55, 0xca]); // Cassandra appends a few bytes and forgets to mention it in the spec...
hasher.update(bytes);
hasher.finalize()
}
================================================
FILE: cassandra-protocol/src/error.rs
================================================
use crate::compression::CompressionError;
use crate::frame::message_error::ErrorBody;
use crate::frame::Opcode;
use crate::types::{CInt, CIntShort};
use std::fmt::{Debug, Display};
use std::io;
use std::net::SocketAddr;
use std::result;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use thiserror::Error as ThisError;
use uuid::Error as UuidError;
pub type Result = result::Result;
/// CDRS custom error type. CDRS expects two types of error - errors returned by Server
/// and internal errors occurred within the driver itself. Occasionally `io::Error`
/// is a type that represent internal error because due to implementation IO errors only
/// can be raised by CDRS driver. `Server` error is an error which are ones returned by
/// a Server via result error frames.
#[derive(Debug, ThisError)]
#[non_exhaustive]
pub enum Error {
/// Internal IO error.
#[error("IO error: {0}")]
Io(#[from] io::Error),
/// Internal error that may be raised during `uuid::Uuid::from_bytes`
#[error("Uuid parse error: {0}")]
UuidParse(#[from] UuidError),
/// General error
#[error("General error: {0}")]
General(String),
/// Internal error that may be raised during `String::from_utf8`
#[error("FromUtf8 error: {0}")]
FromUtf8(#[from] FromUtf8Error),
/// Internal error that may be raised during `str::from_utf8`
#[error("Utf8 error: {0}")]
Utf8(#[from] Utf8Error),
/// Internal Compression/Decompression error.
#[error("Compressor error: {0}")]
Compression(#[from] CompressionError),
/// Server error.
#[error("Server {addr} error: {body:?}")]
Server { body: ErrorBody, addr: SocketAddr },
/// Timed out waiting for an operation to complete.
#[error("Timeout: {0}")]
Timeout(String),
/// Unknown consistency.
#[error("Unknown consistency: {0}")]
UnknownConsistency(CIntShort),
/// Unknown server event.
#[error("Unknown server event: {0}")]
UnknownServerEvent(String),
/// Unexpected topology change event type.
#[error("Unexpected topology change type: {0}")]
UnexpectedTopologyChangeType(String),
/// Unexpected status change event type.
#[error("Unexpected status change type: {0}")]
UnexpectedStatusChangeType(String),
/// Unexpected schema change event type.
#[error("Unexpected schema change type: {0}")]
UnexpectedSchemaChangeType(String),
/// Unexpected schema change event target.
#[error("Unexpected schema change target: {0}")]
UnexpectedSchemaChangeTarget(String),
/// Unexpected additional error info.
#[error("Unexpected error code: {0}")]
UnexpectedErrorCode(CInt),
/// Unexpected write type.
#[error("Unexpected write type: {0}")]
UnexpectedWriteType(String),
/// Expected a request opcode, got something else.
#[error("Opcode is not a request: {0}")]
NonRequestOpcode(Opcode),
/// Expected a response opcode, got something else.
#[error("Opcode is not a response: {0}")]
NonResponseOpcode(Opcode),
/// Unexpected result kind.
#[error("Unexpected result kind: {0}")]
UnexpectedResultKind(CInt),
/// Unexpected column type.
#[error("Unexpected column type: {0}")]
UnexpectedColumnType(CIntShort),
/// Invalid format found for given keyspace replication strategy.
#[error("Invalid replication format for: {keyspace}")]
InvalidReplicationFormat { keyspace: String },
/// Unexpected response to auth message.
#[error("Unexpected auth response: {0}")]
UnexpectedAuthResponse(Opcode),
/// Unexpected startup response.
#[error("Unexpected startup response: {0}")]
UnexpectedStartupResponse(Opcode),
/// Special error for cases when starting up a connection and protocol negotiation fails. There
/// currently is no explicit server-side code for this, so the information must be inferred from
/// returned error response.
#[error("Invalid protocol used when communicating with a node: {0}")]
InvalidProtocol(SocketAddr),
}
pub fn column_is_empty_err(column_name: T) -> Error {
Error::General(format!("Column or Udt property '{column_name}' is empty"))
}
impl From for Error {
fn from(err: String) -> Error {
Error::General(err)
}
}
impl From<&str> for Error {
fn from(err: &str) -> Error {
Error::General(err.to_string())
}
}
impl Clone for Error {
fn clone(&self) -> Self {
match self {
Error::Io(error) => Error::Io(io::Error::new(
error.kind(),
error
.get_ref()
.map(|error| error.to_string())
.unwrap_or_default(),
)),
Error::UuidParse(error) => Error::UuidParse(error.clone()),
Error::General(error) => Error::General(error.clone()),
Error::FromUtf8(error) => Error::FromUtf8(error.clone()),
Error::Utf8(error) => Error::Utf8(*error),
Error::Compression(error) => Error::Compression(error.clone()),
Error::Server { body, addr } => Error::Server {
body: body.clone(),
addr: *addr,
},
Error::Timeout(error) => Error::Timeout(error.clone()),
Error::UnknownConsistency(value) => Error::UnknownConsistency(*value),
Error::UnknownServerEvent(value) => Error::UnknownServerEvent(value.clone()),
Error::UnexpectedTopologyChangeType(value) => {
Error::UnexpectedTopologyChangeType(value.clone())
}
Error::UnexpectedStatusChangeType(value) => {
Error::UnexpectedStatusChangeType(value.clone())
}
Error::UnexpectedSchemaChangeType(value) => {
Error::UnexpectedSchemaChangeType(value.clone())
}
Error::UnexpectedSchemaChangeTarget(value) => {
Error::UnexpectedSchemaChangeTarget(value.clone())
}
Error::UnexpectedErrorCode(value) => Error::UnexpectedErrorCode(*value),
Error::UnexpectedWriteType(value) => Error::UnexpectedWriteType(value.clone()),
Error::NonRequestOpcode(value) => Error::NonRequestOpcode(*value),
Error::NonResponseOpcode(value) => Error::NonResponseOpcode(*value),
Error::UnexpectedResultKind(value) => Error::UnexpectedResultKind(*value),
Error::UnexpectedColumnType(value) => Error::UnexpectedColumnType(*value),
Error::InvalidReplicationFormat { keyspace } => Error::InvalidReplicationFormat {
keyspace: keyspace.clone(),
},
Error::UnexpectedAuthResponse(value) => Error::UnexpectedAuthResponse(*value),
Error::UnexpectedStartupResponse(value) => Error::UnexpectedStartupResponse(*value),
Error::InvalidProtocol(addr) => Error::InvalidProtocol(*addr),
}
}
}
================================================
FILE: cassandra-protocol/src/events.rs
================================================
use crate::frame::events::{
SchemaChange as MessageSchemaChange, ServerEvent as MessageServerEvent,
SimpleServerEvent as MessageSimpleServerEvent,
};
/// Full Server Event which includes all details about occurred change.
pub type ServerEvent = MessageServerEvent;
/// Simplified Server event. It should be used to represent an event
/// which consumer wants listen to.
pub type SimpleServerEvent = MessageSimpleServerEvent;
/// Reexport of `MessageSchemaChange`.
pub type SchemaChange = MessageSchemaChange;
================================================
FILE: cassandra-protocol/src/frame/events.rs
================================================
use crate::frame::traits::FromCursor;
use crate::frame::{Serialize, Version};
use crate::types::{from_cursor_str, from_cursor_string_list, serialize_str, CIntShort};
use crate::{error, Error};
use derive_more::Display;
use std::cmp::PartialEq;
use std::convert::TryFrom;
use std::io::Cursor;
use std::net::SocketAddr;
// Event types
const TOPOLOGY_CHANGE: &str = "TOPOLOGY_CHANGE";
const STATUS_CHANGE: &str = "STATUS_CHANGE";
const SCHEMA_CHANGE: &str = "SCHEMA_CHANGE";
// Topology changes
const NEW_NODE: &str = "NEW_NODE";
const REMOVED_NODE: &str = "REMOVED_NODE";
// Status changes
const UP: &str = "UP";
const DOWN: &str = "DOWN";
// Schema changes
const CREATED: &str = "CREATED";
const UPDATED: &str = "UPDATED";
const DROPPED: &str = "DROPPED";
// Schema change targets
const KEYSPACE: &str = "KEYSPACE";
const TABLE: &str = "TABLE";
const TYPE: &str = "TYPE";
const FUNCTION: &str = "FUNCTION";
const AGGREGATE: &str = "AGGREGATE";
/// Simplified `ServerEvent` that does not contain details
/// about a concrete change. It may be useful for subscription
/// when you need only string representation of an event.
#[derive(Debug, PartialEq, Copy, Clone, Ord, PartialOrd, Eq, Hash)]
#[non_exhaustive]
pub enum SimpleServerEvent {
TopologyChange,
StatusChange,
SchemaChange,
}
impl SimpleServerEvent {
pub fn as_str(&self) -> &'static str {
match *self {
SimpleServerEvent::TopologyChange => TOPOLOGY_CHANGE,
SimpleServerEvent::StatusChange => STATUS_CHANGE,
SimpleServerEvent::SchemaChange => SCHEMA_CHANGE,
}
}
}
impl From for SimpleServerEvent {
fn from(event: ServerEvent) -> SimpleServerEvent {
match event {
ServerEvent::TopologyChange(_) => SimpleServerEvent::TopologyChange,
ServerEvent::StatusChange(_) => SimpleServerEvent::StatusChange,
ServerEvent::SchemaChange(_) => SimpleServerEvent::SchemaChange,
}
}
}
impl<'a> From<&'a ServerEvent> for SimpleServerEvent {
fn from(event: &'a ServerEvent) -> SimpleServerEvent {
match *event {
ServerEvent::TopologyChange(_) => SimpleServerEvent::TopologyChange,
ServerEvent::StatusChange(_) => SimpleServerEvent::StatusChange,
ServerEvent::SchemaChange(_) => SimpleServerEvent::SchemaChange,
}
}
}
impl TryFrom<&str> for SimpleServerEvent {
type Error = error::Error;
fn try_from(value: &str) -> Result {
match value {
TOPOLOGY_CHANGE => Ok(SimpleServerEvent::TopologyChange),
STATUS_CHANGE => Ok(SimpleServerEvent::StatusChange),
SCHEMA_CHANGE => Ok(SimpleServerEvent::SchemaChange),
value => Err(Error::UnknownServerEvent(value.into())),
}
}
}
impl PartialEq for SimpleServerEvent {
fn eq(&self, full_event: &ServerEvent) -> bool {
self == &SimpleServerEvent::from(full_event)
}
}
/// Full server event that contains all details about a concrete change.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum ServerEvent {
/// Events related to change in the cluster topology
TopologyChange(TopologyChange),
/// Events related to change of node status.
StatusChange(StatusChange),
/// Events related to schema change.
SchemaChange(SchemaChange),
}
impl Serialize for ServerEvent {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match &self {
ServerEvent::TopologyChange(t) => {
serialize_str(cursor, TOPOLOGY_CHANGE, version);
t.serialize(cursor, version);
}
ServerEvent::StatusChange(s) => {
serialize_str(cursor, STATUS_CHANGE, version);
s.serialize(cursor, version);
}
ServerEvent::SchemaChange(s) => {
serialize_str(cursor, SCHEMA_CHANGE, version);
s.serialize(cursor, version);
}
}
}
}
impl PartialEq for ServerEvent {
fn eq(&self, event: &SimpleServerEvent) -> bool {
&SimpleServerEvent::from(self) == event
}
}
impl FromCursor for ServerEvent {
fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result {
let event_type = from_cursor_str(cursor)?;
match event_type {
TOPOLOGY_CHANGE => Ok(ServerEvent::TopologyChange(TopologyChange::from_cursor(
cursor, version,
)?)),
STATUS_CHANGE => Ok(ServerEvent::StatusChange(StatusChange::from_cursor(
cursor, version,
)?)),
SCHEMA_CHANGE => Ok(ServerEvent::SchemaChange(SchemaChange::from_cursor(
cursor, version,
)?)),
_ => Err(Error::UnknownServerEvent(event_type.into())),
}
}
}
/// Events related to change in the cluster topology
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TopologyChange {
pub change_type: TopologyChangeType,
pub addr: SocketAddr,
}
impl Serialize for TopologyChange {
//noinspection DuplicatedCode
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
self.change_type.serialize(cursor, version);
self.addr.serialize(cursor, version);
}
}
impl FromCursor for TopologyChange {
fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result {
let change_type = TopologyChangeType::from_cursor(cursor, version)?;
let addr = SocketAddr::from_cursor(cursor, version)?;
Ok(TopologyChange { change_type, addr })
}
}
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash, Display)]
#[non_exhaustive]
pub enum TopologyChangeType {
NewNode,
RemovedNode,
}
impl Serialize for TopologyChangeType {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match &self {
TopologyChangeType::NewNode => serialize_str(cursor, NEW_NODE, version),
TopologyChangeType::RemovedNode => serialize_str(cursor, REMOVED_NODE, version),
}
}
}
impl FromCursor for TopologyChangeType {
fn from_cursor(
cursor: &mut Cursor<&[u8]>,
_version: Version,
) -> error::Result {
from_cursor_str(cursor).and_then(|tc| match tc {
NEW_NODE => Ok(TopologyChangeType::NewNode),
REMOVED_NODE => Ok(TopologyChangeType::RemovedNode),
_ => Err(Error::UnexpectedTopologyChangeType(tc.into())),
})
}
}
/// Events related to change of node status.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StatusChange {
pub change_type: StatusChangeType,
pub addr: SocketAddr,
}
impl Serialize for StatusChange {
//noinspection DuplicatedCode
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
self.change_type.serialize(cursor, version);
self.addr.serialize(cursor, version);
}
}
impl FromCursor for StatusChange {
fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result {
let change_type = StatusChangeType::from_cursor(cursor, version)?;
let addr = SocketAddr::from_cursor(cursor, version)?;
Ok(StatusChange { change_type, addr })
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[non_exhaustive]
pub enum StatusChangeType {
Up,
Down,
}
impl Serialize for StatusChangeType {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match self {
StatusChangeType::Up => serialize_str(cursor, UP, version),
StatusChangeType::Down => serialize_str(cursor, DOWN, version),
}
}
}
impl FromCursor for StatusChangeType {
fn from_cursor(
cursor: &mut Cursor<&[u8]>,
_version: Version,
) -> error::Result {
from_cursor_str(cursor).and_then(|sct| match sct {
UP => Ok(StatusChangeType::Up),
DOWN => Ok(StatusChangeType::Down),
_ => Err(Error::UnexpectedStatusChangeType(sct.into())),
})
}
}
/// Events related to schema change.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct SchemaChange {
pub change_type: SchemaChangeType,
pub target: SchemaChangeTarget,
pub options: SchemaChangeOptions,
}
impl Serialize for SchemaChange {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
self.change_type.serialize(cursor, version);
self.target.serialize(cursor, version);
self.options.serialize(cursor, version);
}
}
impl FromCursor for SchemaChange {
fn from_cursor(cursor: &mut Cursor<&[u8]>, version: Version) -> error::Result {
let change_type = SchemaChangeType::from_cursor(cursor, version)?;
let target = SchemaChangeTarget::from_cursor(cursor, version)?;
let options = SchemaChangeOptions::from_cursor_and_target(cursor, &target)?;
Ok(SchemaChange {
change_type,
target,
options,
})
}
}
/// Represents type of changes.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[non_exhaustive]
pub enum SchemaChangeType {
Created,
Updated,
Dropped,
}
impl Serialize for SchemaChangeType {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match self {
SchemaChangeType::Created => serialize_str(cursor, CREATED, version),
SchemaChangeType::Updated => serialize_str(cursor, UPDATED, version),
SchemaChangeType::Dropped => serialize_str(cursor, DROPPED, version),
}
}
}
impl FromCursor for SchemaChangeType {
fn from_cursor(
cursor: &mut Cursor<&[u8]>,
_version: Version,
) -> error::Result {
from_cursor_str(cursor).and_then(|ct| match ct {
CREATED => Ok(SchemaChangeType::Created),
UPDATED => Ok(SchemaChangeType::Updated),
DROPPED => Ok(SchemaChangeType::Dropped),
_ => Err(Error::UnexpectedSchemaChangeType(ct.into())),
})
}
}
/// Refers to a target of changes were made.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[non_exhaustive]
pub enum SchemaChangeTarget {
Keyspace,
Table,
Type,
Function,
Aggregate,
}
impl Serialize for SchemaChangeTarget {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match self {
SchemaChangeTarget::Keyspace => serialize_str(cursor, KEYSPACE, version),
SchemaChangeTarget::Table => serialize_str(cursor, TABLE, version),
SchemaChangeTarget::Type => serialize_str(cursor, TYPE, version),
SchemaChangeTarget::Function => serialize_str(cursor, FUNCTION, version),
SchemaChangeTarget::Aggregate => serialize_str(cursor, AGGREGATE, version),
}
}
}
impl FromCursor for SchemaChangeTarget {
fn from_cursor(
cursor: &mut Cursor<&[u8]>,
_version: Version,
) -> error::Result {
from_cursor_str(cursor).and_then(|t| match t {
KEYSPACE => Ok(SchemaChangeTarget::Keyspace),
TABLE => Ok(SchemaChangeTarget::Table),
TYPE => Ok(SchemaChangeTarget::Type),
FUNCTION => Ok(SchemaChangeTarget::Function),
AGGREGATE => Ok(SchemaChangeTarget::Aggregate),
_ => Err(Error::UnexpectedSchemaChangeTarget(t.into())),
})
}
}
/// Information about changes made.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum SchemaChangeOptions {
/// Changes related to keyspaces. Contains keyspace name.
Keyspace(String),
/// Changes related to tables. Contains keyspace and table names.
TableType(String, String),
/// Changes related to functions and aggregations. Contains:
/// * keyspace containing the user defined function/aggregate
/// * the function/aggregate name
/// * list of strings, one string for each argument type (as CQL type)
FunctionAggregate(String, String, Vec),
}
impl Serialize for SchemaChangeOptions {
fn serialize(&self, cursor: &mut Cursor<&mut Vec>, version: Version) {
match self {
SchemaChangeOptions::Keyspace(ks) => {
serialize_str(cursor, ks, version);
}
SchemaChangeOptions::TableType(ks, t) => {
serialize_str(cursor, ks, version);
serialize_str(cursor, t, version);
}
SchemaChangeOptions::FunctionAggregate(ks, fa_name, list) => {
serialize_str(cursor, ks, version);
serialize_str(cursor, fa_name, version);
let len = list.len() as CIntShort;
len.serialize(cursor, version);
list.iter().for_each(|x| serialize_str(cursor, x, version));
}
}
}
}
impl SchemaChangeOptions {
fn from_cursor_and_target(
cursor: &mut Cursor<&[u8]>,
target: &SchemaChangeTarget,
) -> error::Result {
Ok(match *target {
SchemaChangeTarget::Keyspace => SchemaChangeOptions::from_cursor_keyspace(cursor)?,
SchemaChangeTarget::Table | SchemaChangeTarget::Type => {
SchemaChangeOptions::from_cursor_table_type(cursor)?
}
SchemaChangeTarget::Function | SchemaChangeTarget::Aggregate => {
SchemaChangeOptions::from_cursor_function_aggregate(cursor)?
}
})
}
fn from_cursor_keyspace(cursor: &mut Cursor<&[u8]>) -> error::Result {
Ok(SchemaChangeOptions::Keyspace(
from_cursor_str(cursor)?.to_string(),
))
}
fn from_cursor_table_type(cursor: &mut Cursor<&[u8]>) -> error::Result {
let keyspace = from_cursor_str(cursor)?.to_string();
let name = from_cursor_str(cursor)?.to_string();
Ok(SchemaChangeOptions::TableType(keyspace, name))
}
fn from_cursor_function_aggregate(
cursor: &mut Cursor<&[u8]>,
) -> error::Result {
let keyspace = from_cursor_str(cursor)?.to_string();
let name = from_cursor_str(cursor)?.to_string();
let types = from_cursor_string_list(cursor)?;
Ok(SchemaChangeOptions::FunctionAggregate(
keyspace, name, types,
))
}
}
#[cfg(test)]
fn test_encode_decode(bytes: &[u8], expected: ServerEvent) {
let mut ks: Cursor<&[u8]> = Cursor::new(bytes);
let event = ServerEvent::from_cursor(&mut ks, Version::V4).unwrap();
assert_eq!(expected, event);
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
expected.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, bytes);
}
#[cfg(test)]
mod topology_change_type_test {
use super::*;
use crate::frame::traits::FromCursor;
use std::io::Cursor;
#[test]
fn from_cursor() {
let a = &[0, 8, 78, 69, 87, 95, 78, 79, 68, 69];
let mut new_node: Cursor<&[u8]> = Cursor::new(a);
assert_eq!(
TopologyChangeType::from_cursor(&mut new_node, Version::V4).unwrap(),
TopologyChangeType::NewNode
);
let b = &[0, 12, 82, 69, 77, 79, 86, 69, 68, 95, 78, 79, 68, 69];
let mut removed_node: Cursor<&[u8]> = Cursor::new(b);
assert_eq!(
TopologyChangeType::from_cursor(&mut removed_node, Version::V4).unwrap(),
TopologyChangeType::RemovedNode
);
}
#[test]
fn serialize() {
{
let a = &[0, 8, 78, 69, 87, 95, 78, 79, 68, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let new_node = TopologyChangeType::NewNode;
new_node.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, a);
}
{
let b = &[0, 12, 82, 69, 77, 79, 86, 69, 68, 95, 78, 79, 68, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let removed_node = TopologyChangeType::RemovedNode;
removed_node.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, b);
}
}
#[test]
#[should_panic]
fn from_cursor_wrong() {
let a = &[0, 1, 78];
let mut wrong: Cursor<&[u8]> = Cursor::new(a);
let _ = TopologyChangeType::from_cursor(&mut wrong, Version::V4).unwrap();
}
}
#[cfg(test)]
mod status_change_type_test {
use super::*;
use crate::frame::traits::FromCursor;
use std::io::Cursor;
#[test]
fn from_cursor() {
let a = &[0, 2, 85, 80];
let mut up: Cursor<&[u8]> = Cursor::new(a);
assert_eq!(
StatusChangeType::from_cursor(&mut up, Version::V4).unwrap(),
StatusChangeType::Up
);
let b = &[0, 4, 68, 79, 87, 78];
let mut down: Cursor<&[u8]> = Cursor::new(b);
assert_eq!(
StatusChangeType::from_cursor(&mut down, Version::V4).unwrap(),
StatusChangeType::Down
);
}
#[test]
fn serialize() {
{
let a = &[0, 2, 85, 80];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let up = StatusChangeType::Up;
up.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, a);
}
{
let b = &[0, 4, 68, 79, 87, 78];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let down = StatusChangeType::Down;
down.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, b);
}
}
#[test]
fn from_cursor_wrong() {
let a = &[0, 1, 78];
let mut wrong: Cursor<&[u8]> = Cursor::new(a);
let err = StatusChangeType::from_cursor(&mut wrong, Version::V4).unwrap_err();
assert!(matches!(err, Error::UnexpectedStatusChangeType(_)));
}
}
#[cfg(test)]
mod schema_change_type_test {
use super::*;
use crate::frame::traits::FromCursor;
use std::io::Cursor;
#[test]
fn from_cursor() {
let a = &[0, 7, 67, 82, 69, 65, 84, 69, 68];
let mut created: Cursor<&[u8]> = Cursor::new(a);
assert_eq!(
SchemaChangeType::from_cursor(&mut created, Version::V4).unwrap(),
SchemaChangeType::Created
);
let b = &[0, 7, 85, 80, 68, 65, 84, 69, 68];
let mut updated: Cursor<&[u8]> = Cursor::new(b);
assert_eq!(
SchemaChangeType::from_cursor(&mut updated, Version::V4).unwrap(),
SchemaChangeType::Updated
);
let c = &[0, 7, 68, 82, 79, 80, 80, 69, 68];
let mut dropped: Cursor<&[u8]> = Cursor::new(c);
assert_eq!(
SchemaChangeType::from_cursor(&mut dropped, Version::V4).unwrap(),
SchemaChangeType::Dropped
);
}
#[test]
fn serialize() {
{
let a = &[0, 7, 67, 82, 69, 65, 84, 69, 68];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let created = SchemaChangeType::Created;
created.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, a);
}
{
let b = &[0, 7, 85, 80, 68, 65, 84, 69, 68];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let updated = SchemaChangeType::Updated;
updated.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, b);
}
{
let c = &[0, 7, 68, 82, 79, 80, 80, 69, 68];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let dropped = SchemaChangeType::Dropped;
dropped.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, c);
}
}
#[test]
#[should_panic]
fn from_cursor_wrong() {
let a = &[0, 1, 78];
let mut wrong: Cursor<&[u8]> = Cursor::new(a);
let _ = SchemaChangeType::from_cursor(&mut wrong, Version::V4).unwrap();
}
}
#[cfg(test)]
mod schema_change_target_test {
use super::*;
use crate::frame::traits::FromCursor;
use std::io::Cursor;
#[test]
#[allow(clippy::many_single_char_names)]
fn schema_change_target() {
{
let bytes = &[0, 8, 75, 69, 89, 83, 80, 65, 67, 69];
let mut keyspace: Cursor<&[u8]> = Cursor::new(bytes);
assert_eq!(
SchemaChangeTarget::from_cursor(&mut keyspace, Version::V4).unwrap(),
SchemaChangeTarget::Keyspace
);
}
let b = &[0, 5, 84, 65, 66, 76, 69];
let mut table: Cursor<&[u8]> = Cursor::new(b);
assert_eq!(
SchemaChangeTarget::from_cursor(&mut table, Version::V4).unwrap(),
SchemaChangeTarget::Table
);
let c = &[0, 4, 84, 89, 80, 69];
let mut _type: Cursor<&[u8]> = Cursor::new(c);
assert_eq!(
SchemaChangeTarget::from_cursor(&mut _type, Version::V4).unwrap(),
SchemaChangeTarget::Type
);
let d = &[0, 8, 70, 85, 78, 67, 84, 73, 79, 78];
let mut function: Cursor<&[u8]> = Cursor::new(d);
assert_eq!(
SchemaChangeTarget::from_cursor(&mut function, Version::V4).unwrap(),
SchemaChangeTarget::Function
);
let e = &[0, 9, 65, 71, 71, 82, 69, 71, 65, 84, 69];
let mut aggregate: Cursor<&[u8]> = Cursor::new(e);
assert_eq!(
SchemaChangeTarget::from_cursor(&mut aggregate, Version::V4).unwrap(),
SchemaChangeTarget::Aggregate
);
}
#[test]
fn serialize() {
{
let a = &[0, 8, 75, 69, 89, 83, 80, 65, 67, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let keyspace = SchemaChangeTarget::Keyspace;
keyspace.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, a);
}
{
let b = &[0, 5, 84, 65, 66, 76, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let table = SchemaChangeTarget::Table;
table.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, b);
}
{
let c = &[0, 4, 84, 89, 80, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let target_type = SchemaChangeTarget::Type;
target_type.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, c);
}
{
let d = &[0, 8, 70, 85, 78, 67, 84, 73, 79, 78];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let function = SchemaChangeTarget::Function;
function.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, d);
}
{
let e = &[0, 9, 65, 71, 71, 82, 69, 71, 65, 84, 69];
let mut buffer = Vec::new();
let mut cursor = Cursor::new(&mut buffer);
let aggregate = SchemaChangeTarget::Aggregate;
aggregate.serialize(&mut cursor, Version::V4);
assert_eq!(buffer, e);
}
}
#[test]
#[should_panic]
fn from_cursor_wrong() {
let a = &[0, 1, 78];
let mut wrong: Cursor<&[u8]> = Cursor::new(a);
let _ = SchemaChangeTarget::from_cursor(&mut wrong, Version::V4).unwrap();
}
}
#[cfg(test)]
mod server_event {
use super::*;
#[test]
fn topology_change_new_node() {
let bytes = &[
// topology change
0, 15, 84, 79, 80, 79, 76, 79, 71, 89, 95, 67, 72, 65, 78, 71, 69, // new node
0, 8, 78, 69, 87, 95, 78, 79, 68, 69, //
4, 127, 0, 0, 1, 0, 0, 0, 1, // 127.0.0.1:1
];
let expected = ServerEvent::TopologyChange(TopologyChange {
change_type: TopologyChangeType::NewNode,
addr: "127.0.0.1:1".parse().unwrap(),
});
test_encode_decode(bytes, expected);
}
#[test]
fn topology_change_removed_node() {
let bytes = &[
// topology change
0, 15, 84, 79, 80, 79, 76, 79, 71, 89, 95, 67, 72, 65, 78, 71, 69,
// removed node
0, 12, 82, 69, 77, 79, 86, 69, 68, 95, 78, 79, 68, 69, //
4, 127, 0, 0, 1, 0, 0, 0, 1, // 127.0.0.1:1
];
let expected = ServerEvent::TopologyChange(TopologyChange {
change_type: TopologyChangeType::RemovedNode,
addr: "127.0.0.1:1".parse().unwrap(),
});
test_encode_decode(bytes, expected);
}
#[test]
fn status_change_up() {
let bytes = &[
// status change
0, 13, 83, 84, 65, 84, 85, 83, 95, 67, 72, 65, 78, 71, 69, // up
0, 2, 85, 80, //
4, 127, 0, 0, 1, 0, 0, 0, 1, // 127.0.0.1:1
];
let expected = ServerEvent::StatusChange(StatusChange {
change_type: StatusChangeType::Up,
addr: "127.0.0.1:1".parse().unwrap(),
});
test_encode_decode(bytes, expected);
}
#[test]
fn status_change_down() {
let bytes = &[
// status change
0, 13, 83, 84, 65, 84, 85, 83, 95, 67, 72, 65, 78, 71, 69, // down
0, 4, 68, 79, 87, 78, //
4, 127, 0, 0, 1, 0, 0, 0, 1, // 127.0.0.1:1
];
let expected = ServerEvent::StatusChange(StatusChange {
change_type: StatusChangeType::Down,
addr: "127.0.0.1:1".parse().unwrap(),
});
test_encode_decode(bytes, expected);
}
#[test]
fn schema_change_created() {
// keyspace
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // created
0, 7, 67, 82, 69, 65, 84, 69, 68, // keyspace
0, 8, 75, 69, 89, 83, 80, 65, 67, 69, // my_ks
0, 5, 109, 121, 95, 107, 115,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Created,
target: SchemaChangeTarget::Keyspace,
options: SchemaChangeOptions::Keyspace("my_ks".to_string()),
});
test_encode_decode(bytes, expected);
}
// table
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // created
0, 7, 67, 82, 69, 65, 84, 69, 68, // table
0, 5, 84, 65, 66, 76, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Created,
target: SchemaChangeTarget::Table,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
// type
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // created
0, 7, 67, 82, 69, 65, 84, 69, 68, // type
0, 4, 84, 89, 80, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Created,
target: SchemaChangeTarget::Type,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
{
// function
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // created
0, 7, 67, 82, 69, 65, 84, 69, 68, // function
0, 8, 70, 85, 78, 67, 84, 73, 79, 78, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Created,
target: SchemaChangeTarget::Function,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
{
// aggregate
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // created
0, 7, 67, 82, 69, 65, 84, 69, 68, // aggregate
0, 9, 65, 71, 71, 82, 69, 71, 65, 84, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Created,
target: SchemaChangeTarget::Aggregate,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
}
#[test]
fn schema_change_updated() {
// keyspace
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // updated
0, 7, 85, 80, 68, 65, 84, 69, 68, // keyspace
0, 8, 75, 69, 89, 83, 80, 65, 67, 69, // my_ks
0, 5, 109, 121, 95, 107, 115,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Updated,
target: SchemaChangeTarget::Keyspace,
options: SchemaChangeOptions::Keyspace("my_ks".to_string()),
});
test_encode_decode(bytes, expected);
}
// table
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // updated
0, 7, 85, 80, 68, 65, 84, 69, 68, // table
0, 5, 84, 65, 66, 76, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Updated,
target: SchemaChangeTarget::Table,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
// type
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // updated
0, 7, 85, 80, 68, 65, 84, 69, 68, // type
0, 4, 84, 89, 80, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Updated,
target: SchemaChangeTarget::Type,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
// function
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // updated
0, 7, 85, 80, 68, 65, 84, 69, 68, // function
0, 8, 70, 85, 78, 67, 84, 73, 79, 78, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Updated,
target: SchemaChangeTarget::Function,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
// aggreate
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // updated
0, 7, 85, 80, 68, 65, 84, 69, 68, // aggregate
0, 9, 65, 71, 71, 82, 69, 71, 65, 84, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Updated,
target: SchemaChangeTarget::Aggregate,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
}
#[test]
fn schema_change_dropped() {
// keyspace
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // dropped
0, 7, 68, 82, 79, 80, 80, 69, 68, // keyspace
0, 8, 75, 69, 89, 83, 80, 65, 67, 69, // my_ks
0, 5, 109, 121, 95, 107, 115,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Dropped,
target: SchemaChangeTarget::Keyspace,
options: SchemaChangeOptions::Keyspace("my_ks".to_string()),
});
test_encode_decode(bytes, expected);
}
// table
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // dropped
0, 7, 68, 82, 79, 80, 80, 69, 68, // table
0, 5, 84, 65, 66, 76, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Dropped,
target: SchemaChangeTarget::Table,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
// type
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // dropped
0, 7, 68, 82, 79, 80, 80, 69, 68, // type
0, 4, 84, 89, 80, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // my_table
0, 8, 109, 121, 95, 116, 97, 98, 108, 101,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Dropped,
target: SchemaChangeTarget::Type,
options: SchemaChangeOptions::TableType(
"my_ks".to_string(),
"my_table".to_string(),
),
});
test_encode_decode(bytes, expected);
}
// function
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // dropped
0, 7, 68, 82, 79, 80, 80, 69, 68, // function
0, 8, 70, 85, 78, 67, 84, 73, 79, 78, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Dropped,
target: SchemaChangeTarget::Function,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
// function
{
let bytes = &[
// schema change
0, 13, 83, 67, 72, 69, 77, 65, 95, 67, 72, 65, 78, 71, 69, // dropped
0, 7, 68, 82, 79, 80, 80, 69, 68, // aggregate
0, 9, 65, 71, 71, 82, 69, 71, 65, 84, 69, // my_ks
0, 5, 109, 121, 95, 107, 115, // name
0, 4, 110, 97, 109, 101, // empty list of parameters
0, 0,
];
let expected = ServerEvent::SchemaChange(SchemaChange {
change_type: SchemaChangeType::Dropped,
target: SchemaChangeTarget::Aggregate,
options: SchemaChangeOptions::FunctionAggregate(
"my_ks".to_string(),
"name".to_string(),
Vec::new(),
),
});
test_encode_decode(bytes, expected);
}
}
}
================================================
FILE: cassandra-protocol/src/frame/frame_decoder.rs
================================================
use crate::compression::{Compression, CompressionError};
use crate::crc::{crc24, crc32};
use crate::error::{Error, Result};
use crate::frame::{
Envelope, ParseEnvelopeError, COMPRESSED_FRAME_HEADER_LENGTH, ENVELOPE_HEADER_LEN,
FRAME_TRAILER_LENGTH, MAX_FRAME_SIZE, PAYLOAD_SIZE_LIMIT, UNCOMPRESSED_FRAME_HEADER_LENGTH,
};
use lz4_flex::decompress;
use std::convert::TryInto;
use std::io;
#[inline]
fn create_unexpected_self_contained_error() -> Error {
"Found self-contained frame while waiting for non self-contained continuation!".into()
}
#[inline]
fn create_header_crc_mismatch_error(computed_crc: i32, header_crc24: i32) -> Error {
format!("Header CRC mismatch - expected {header_crc24}, found {computed_crc}.",).into()
}
#[inline]
fn create_payload_crc_mismatch_error(computed_crc: u32, payload_crc32: u32) -> Error {
format!("Payload CRC mismatch - read {payload_crc32}, computed {computed_crc}.",).into()
}
fn extract_envelopes(buffer: &[u8], compression: Compression) -> Result<(usize, Vec)> {
let mut current_pos = 0;
let mut envelopes = vec![];
loop {
match Envelope::from_buffer(&buffer[current_pos..], compression) {
Ok(envelope) => {
envelopes.push(envelope.envelope);
current_pos += envelope.envelope_len;
}
Err(ParseEnvelopeError::NotEnoughBytes) => break,
Err(error) => return Err(error.to_string().into()),
}
}
Ok((current_pos, envelopes))
}
fn try_decode_envelopes_with_spare_data(
buffer: &mut Vec,
compression: Compression,
) -> Result<(Vec, Vec)> {
let (current_pos, envelopes) = extract_envelopes(buffer.as_slice(), compression)?;
Ok((envelopes, buffer.split_off(current_pos)))
}
fn try_decode_envelopes_without_spare_data(buffer: &[u8]) -> Result> {
let (_, envelopes) = extract_envelopes(buffer, Compression::None)?;
Ok(envelopes)
}
/// A decoder for frames. Since protocol v5, frames became "envelopes" and a frame now can contain
/// multiple complete envelopes (self-contained frame) or a part of one bigger envelope.
pub trait FrameDecoder {
/// Consumes some data and returns decoded envelopes. Decoders can be stateful, so data can be
/// buffered until envelopes can be parsed.
/// The buffer passed in should be cleared of consumed data by the decoder.
fn consume(&mut self, data: &mut Vec, compression: Compression) -> Result>;
}
/// Pre-V5 frame decoder which simply decodes one envelope directly into a buffer.
#[derive(Clone, Debug)]
pub struct LegacyFrameDecoder {
buffer: Vec,
}
impl Default for LegacyFrameDecoder {
fn default() -> Self {
Self {
buffer: Vec::with_capacity(MAX_FRAME_SIZE),
}
}
}
impl FrameDecoder for LegacyFrameDecoder {
fn consume(&mut self, data: &mut Vec, compression: Compression) -> Result> {
if self.buffer.is_empty() {
// optimistic case
let (envelopes, buffer) = try_decode_envelopes_with_spare_data(data, compression)?;
self.buffer = buffer;
data.clear();
return Ok(envelopes);
}
self.buffer.append(data);
let (envelopes, buffer) =
try_decode_envelopes_with_spare_data(&mut self.buffer, compression)?;
self.buffer = buffer;
Ok(envelopes)
}
}
/// Post-V5 Lz4 decoder with support for envelope frames with CRC checksum.
#[derive(Clone, Debug, Default)]
pub struct Lz4FrameDecoder {
inner_decoder: GenericFrameDecoder,
}
impl FrameDecoder for Lz4FrameDecoder {
//noinspection DuplicatedCode
#[inline]
fn consume(&mut self, data: &mut Vec, _compression: Compression) -> Result> {
self.inner_decoder.consume(data, Self::try_decode_frame)
}
}
impl Lz4FrameDecoder {
fn try_decode_frame(buffer: &mut Vec) -> Result