You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by hg...@apache.org on 2026/03/26 15:38:11 UTC
(iggy) branch master updated: refactor(server): replace ServerCommand enum and binary mapper with wire types (#3027)
This is an automated email from the ASF dual-hosted git repository.
hgruszecki pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/master by this push:
new f8be02437 refactor(server): replace ServerCommand enum and binary mapper with wire types (#3027)
f8be02437 is described below
commit f8be02437b0bea1d035c5caaa0cc324386a8e8dd
Author: Hubert Gruszecki <h....@gmail.com>
AuthorDate: Thu Mar 26 16:38:06 2026 +0100
refactor(server): replace ServerCommand enum and binary mapper with wire types (#3027)
---
Cargo.lock | 16 +-
Cargo.toml | 1 -
DEPENDENCIES.md | 1 -
core/binary_protocol/src/framing.rs | 52 +-
.../src/requests/system/get_snapshot.rs | 98 +++-
.../src/requests/users/create_user.rs | 18 +-
.../src/requests/users/update_permissions.rs | 14 +-
core/integration/Cargo.toml | 2 +-
.../server/scenarios/authentication_scenario.rs | 349 +++++--------
core/server/Cargo.toml | 2 +-
core/server/src/binary/command.rs | 432 ---------------
core/server/src/binary/dispatch.rs | 581 +++++++++++++++++++++
.../cluster/get_cluster_metadata_handler.rs | 72 ++-
.../create_consumer_group_handler.rs | 88 ++--
.../delete_consumer_group_handler.rs | 73 ++-
.../consumer_groups/get_consumer_group_handler.rs | 97 ++--
.../consumer_groups/get_consumer_groups_handler.rs | 86 ++-
.../consumer_groups/join_consumer_group_handler.rs | 74 ++-
.../leave_consumer_group_handler.rs | 74 ++-
.../delete_consumer_offset_handler.rs | 76 +--
.../get_consumer_offset_handler.rs | 108 ++--
.../store_consumer_offset_handler.rs | 86 ++-
.../messages/flush_unsaved_buffer_handler.rs | 93 ++--
.../handlers/messages/poll_messages_handler.rs | 128 ++---
.../handlers/messages/send_messages_handler.rs | 343 ++++++------
core/server/src/binary/handlers/mod.rs | 1 -
.../partitions/create_partitions_handler.rs | 74 ++-
.../partitions/delete_partitions_handler.rs | 74 ++-
.../create_personal_access_token_handler.rs | 85 ++-
.../delete_personal_access_token_handler.rs | 75 ++-
.../get_personal_access_tokens_handler.rs | 79 ++-
.../login_with_personal_access_token_handler.rs | 78 ++-
.../handlers/segments/delete_segments_handler.rs | 101 ++--
core/server/src/binary/handlers/segments/mod.rs | 2 +-
.../handlers/streams/create_stream_handler.rs | 86 ++-
.../handlers/streams/delete_stream_handler.rs | 68 +--
.../binary/handlers/streams/get_stream_handler.rs | 126 +++--
.../binary/handlers/streams/get_streams_handler.rs | 68 ++-
.../handlers/streams/purge_stream_handler.rs | 68 +--
.../handlers/streams/update_stream_handler.rs | 74 ++-
.../binary/handlers/system/get_client_handler.rs | 67 +--
.../binary/handlers/system/get_clients_handler.rs | 57 +-
.../src/binary/handlers/system/get_me_handler.rs | 79 +--
.../binary/handlers/system/get_snapshot_handler.rs | 60 +--
.../binary/handlers/system/get_stats_handler.rs | 105 ++--
.../src/binary/handlers/system/ping_handler.rs | 48 +-
.../binary/handlers/topics/create_topic_handler.rs | 126 +++--
.../binary/handlers/topics/delete_topic_handler.rs | 70 +--
.../binary/handlers/topics/get_topic_handler.rs | 92 ++--
.../binary/handlers/topics/get_topics_handler.rs | 74 ++-
.../binary/handlers/topics/purge_topic_handler.rs | 70 +--
.../binary/handlers/topics/update_topic_handler.rs | 84 +--
.../handlers/users/change_password_handler.rs | 84 ++-
.../binary/handlers/users/create_user_handler.rs | 97 ++--
.../binary/handlers/users/delete_user_handler.rs | 70 +--
.../src/binary/handlers/users/get_user_handler.rs | 78 ++-
.../src/binary/handlers/users/get_users_handler.rs | 67 +--
.../binary/handlers/users/login_user_handler.rs | 88 ++--
.../binary/handlers/users/logout_user_handler.rs | 65 +--
.../handlers/users/update_permissions_handler.rs | 88 ++--
.../binary/handlers/users/update_user_handler.rs | 78 ++-
core/server/src/binary/handlers/utils.rs | 43 --
core/server/src/binary/macros.rs | 114 ----
core/server/src/binary/mapper.rs | 431 ---------------
core/server/src/binary/mod.rs | 6 +-
core/server/src/http/partitions.rs | 4 +-
core/server/src/http/streams.rs | 2 +-
core/server/src/http/topics.rs | 2 +-
core/server/src/quic/listener.rs | 52 +-
core/server/src/shard/execution.rs | 43 +-
core/server/src/shard/handlers.rs | 20 +-
core/server/src/shard/transmission/frame.rs | 8 +-
core/server/src/tcp/connection_handler.rs | 82 +--
core/server/src/websocket/connection_handler.rs | 136 ++---
74 files changed, 2916 insertions(+), 3697 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 02b353962..827bef7a4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3428,18 +3428,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "enum_dispatch"
-version = "0.3.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
-dependencies = [
- "once_cell",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
[[package]]
name = "enumset"
version = "1.1.10"
@@ -5840,6 +5828,7 @@ dependencies = [
"humantime",
"iggy",
"iggy-cli",
+ "iggy_binary_protocol",
"iggy_common",
"iggy_connector_sdk",
"keyring",
@@ -5861,7 +5850,6 @@ dependencies = [
"server",
"socket2 0.6.3",
"sqlx",
- "strum 0.28.0",
"sysinfo 0.38.4",
"tempfile",
"test-case",
@@ -9743,7 +9731,6 @@ dependencies = [
"cyper-axum",
"dashmap",
"dotenvy",
- "enum_dispatch",
"err_trail",
"error_set",
"figlet-rs",
@@ -9753,6 +9740,7 @@ dependencies = [
"hash32 1.0.0",
"human-repr",
"hwlocality",
+ "iggy_binary_protocol",
"iggy_common",
"jsonwebtoken",
"left-right",
diff --git a/Cargo.toml b/Cargo.toml
index 0f8142ef2..5918468b0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -135,7 +135,6 @@ dirs = "6.0.0"
dlopen2 = "0.8.2"
dotenvy = "0.15.7"
elasticsearch = { version = "9.1.0-alpha.1", features = ["rustls-tls"], default-features = false }
-enum_dispatch = "0.3.13"
enumset = "1.1"
env_logger = "0.11.9"
err_trail = { version = "0.11.0", features = ["tracing"] }
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 83d9349ad..b6100cdfb 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -297,7 +297,6 @@ embedded-io: 0.6.1, "Apache-2.0 OR MIT",
encode_unicode: 1.0.0, "Apache-2.0 OR MIT",
encoding_rs: 0.8.35, "(Apache-2.0 OR MIT) AND BSD-3-Clause",
enum-as-inner: 0.6.1, "Apache-2.0 OR MIT",
-enum_dispatch: 0.3.13, "Apache-2.0 OR MIT",
enumset: 1.1.10, "Apache-2.0 OR MIT",
enumset_derive: 0.14.0, "Apache-2.0 OR MIT",
equator: 0.4.2, "MIT",
diff --git a/core/binary_protocol/src/framing.rs b/core/binary_protocol/src/framing.rs
index 981ead667..f303a61ae 100644
--- a/core/binary_protocol/src/framing.rs
+++ b/core/binary_protocol/src/framing.rs
@@ -48,19 +48,40 @@ impl<'a> RequestFrame<'a> {
/// Size of the frame header: `[length:4][code:4]`.
pub const HEADER_SIZE: usize = 8;
+ /// Validate a frame length field and return the payload size.
+ ///
+ /// Transport layers that read the length and code fields incrementally
+ /// (e.g. compio completion-based I/O) can use this to validate the length
+ /// before reading the payload, without buffering the entire frame.
+ ///
+ /// # Errors
+ /// Returns `WireError::Validation` if `frame_length < 4` (too small to
+ /// contain even the command code).
+ pub fn payload_length(frame_length: u32) -> Result<u32, WireError> {
+ frame_length
+ .checked_sub(4)
+ .ok_or(WireError::Validation(Cow::Borrowed(
+ "request frame length must be at least 4 (code size)",
+ )))
+ }
+
+ /// Construct a frame from pre-parsed header fields and a payload slice.
+ ///
+ /// Used by transport layers that read the header incrementally (e.g.
+ /// compio completion-based I/O) and then read the payload separately.
+ #[must_use]
+ pub const fn from_parts(code: u32, payload: &'a [u8]) -> Self {
+ Self { code, payload }
+ }
+
/// Decode a request frame from a complete buffer.
///
/// # Errors
/// Returns `WireError::UnexpectedEof` if the buffer is too short.
pub fn decode(buf: &'a [u8]) -> Result<(Self, usize), WireError> {
- let length = read_u32_le(buf, 0)? as usize;
- if length < 4 {
- return Err(WireError::Validation(Cow::Borrowed(
- "request frame length must be at least 4 (code size)",
- )));
- }
+ let frame_length = read_u32_le(buf, 0)?;
+ let payload_len = Self::payload_length(frame_length)? as usize;
let code = read_u32_le(buf, 4)?;
- let payload_len = length - 4;
let payload = read_bytes(buf, Self::HEADER_SIZE, payload_len)?;
let total = Self::HEADER_SIZE + payload_len;
Ok((Self { code, payload }, total))
@@ -222,6 +243,23 @@ mod tests {
assert!(RequestFrame::decode(&buf).is_err());
}
+ #[test]
+ fn payload_length_valid() {
+ assert_eq!(RequestFrame::payload_length(4).unwrap(), 0);
+ assert_eq!(RequestFrame::payload_length(104).unwrap(), 100);
+ assert_eq!(
+ RequestFrame::payload_length(u32::MAX).unwrap(),
+ u32::MAX - 4
+ );
+ }
+
+ #[test]
+ fn payload_length_too_small() {
+ assert!(RequestFrame::payload_length(0).is_err());
+ assert!(RequestFrame::payload_length(1).is_err());
+ assert!(RequestFrame::payload_length(3).is_err());
+ }
+
#[test]
fn request_encoded_size() {
assert_eq!(RequestFrame::encoded_size(0), Some(8));
diff --git a/core/binary_protocol/src/requests/system/get_snapshot.rs b/core/binary_protocol/src/requests/system/get_snapshot.rs
index 4509158ff..0342cde3f 100644
--- a/core/binary_protocol/src/requests/system/get_snapshot.rs
+++ b/core/binary_protocol/src/requests/system/get_snapshot.rs
@@ -16,24 +16,57 @@
// under the License.
use crate::WireError;
-use crate::codec::{WireDecode, WireEncode};
-use bytes::BytesMut;
+use crate::codec::{WireDecode, WireEncode, read_u8};
+use bytes::{BufMut, BytesMut};
-/// `GetSnapshot` request. Wire format: empty.
+/// `GetSnapshot` request.
+///
+/// Wire format:
+/// ```text
+/// [compression:1][types_count:1][snapshot_type:1]*
+/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct GetSnapshotRequest;
+pub struct GetSnapshotRequest {
+ /// Compression method code (1=Stored, 2=Deflated, 3=Bzip2, 4=Zstd, 5=Lzma, 6=Xz).
+ pub compression: u8,
+ /// Snapshot type codes (1=FilesystemOverview, 2=ProcessList, 3=ResourceUsage,
+ /// 4=Test, 5=ServerLogs, 6=ServerConfig, 100=All).
+ pub snapshot_types: Vec<u8>,
+}
impl WireEncode for GetSnapshotRequest {
fn encoded_size(&self) -> usize {
- 0
+ 1 + 1 + self.snapshot_types.len()
}
- fn encode(&self, _buf: &mut BytesMut) {}
+ fn encode(&self, buf: &mut BytesMut) {
+ buf.put_u8(self.compression);
+ let count =
+ u8::try_from(self.snapshot_types.len()).expect("snapshot_types count exceeds u8::MAX");
+ buf.put_u8(count);
+ for &code in &self.snapshot_types {
+ buf.put_u8(code);
+ }
+ }
}
impl WireDecode for GetSnapshotRequest {
- fn decode(_buf: &[u8]) -> Result<(Self, usize), WireError> {
- Ok((Self, 0))
+ fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
+ let compression = read_u8(buf, 0)?;
+ let types_count = read_u8(buf, 1)? as usize;
+ let mut pos = 2;
+ let mut snapshot_types = Vec::with_capacity(types_count);
+ for _ in 0..types_count {
+ snapshot_types.push(read_u8(buf, pos)?);
+ pos += 1;
+ }
+ Ok((
+ Self {
+ compression,
+ snapshot_types,
+ },
+ pos,
+ ))
}
}
@@ -43,11 +76,54 @@ mod tests {
#[test]
fn roundtrip() {
- let req = GetSnapshotRequest;
+ let req = GetSnapshotRequest {
+ compression: 2,
+ snapshot_types: vec![1, 5],
+ };
+ let bytes = req.to_bytes();
+ assert_eq!(bytes.len(), 4); // 1 + 1 + 2
+ let (decoded, consumed) = GetSnapshotRequest::decode(&bytes).unwrap();
+ assert_eq!(consumed, bytes.len());
+ assert_eq!(decoded, req);
+ }
+
+ #[test]
+ fn roundtrip_empty_types() {
+ let req = GetSnapshotRequest {
+ compression: 1,
+ snapshot_types: vec![],
+ };
+ let bytes = req.to_bytes();
+ assert_eq!(bytes.len(), 2);
+ let (decoded, consumed) = GetSnapshotRequest::decode(&bytes).unwrap();
+ assert_eq!(consumed, 2);
+ assert_eq!(decoded, req);
+ }
+
+ #[test]
+ fn roundtrip_all_type() {
+ let req = GetSnapshotRequest {
+ compression: 2,
+ snapshot_types: vec![100],
+ };
let bytes = req.to_bytes();
- assert!(bytes.is_empty());
let (decoded, consumed) = GetSnapshotRequest::decode(&bytes).unwrap();
- assert_eq!(consumed, 0);
+ assert_eq!(consumed, bytes.len());
assert_eq!(decoded, req);
}
+
+ #[test]
+ fn truncated_returns_error() {
+ let req = GetSnapshotRequest {
+ compression: 2,
+ snapshot_types: vec![1, 2, 3],
+ };
+ let bytes = req.to_bytes();
+ for i in 0..bytes.len() {
+ assert!(
+ GetSnapshotRequest::decode(&bytes[..i]).is_err(),
+ "expected error for truncation at byte {i}"
+ );
+ }
+ }
}
diff --git a/core/binary_protocol/src/requests/users/create_user.rs b/core/binary_protocol/src/requests/users/create_user.rs
index 632e077d9..8f17710c7 100644
--- a/core/binary_protocol/src/requests/users/create_user.rs
+++ b/core/binary_protocol/src/requests/users/create_user.rs
@@ -42,8 +42,10 @@ impl WireEncode for CreateUserRequest {
+ self.password.len()
+ 1 // status
+ 1 // has_permissions
- + 4 // permissions_len (always present)
- + self.permissions.as_ref().map_or(0, WireEncode::encoded_size)
+ + self
+ .permissions
+ .as_ref()
+ .map_or(0, |p| 4 + p.encoded_size())
}
fn encode(&self, buf: &mut BytesMut) {
@@ -60,7 +62,6 @@ impl WireEncode for CreateUserRequest {
buf.put_slice(&perm_bytes);
} else {
buf.put_u8(0);
- buf.put_u32_le(0);
}
}
}
@@ -80,10 +81,9 @@ impl WireDecode for CreateUserRequest {
let has_permissions = read_u8(buf, pos)?;
pos += 1;
- let perm_len = read_u32_le(buf, pos)? as usize;
- pos += 4;
-
- let permissions = if has_permissions == 1 && perm_len > 0 {
+ let permissions = if has_permissions == 1 {
+ let perm_len = read_u32_le(buf, pos)? as usize;
+ pos += 4;
let (perms, consumed) = WirePermissions::decode(&buf[pos..])?;
if consumed != perm_len {
return Err(WireError::Validation(Cow::Owned(format!(
@@ -197,8 +197,8 @@ mod tests {
};
let bytes = req.to_bytes();
// username: [1, b'u'] + password: [1, b'p'] + status: [0]
- // + has_perm: [0] + perm_len: [0,0,0,0]
- let expected: &[u8] = &[1, b'u', 1, b'p', 0, 0, 0, 0, 0, 0];
+ // + has_perm: [0]
+ let expected: &[u8] = &[1, b'u', 1, b'p', 0, 0];
assert_eq!(&bytes[..], expected);
}
}
diff --git a/core/binary_protocol/src/requests/users/update_permissions.rs b/core/binary_protocol/src/requests/users/update_permissions.rs
index 16de85737..c304e1a15 100644
--- a/core/binary_protocol/src/requests/users/update_permissions.rs
+++ b/core/binary_protocol/src/requests/users/update_permissions.rs
@@ -36,8 +36,10 @@ impl WireEncode for UpdatePermissionsRequest {
fn encoded_size(&self) -> usize {
self.user_id.encoded_size()
+ 1 // has_permissions
- + 4 // permissions_len (always present)
- + self.permissions.as_ref().map_or(0, WireEncode::encoded_size)
+ + self
+ .permissions
+ .as_ref()
+ .map_or(0, |p| 4 + p.encoded_size())
}
fn encode(&self, buf: &mut BytesMut) {
@@ -50,7 +52,6 @@ impl WireEncode for UpdatePermissionsRequest {
buf.put_slice(&perm_bytes);
} else {
buf.put_u8(0);
- buf.put_u32_le(0);
}
}
}
@@ -62,10 +63,9 @@ impl WireDecode for UpdatePermissionsRequest {
let has_permissions = read_u8(buf, pos)?;
pos += 1;
- let perm_len = read_u32_le(buf, pos)? as usize;
- pos += 4;
-
- let permissions = if has_permissions == 1 && perm_len > 0 {
+ let permissions = if has_permissions == 1 {
+ let perm_len = read_u32_le(buf, pos)? as usize;
+ pos += 4;
let (perms, consumed) = WirePermissions::decode(&buf[pos..])?;
if consumed != perm_len {
return Err(WireError::Validation(Cow::Owned(format!(
diff --git a/core/integration/Cargo.toml b/core/integration/Cargo.toml
index 9851e5be0..aef357632 100644
--- a/core/integration/Cargo.toml
+++ b/core/integration/Cargo.toml
@@ -42,6 +42,7 @@ harness_derive = { workspace = true }
humantime = { workspace = true }
iggy = { workspace = true }
iggy-cli = { workspace = true }
+iggy_binary_protocol = { workspace = true }
iggy_common = { workspace = true }
iggy_connector_sdk = { workspace = true, features = ["api"] }
keyring = { workspace = true }
@@ -68,7 +69,6 @@ serial_test = { workspace = true }
server = { workspace = true }
socket2 = { workspace = true }
sqlx = { workspace = true }
-strum = { workspace = true }
sysinfo = { workspace = true }
tempfile = { workspace = true }
test-case = { workspace = true }
diff --git a/core/integration/tests/server/scenarios/authentication_scenario.rs b/core/integration/tests/server/scenarios/authentication_scenario.rs
index 4dc440c4f..6e476cf4c 100644
--- a/core/integration/tests/server/scenarios/authentication_scenario.rs
+++ b/core/integration/tests/server/scenarios/authentication_scenario.rs
@@ -23,15 +23,14 @@
//! - LoginUser (authenticate with username/password)
//! - LoginWithPersonalAccessToken (authenticate with PAT)
//!
-//! Uses exhaustive matching on `ServerCommand` to ensure no command is missed
-//! when new commands are added.
+//! Covers all command codes from the dispatch table to ensure no command
+//! is missed when new commands are added.
use crate::server::scenarios::create_client;
use bytes::Bytes;
use iggy::prelude::*;
+use iggy_binary_protocol::dispatch::COMMAND_TABLE;
use integration::harness::{TestHarness, login_root};
-use server::binary::command::ServerCommand;
-use strum::IntoEnumIterator;
const STREAM_NAME: &str = "auth-test-stream";
const TOPIC_NAME: &str = "auth-test-topic";
@@ -103,142 +102,101 @@ pub async fn run(harness: &TestHarness) {
cleanup_test_resources(&client).await;
}
-/// Tests all commands require authentication using exhaustive matching.
-/// If a new command is added to ServerCommand, this match becomes non-exhaustive.
+/// Tests all commands require authentication by iterating the dispatch table.
+/// New entries in `COMMAND_TABLE` will hit the wildcard arm and panic,
+/// forcing an explicit decision about each new command.
async fn test_all_commands_require_auth(client: &IggyClient) {
+ use iggy_binary_protocol::codes::*;
+
let ctx = TestContext::new();
- for cmd in ServerCommand::iter() {
- let (name, result): (&str, Result<(), IggyError>) = match cmd {
- // ================================================================
- // NO AUTH REQUIRED (3 commands)
- // ================================================================
- ServerCommand::Ping(_)
- | ServerCommand::LoginUser(_)
- | ServerCommand::LoginWithPersonalAccessToken(_) => continue,
-
- // ================================================================
- // STATEFUL - NOT SUPPORTED ON HTTP (2 commands)
- // ================================================================
- ServerCommand::JoinConsumerGroup(_) | ServerCommand::LeaveConsumerGroup(_) => continue,
-
- // ================================================================
- // SPECIAL SETUP REQUIRED (3 commands)
- // ================================================================
- ServerCommand::GetSnapshot(_)
- | ServerCommand::DeleteSegments(_)
- | ServerCommand::LogoutUser(_) => continue,
-
- // ================================================================
- // REQUIRES AUTH (39 commands)
- // ================================================================
+ for entry in COMMAND_TABLE {
+ let code = entry.code;
+ let name = entry.name;
+
+ // ================================================================
+ // SKIPPED COMMANDS (8 total)
+ // ================================================================
+ // No auth required
+ if matches!(
+ code,
+ PING_CODE | LOGIN_USER_CODE | LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE
+ ) {
+ continue;
+ }
+ // Stateful - not supported on HTTP
+ if matches!(code, JOIN_CONSUMER_GROUP_CODE | LEAVE_CONSUMER_GROUP_CODE) {
+ continue;
+ }
+ // Special setup required
+ if matches!(
+ code,
+ GET_SNAPSHOT_FILE_CODE | DELETE_SEGMENTS_CODE | LOGOUT_USER_CODE
+ ) {
+ continue;
+ }
+ // ================================================================
+ // REQUIRES AUTH
+ // ================================================================
+ let result: Result<(), IggyError> = match code {
// System
- ServerCommand::GetStats(_) => ("GetStats", client.get_stats().await.map(|_| ())),
- ServerCommand::GetMe(_) => ("GetMe", client.get_me().await.map(|_| ())),
- ServerCommand::GetClient(_) => ("GetClient", client.get_client(1).await.map(|_| ())),
- ServerCommand::GetClients(_) => ("GetClients", client.get_clients().await.map(|_| ())),
- ServerCommand::GetClusterMetadata(_) => (
- "GetClusterMetadata",
- client.get_cluster_metadata().await.map(|_| ()),
- ),
+ GET_STATS_CODE => client.get_stats().await.map(|_| ()),
+ GET_ME_CODE => client.get_me().await.map(|_| ()),
+ GET_CLIENT_CODE => client.get_client(1).await.map(|_| ()),
+ GET_CLIENTS_CODE => client.get_clients().await.map(|_| ()),
+ GET_CLUSTER_METADATA_CODE => client.get_cluster_metadata().await.map(|_| ()),
// Users
- ServerCommand::GetUser(_) => {
- ("GetUser", client.get_user(&ctx.user_id).await.map(|_| ()))
- }
- ServerCommand::GetUsers(_) => ("GetUsers", client.get_users().await.map(|_| ())),
- ServerCommand::CreateUser(_) => (
- "CreateUser",
- client
- .create_user("test", "test", UserStatus::Active, None)
- .await
- .map(|_| ()),
- ),
- ServerCommand::DeleteUser(_) => ("DeleteUser", client.delete_user(&ctx.user_id).await),
- ServerCommand::UpdateUser(_) => (
- "UpdateUser",
- client.update_user(&ctx.user_id, Some("x"), None).await,
- ),
- ServerCommand::UpdatePermissions(_) => (
- "UpdatePermissions",
- client.update_permissions(&ctx.user_id, None).await,
- ),
- ServerCommand::ChangePassword(_) => (
- "ChangePassword",
- client.change_password(&ctx.user_id, "old", "new").await,
- ),
+ GET_USER_CODE => client.get_user(&ctx.user_id).await.map(|_| ()),
+ GET_USERS_CODE => client.get_users().await.map(|_| ()),
+ CREATE_USER_CODE => client
+ .create_user("test", "test", UserStatus::Active, None)
+ .await
+ .map(|_| ()),
+ DELETE_USER_CODE => client.delete_user(&ctx.user_id).await,
+ UPDATE_USER_CODE => client.update_user(&ctx.user_id, Some("x"), None).await,
+ UPDATE_PERMISSIONS_CODE => client.update_permissions(&ctx.user_id, None).await,
+ CHANGE_PASSWORD_CODE => client.change_password(&ctx.user_id, "old", "new").await,
// PAT
- ServerCommand::GetPersonalAccessTokens(_) => (
- "GetPersonalAccessTokens",
- client.get_personal_access_tokens().await.map(|_| ()),
- ),
- ServerCommand::CreatePersonalAccessToken(_) => (
- "CreatePersonalAccessToken",
- client
- .create_personal_access_token("x", PersonalAccessTokenExpiry::NeverExpire)
- .await
- .map(|_| ()),
- ),
- ServerCommand::DeletePersonalAccessToken(_) => (
- "DeletePersonalAccessToken",
- client.delete_personal_access_token("x").await,
- ),
+ GET_PERSONAL_ACCESS_TOKENS_CODE => {
+ client.get_personal_access_tokens().await.map(|_| ())
+ }
+ CREATE_PERSONAL_ACCESS_TOKEN_CODE => client
+ .create_personal_access_token("x", PersonalAccessTokenExpiry::NeverExpire)
+ .await
+ .map(|_| ()),
+ DELETE_PERSONAL_ACCESS_TOKEN_CODE => client.delete_personal_access_token("x").await,
// Streams
- ServerCommand::GetStream(_) => (
- "GetStream",
- client.get_stream(&ctx.stream_id).await.map(|_| ()),
- ),
- ServerCommand::GetStreams(_) => ("GetStreams", client.get_streams().await.map(|_| ())),
- ServerCommand::CreateStream(_) => {
- ("CreateStream", client.create_stream("x").await.map(|_| ()))
- }
- ServerCommand::DeleteStream(_) => {
- ("DeleteStream", client.delete_stream(&ctx.stream_id).await)
- }
- ServerCommand::UpdateStream(_) => (
- "UpdateStream",
- client.update_stream(&ctx.stream_id, "x").await,
- ),
- ServerCommand::PurgeStream(_) => {
- ("PurgeStream", client.purge_stream(&ctx.stream_id).await)
- }
+ GET_STREAM_CODE => client.get_stream(&ctx.stream_id).await.map(|_| ()),
+ GET_STREAMS_CODE => client.get_streams().await.map(|_| ()),
+ CREATE_STREAM_CODE => client.create_stream("x").await.map(|_| ()),
+ DELETE_STREAM_CODE => client.delete_stream(&ctx.stream_id).await,
+ UPDATE_STREAM_CODE => client.update_stream(&ctx.stream_id, "x").await,
+ PURGE_STREAM_CODE => client.purge_stream(&ctx.stream_id).await,
// Topics
- ServerCommand::GetTopic(_) => (
- "GetTopic",
- client
- .get_topic(&ctx.stream_id, &ctx.topic_id)
- .await
- .map(|_| ()),
- ),
- ServerCommand::GetTopics(_) => (
- "GetTopics",
- client.get_topics(&ctx.stream_id).await.map(|_| ()),
- ),
- ServerCommand::CreateTopic(_) => (
- "CreateTopic",
- client
- .create_topic(
- &ctx.stream_id,
- "x",
- 1,
- CompressionAlgorithm::None,
- None,
- IggyExpiry::NeverExpire,
- MaxTopicSize::ServerDefault,
- )
- .await
- .map(|_| ()),
- ),
- ServerCommand::DeleteTopic(_) => (
- "DeleteTopic",
- client.delete_topic(&ctx.stream_id, &ctx.topic_id).await,
- ),
- ServerCommand::UpdateTopic(_) => (
- "UpdateTopic",
+ GET_TOPIC_CODE => client
+ .get_topic(&ctx.stream_id, &ctx.topic_id)
+ .await
+ .map(|_| ()),
+ GET_TOPICS_CODE => client.get_topics(&ctx.stream_id).await.map(|_| ()),
+ CREATE_TOPIC_CODE => client
+ .create_topic(
+ &ctx.stream_id,
+ "x",
+ 1,
+ CompressionAlgorithm::None,
+ None,
+ IggyExpiry::NeverExpire,
+ MaxTopicSize::ServerDefault,
+ )
+ .await
+ .map(|_| ()),
+ DELETE_TOPIC_CODE => client.delete_topic(&ctx.stream_id, &ctx.topic_id).await,
+ UPDATE_TOPIC_CODE => {
client
.update_topic(
&ctx.stream_id,
@@ -249,118 +207,93 @@ async fn test_all_commands_require_auth(client: &IggyClient) {
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
- .await,
- ),
- ServerCommand::PurgeTopic(_) => (
- "PurgeTopic",
- client.purge_topic(&ctx.stream_id, &ctx.topic_id).await,
- ),
+ .await
+ }
+ PURGE_TOPIC_CODE => client.purge_topic(&ctx.stream_id, &ctx.topic_id).await,
// Partitions
- ServerCommand::CreatePartitions(_) => (
- "CreatePartitions",
+ CREATE_PARTITIONS_CODE => {
client
.create_partitions(&ctx.stream_id, &ctx.topic_id, 1)
- .await,
- ),
- ServerCommand::DeletePartitions(_) => (
- "DeletePartitions",
+ .await
+ }
+ DELETE_PARTITIONS_CODE => {
client
.delete_partitions(&ctx.stream_id, &ctx.topic_id, 1)
- .await,
- ),
+ .await
+ }
// Messages
- ServerCommand::SendMessages(_) => {
+ SEND_MESSAGES_CODE => {
let mut msgs = vec![
IggyMessage::builder()
.payload(Bytes::from("x"))
.build()
.unwrap(),
];
- (
- "SendMessages",
- client
- .send_messages(
- &ctx.stream_id,
- &ctx.topic_id,
- &Partitioning::partition_id(0),
- &mut msgs,
- )
- .await,
- )
- }
- ServerCommand::PollMessages(_) => (
- "PollMessages",
client
- .poll_messages(
+ .send_messages(
&ctx.stream_id,
&ctx.topic_id,
- Some(0),
- &ctx.consumer,
- &PollingStrategy::offset(0),
- 1,
- false,
+ &Partitioning::partition_id(0),
+ &mut msgs,
)
.await
- .map(|_| ()),
- ),
- ServerCommand::FlushUnsavedBuffer(_) => (
- "FlushUnsavedBuffer",
+ }
+ POLL_MESSAGES_CODE => client
+ .poll_messages(
+ &ctx.stream_id,
+ &ctx.topic_id,
+ Some(0),
+ &ctx.consumer,
+ &PollingStrategy::offset(0),
+ 1,
+ false,
+ )
+ .await
+ .map(|_| ()),
+ FLUSH_UNSAVED_BUFFER_CODE => {
client
.flush_unsaved_buffer(&ctx.stream_id, &ctx.topic_id, 0, false)
- .await,
- ),
+ .await
+ }
// Consumer Offsets
- ServerCommand::GetConsumerOffset(_) => (
- "GetConsumerOffset",
- client
- .get_consumer_offset(&ctx.consumer, &ctx.stream_id, &ctx.topic_id, Some(0))
- .await
- .map(|_| ()),
- ),
- ServerCommand::StoreConsumerOffset(_) => (
- "StoreConsumerOffset",
+ GET_CONSUMER_OFFSET_CODE => client
+ .get_consumer_offset(&ctx.consumer, &ctx.stream_id, &ctx.topic_id, Some(0))
+ .await
+ .map(|_| ()),
+ STORE_CONSUMER_OFFSET_CODE => {
client
.store_consumer_offset(&ctx.consumer, &ctx.stream_id, &ctx.topic_id, Some(0), 0)
- .await,
- ),
- ServerCommand::DeleteConsumerOffset(_) => (
- "DeleteConsumerOffset",
+ .await
+ }
+ DELETE_CONSUMER_OFFSET_CODE => {
client
.delete_consumer_offset(&ctx.consumer, &ctx.stream_id, &ctx.topic_id, Some(0))
- .await,
- ),
+ .await
+ }
// Consumer Groups
- ServerCommand::GetConsumerGroup(_) => (
- "GetConsumerGroup",
- client
- .get_consumer_group(&ctx.stream_id, &ctx.topic_id, &ctx.group_id)
- .await
- .map(|_| ()),
- ),
- ServerCommand::GetConsumerGroups(_) => (
- "GetConsumerGroups",
- client
- .get_consumer_groups(&ctx.stream_id, &ctx.topic_id)
- .await
- .map(|_| ()),
- ),
- ServerCommand::CreateConsumerGroup(_) => (
- "CreateConsumerGroup",
- client
- .create_consumer_group(&ctx.stream_id, &ctx.topic_id, "x")
- .await
- .map(|_| ()),
- ),
- ServerCommand::DeleteConsumerGroup(_) => (
- "DeleteConsumerGroup",
+ GET_CONSUMER_GROUP_CODE => client
+ .get_consumer_group(&ctx.stream_id, &ctx.topic_id, &ctx.group_id)
+ .await
+ .map(|_| ()),
+ GET_CONSUMER_GROUPS_CODE => client
+ .get_consumer_groups(&ctx.stream_id, &ctx.topic_id)
+ .await
+ .map(|_| ()),
+ CREATE_CONSUMER_GROUP_CODE => client
+ .create_consumer_group(&ctx.stream_id, &ctx.topic_id, "x")
+ .await
+ .map(|_| ()),
+ DELETE_CONSUMER_GROUP_CODE => {
client
.delete_consumer_group(&ctx.stream_id, &ctx.topic_id, &ctx.group_id)
- .await,
- ),
+ .await
+ }
+
+ _ => panic!("Unhandled command code {code} ({name}) in auth test"),
};
assert_unauthenticated(result, name);
diff --git a/core/server/Cargo.toml b/core/server/Cargo.toml
index c852010f3..62027ca6c 100644
--- a/core/server/Cargo.toml
+++ b/core/server/Cargo.toml
@@ -55,7 +55,6 @@ cyper = { workspace = true }
cyper-axum = { workspace = true }
dashmap = { workspace = true }
dotenvy = { workspace = true }
-enum_dispatch = { workspace = true }
err_trail = { workspace = true }
error_set = { workspace = true }
figlet-rs = { workspace = true }
@@ -64,6 +63,7 @@ fs2 = { workspace = true }
futures = { workspace = true }
hash32 = { workspace = true }
human-repr = { workspace = true }
+iggy_binary_protocol = { workspace = true }
iggy_common = { workspace = true }
jsonwebtoken = { workspace = true }
left-right = { workspace = true }
diff --git a/core/server/src/binary/command.rs b/core/server/src/binary/command.rs
deleted file mode 100644
index cac319f59..000000000
--- a/core/server/src/binary/command.rs
+++ /dev/null
@@ -1,432 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-
-use crate::define_server_command_enum;
-use crate::shard::IggyShard;
-use crate::streaming::session::Session;
-use bytes::{BufMut, Bytes, BytesMut};
-use enum_dispatch::enum_dispatch;
-use iggy_common::SenderKind;
-use iggy_common::change_password::ChangePassword;
-use iggy_common::create_consumer_group::CreateConsumerGroup;
-use iggy_common::create_partitions::CreatePartitions;
-use iggy_common::create_personal_access_token::CreatePersonalAccessToken;
-use iggy_common::create_stream::CreateStream;
-use iggy_common::create_topic::CreateTopic;
-use iggy_common::create_user::CreateUser;
-use iggy_common::delete_consumer_group::DeleteConsumerGroup;
-use iggy_common::delete_consumer_offset::DeleteConsumerOffset;
-use iggy_common::delete_partitions::DeletePartitions;
-use iggy_common::delete_personal_access_token::DeletePersonalAccessToken;
-use iggy_common::delete_segments::DeleteSegments;
-use iggy_common::delete_stream::DeleteStream;
-use iggy_common::delete_topic::DeleteTopic;
-use iggy_common::delete_user::DeleteUser;
-use iggy_common::get_client::GetClient;
-use iggy_common::get_clients::GetClients;
-use iggy_common::get_cluster_metadata::GetClusterMetadata;
-use iggy_common::get_consumer_group::GetConsumerGroup;
-use iggy_common::get_consumer_groups::GetConsumerGroups;
-use iggy_common::get_consumer_offset::GetConsumerOffset;
-use iggy_common::get_me::GetMe;
-use iggy_common::get_personal_access_tokens::GetPersonalAccessTokens;
-use iggy_common::get_snapshot::GetSnapshot;
-use iggy_common::get_stats::GetStats;
-use iggy_common::get_stream::GetStream;
-use iggy_common::get_streams::GetStreams;
-use iggy_common::get_topic::GetTopic;
-use iggy_common::get_topics::GetTopics;
-use iggy_common::get_user::GetUser;
-use iggy_common::get_users::GetUsers;
-use iggy_common::join_consumer_group::JoinConsumerGroup;
-use iggy_common::leave_consumer_group::LeaveConsumerGroup;
-use iggy_common::login_user::LoginUser;
-use iggy_common::login_with_personal_access_token::LoginWithPersonalAccessToken;
-use iggy_common::logout_user::LogoutUser;
-use iggy_common::ping::Ping;
-use iggy_common::purge_stream::PurgeStream;
-use iggy_common::purge_topic::PurgeTopic;
-use iggy_common::store_consumer_offset::StoreConsumerOffset;
-use iggy_common::update_permissions::UpdatePermissions;
-use iggy_common::update_stream::UpdateStream;
-use iggy_common::update_topic::UpdateTopic;
-use iggy_common::update_user::UpdateUser;
-use iggy_common::*;
-use std::rc::Rc;
-use strum::{EnumIter, EnumString};
-use tracing::error;
-
-define_server_command_enum! {
- Ping(Ping), PING_CODE, PING, false;
- GetStats(GetStats), GET_STATS_CODE, GET_STATS, false;
- GetMe(GetMe), GET_ME_CODE, GET_ME, false;
- GetClient(GetClient), GET_CLIENT_CODE, GET_CLIENT, true;
- GetClients(GetClients), GET_CLIENTS_CODE, GET_CLIENTS, false;
- GetSnapshot(GetSnapshot), GET_SNAPSHOT_FILE_CODE, GET_SNAPSHOT_FILE, false;
- GetClusterMetadata(GetClusterMetadata), GET_CLUSTER_METADATA_CODE, GET_CLUSTER_METADATA, false;
- PollMessages(PollMessages), POLL_MESSAGES_CODE, POLL_MESSAGES, true;
- FlushUnsavedBuffer(FlushUnsavedBuffer), FLUSH_UNSAVED_BUFFER_CODE, FLUSH_UNSAVED_BUFFER, true;
- GetUser(GetUser), GET_USER_CODE, GET_USER, true;
- GetUsers(GetUsers), GET_USERS_CODE, GET_USERS, false;
- CreateUser(CreateUser), CREATE_USER_CODE, CREATE_USER, true;
- DeleteUser(DeleteUser), DELETE_USER_CODE, DELETE_USER, true;
- UpdateUser(UpdateUser), UPDATE_USER_CODE, UPDATE_USER, true;
- UpdatePermissions(UpdatePermissions), UPDATE_PERMISSIONS_CODE, UPDATE_PERMISSIONS, true;
- ChangePassword(ChangePassword), CHANGE_PASSWORD_CODE, CHANGE_PASSWORD, true;
- LoginUser(LoginUser), LOGIN_USER_CODE, LOGIN_USER, true;
- LogoutUser(LogoutUser), LOGOUT_USER_CODE, LOGOUT_USER, false;
- GetPersonalAccessTokens(GetPersonalAccessTokens), GET_PERSONAL_ACCESS_TOKENS_CODE, GET_PERSONAL_ACCESS_TOKENS, false;
- CreatePersonalAccessToken(CreatePersonalAccessToken), CREATE_PERSONAL_ACCESS_TOKEN_CODE, CREATE_PERSONAL_ACCESS_TOKEN, true;
- DeletePersonalAccessToken(DeletePersonalAccessToken), DELETE_PERSONAL_ACCESS_TOKEN_CODE, DELETE_PERSONAL_ACCESS_TOKEN, false;
- LoginWithPersonalAccessToken(LoginWithPersonalAccessToken), LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE, LOGIN_WITH_PERSONAL_ACCESS_TOKEN, true;
- SendMessages(SendMessages), SEND_MESSAGES_CODE, SEND_MESSAGES, false;
- GetConsumerOffset(GetConsumerOffset), GET_CONSUMER_OFFSET_CODE, GET_CONSUMER_OFFSET, true;
- StoreConsumerOffset(StoreConsumerOffset), STORE_CONSUMER_OFFSET_CODE, STORE_CONSUMER_OFFSET, true;
- DeleteConsumerOffset(DeleteConsumerOffset), DELETE_CONSUMER_OFFSET_CODE, DELETE_CONSUMER_OFFSET, true;
- GetStream(GetStream), GET_STREAM_CODE, GET_STREAM, true;
- GetStreams(GetStreams), GET_STREAMS_CODE, GET_STREAMS, false;
- CreateStream(CreateStream), CREATE_STREAM_CODE, CREATE_STREAM, true;
- DeleteStream(DeleteStream), DELETE_STREAM_CODE, DELETE_STREAM, true;
- UpdateStream(UpdateStream), UPDATE_STREAM_CODE, UPDATE_STREAM, true;
- PurgeStream(PurgeStream), PURGE_STREAM_CODE, PURGE_STREAM, true;
- GetTopic(GetTopic), GET_TOPIC_CODE, GET_TOPIC, true;
- GetTopics(GetTopics), GET_TOPICS_CODE, GET_TOPICS, false;
- CreateTopic(CreateTopic), CREATE_TOPIC_CODE, CREATE_TOPIC, true;
- DeleteTopic(DeleteTopic), DELETE_TOPIC_CODE, DELETE_TOPIC, true;
- UpdateTopic(UpdateTopic), UPDATE_TOPIC_CODE, UPDATE_TOPIC, true;
- PurgeTopic(PurgeTopic), PURGE_TOPIC_CODE, PURGE_TOPIC, true;
- CreatePartitions(CreatePartitions), CREATE_PARTITIONS_CODE, CREATE_PARTITIONS, true;
- DeletePartitions(DeletePartitions), DELETE_PARTITIONS_CODE, DELETE_PARTITIONS, true;
- DeleteSegments(DeleteSegments), DELETE_SEGMENTS_CODE, DELETE_SEGMENTS, true;
- GetConsumerGroup(GetConsumerGroup), GET_CONSUMER_GROUP_CODE, GET_CONSUMER_GROUP, true;
- GetConsumerGroups(GetConsumerGroups), GET_CONSUMER_GROUPS_CODE, GET_CONSUMER_GROUPS, false;
- CreateConsumerGroup(CreateConsumerGroup), CREATE_CONSUMER_GROUP_CODE, CREATE_CONSUMER_GROUP, true;
- DeleteConsumerGroup(DeleteConsumerGroup), DELETE_CONSUMER_GROUP_CODE, DELETE_CONSUMER_GROUP, true;
- JoinConsumerGroup(JoinConsumerGroup), JOIN_CONSUMER_GROUP_CODE, JOIN_CONSUMER_GROUP, true;
- LeaveConsumerGroup(LeaveConsumerGroup), LEAVE_CONSUMER_GROUP_CODE, LEAVE_CONSUMER_GROUP, true;
-}
-
-/// Indicates whether a command handler completed normally or migrated the connection.
-pub enum HandlerResult {
- /// Command completed, connection stays on current shard.
- Finished,
-
- /// Connection was migrated to another shard. Source shard should exit without cleanup.
- Migrated { to_shard: u16 },
-}
-
-#[enum_dispatch]
-pub trait ServerCommandHandler {
- /// Return the command code
- fn code(&self) -> u32;
-
- /// Handle the command execution
- #[allow(async_fn_in_trait)]
- async fn handle(
- self,
- sender: &mut SenderKind,
- length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError>;
-}
-
-pub trait BinaryServerCommand {
- /// Parse command from sender
- #[allow(async_fn_in_trait)]
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError>
- where
- Self: Sized;
-}
-
-fn as_bytes<T: Command>(command: &T) -> Bytes {
- let payload = command.to_bytes();
- let mut bytes = BytesMut::with_capacity(4 + payload.len());
- bytes.put_u32_le(command.code());
- bytes.put_slice(&payload);
- bytes.freeze()
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn should_be_serialized_as_bytes_and_deserialized_from_bytes() {
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::Ping(Ping::default()),
- PING_CODE,
- &Ping::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetStats(GetStats::default()),
- GET_STATS_CODE,
- &GetStats::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetClusterMetadata(GetClusterMetadata::default()),
- GET_CLUSTER_METADATA_CODE,
- &GetClusterMetadata::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetMe(GetMe::default()),
- GET_ME_CODE,
- &GetMe::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetClient(GetClient::default()),
- GET_CLIENT_CODE,
- &GetClient::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetClients(GetClients::default()),
- GET_CLIENTS_CODE,
- &GetClients::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetUser(GetUser::default()),
- GET_USER_CODE,
- &GetUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetUsers(GetUsers::default()),
- GET_USERS_CODE,
- &GetUsers::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreateUser(CreateUser::default()),
- CREATE_USER_CODE,
- &CreateUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeleteUser(DeleteUser::default()),
- DELETE_USER_CODE,
- &DeleteUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::UpdateUser(UpdateUser::default()),
- UPDATE_USER_CODE,
- &UpdateUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::UpdatePermissions(UpdatePermissions::default()),
- UPDATE_PERMISSIONS_CODE,
- &UpdatePermissions::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::ChangePassword(ChangePassword::default()),
- CHANGE_PASSWORD_CODE,
- &ChangePassword::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::LoginUser(LoginUser::default()),
- LOGIN_USER_CODE,
- &LoginUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::LogoutUser(LogoutUser::default()),
- LOGOUT_USER_CODE,
- &LogoutUser::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetPersonalAccessTokens(GetPersonalAccessTokens::default()),
- GET_PERSONAL_ACCESS_TOKENS_CODE,
- &GetPersonalAccessTokens::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreatePersonalAccessToken(CreatePersonalAccessToken::default()),
- CREATE_PERSONAL_ACCESS_TOKEN_CODE,
- &CreatePersonalAccessToken::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeletePersonalAccessToken(DeletePersonalAccessToken::default()),
- DELETE_PERSONAL_ACCESS_TOKEN_CODE,
- &DeletePersonalAccessToken::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::LoginWithPersonalAccessToken(LoginWithPersonalAccessToken::default()),
- LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE,
- &LoginWithPersonalAccessToken::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::PollMessages(PollMessages::default()),
- POLL_MESSAGES_CODE,
- &PollMessages::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::StoreConsumerOffset(StoreConsumerOffset::default()),
- STORE_CONSUMER_OFFSET_CODE,
- &StoreConsumerOffset::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetConsumerOffset(GetConsumerOffset::default()),
- GET_CONSUMER_OFFSET_CODE,
- &GetConsumerOffset::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetStream(GetStream::default()),
- GET_STREAM_CODE,
- &GetStream::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetStreams(GetStreams::default()),
- GET_STREAMS_CODE,
- &GetStreams::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreateStream(CreateStream::default()),
- CREATE_STREAM_CODE,
- &CreateStream::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeleteStream(DeleteStream::default()),
- DELETE_STREAM_CODE,
- &DeleteStream::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::UpdateStream(UpdateStream::default()),
- UPDATE_STREAM_CODE,
- &UpdateStream::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::PurgeStream(PurgeStream::default()),
- PURGE_STREAM_CODE,
- &PurgeStream::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetTopic(GetTopic::default()),
- GET_TOPIC_CODE,
- &GetTopic::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetTopics(GetTopics::default()),
- GET_TOPICS_CODE,
- &GetTopics::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreateTopic(CreateTopic::default()),
- CREATE_TOPIC_CODE,
- &CreateTopic::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeleteTopic(DeleteTopic::default()),
- DELETE_TOPIC_CODE,
- &DeleteTopic::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::UpdateTopic(UpdateTopic::default()),
- UPDATE_TOPIC_CODE,
- &UpdateTopic::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::PurgeTopic(PurgeTopic::default()),
- PURGE_TOPIC_CODE,
- &PurgeTopic::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreatePartitions(CreatePartitions::default()),
- CREATE_PARTITIONS_CODE,
- &CreatePartitions::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeletePartitions(DeletePartitions::default()),
- DELETE_PARTITIONS_CODE,
- &DeletePartitions::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeleteSegments(DeleteSegments::default()),
- DELETE_SEGMENTS_CODE,
- &DeleteSegments::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetConsumerGroup(GetConsumerGroup::default()),
- GET_CONSUMER_GROUP_CODE,
- &GetConsumerGroup::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::GetConsumerGroups(GetConsumerGroups::default()),
- GET_CONSUMER_GROUPS_CODE,
- &GetConsumerGroups::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::CreateConsumerGroup(CreateConsumerGroup::default()),
- CREATE_CONSUMER_GROUP_CODE,
- &CreateConsumerGroup::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::DeleteConsumerGroup(DeleteConsumerGroup::default()),
- DELETE_CONSUMER_GROUP_CODE,
- &DeleteConsumerGroup::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::JoinConsumerGroup(JoinConsumerGroup::default()),
- JOIN_CONSUMER_GROUP_CODE,
- &JoinConsumerGroup::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::LeaveConsumerGroup(LeaveConsumerGroup::default()),
- LEAVE_CONSUMER_GROUP_CODE,
- &LeaveConsumerGroup::default(),
- );
- assert_serialized_as_bytes_and_deserialized_from_bytes(
- &ServerCommand::FlushUnsavedBuffer(FlushUnsavedBuffer::default()),
- FLUSH_UNSAVED_BUFFER_CODE,
- &FlushUnsavedBuffer::default(),
- );
- }
-
- fn assert_serialized_as_bytes_and_deserialized_from_bytes(
- command: &ServerCommand,
- code: u32,
- payload: &dyn Command,
- ) {
- assert_serialized_as_bytes(command, code, payload);
- assert_deserialized_from_bytes(command, code, payload);
- }
-
- fn assert_serialized_as_bytes(
- server_command: &ServerCommand,
- code: u32,
- command: &dyn Command,
- ) {
- let payload = command.to_bytes();
- let mut bytes = BytesMut::with_capacity(4 + payload.len());
- bytes.put_u32_le(code);
- bytes.put_slice(&payload);
- assert_eq!(server_command.to_bytes(), bytes);
- }
-
- fn assert_deserialized_from_bytes(
- command: &ServerCommand,
- command_id: u32,
- payload: &dyn Command,
- ) {
- let payload = payload.to_bytes();
- let mut bytes = BytesMut::with_capacity(payload.len());
- bytes.put_slice(&payload);
- let bytes = Bytes::from(bytes);
- let deserialized = ServerCommand::from_code_and_payload(command_id, bytes).unwrap();
- // SecretString doesn't implement PartialEq, so we compare serialized bytes instead.
- // This is sufficient since to_bytes() covers all wire-protocol-relevant fields.
- assert_eq!(deserialized.to_bytes(), command.to_bytes());
- }
-}
diff --git a/core/server/src/binary/dispatch.rs b/core/server/src/binary/dispatch.rs
new file mode 100644
index 000000000..2ef985e1d
--- /dev/null
+++ b/core/server/src/binary/dispatch.rs
@@ -0,0 +1,581 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+use crate::binary::handlers;
+use crate::shard::IggyShard;
+use crate::streaming::session::Session;
+use ahash::AHashMap;
+use bytes::BytesMut;
+use iggy_binary_protocol::RequestFrame;
+use iggy_binary_protocol::codec::WireDecode;
+use iggy_binary_protocol::codes::*;
+use iggy_binary_protocol::primitives::permissions::{
+ WireGlobalPermissions, WirePermissions, WireStreamPermissions, WireTopicPermissions,
+};
+use iggy_binary_protocol::requests::consumer_groups::*;
+use iggy_binary_protocol::requests::consumer_offsets::*;
+use iggy_binary_protocol::requests::messages::*;
+use iggy_binary_protocol::requests::partitions::*;
+use iggy_binary_protocol::requests::personal_access_tokens::*;
+use iggy_binary_protocol::requests::segments::*;
+use iggy_binary_protocol::requests::streams::*;
+use iggy_binary_protocol::requests::system::*;
+use iggy_binary_protocol::requests::topics::*;
+use iggy_binary_protocol::requests::users::*;
+use iggy_common::{
+ Consumer, ConsumerKind, GlobalPermissions, Identifier, IggyError, Permissions, PollingKind,
+ PollingStrategy, SenderKind, StreamPermissions, TopicPermissions,
+};
+use std::rc::Rc;
+use tracing::{error, warn};
+
+/// Result of handling a command. Most commands return `Finished`.
+/// `SendMessages` may migrate the TCP connection to another shard.
+pub enum HandlerResult {
+ Finished,
+ Migrated { to_shard: u16 },
+}
+
+/// Read the full payload from the sender into a buffer.
+pub async fn read_payload(sender: &mut SenderKind, length: u32) -> Result<BytesMut, IggyError> {
+ if length > MAX_CONTROL_FRAME_PAYLOAD {
+ return Err(IggyError::InvalidCommand);
+ }
+ let mut buffer = BytesMut::with_capacity(length as usize);
+ // SAFETY: when length > 0, sender.read() fills exactly `length` bytes
+ // before returning Ok. On error the buffer is dropped without being read.
+ // When length == 0, set_len(0) is a no-op (no uninitialized bytes exposed).
+ unsafe {
+ buffer.set_len(length as usize);
+ }
+ if length > 0 {
+ let (result, buf) = sender.read(buffer).await;
+ result?;
+ buffer = buf;
+ }
+ Ok(buffer)
+}
+
+fn decode<T: WireDecode>(payload: &[u8]) -> Result<T, IggyError> {
+ use iggy_binary_protocol::error::WireError;
+
+ let (val, consumed) = T::decode(payload).map_err(|e| {
+ warn!("wire decode error: {e}");
+ match e {
+ WireError::PayloadTooLarge { .. } => IggyError::InvalidSizeBytes,
+ WireError::Validation(_) => IggyError::InvalidFormat,
+ _ => IggyError::InvalidCommand,
+ }
+ })?;
+ if consumed != payload.len() {
+ warn!(
+ "wire decode: {} trailing bytes (consumed {consumed}, payload {})",
+ payload.len() - consumed,
+ payload.len()
+ );
+ }
+ Ok(val)
+}
+
+/// Convert a `WireIdentifier` to the domain `Identifier`.
+pub fn wire_id_to_identifier(
+ wire: &iggy_binary_protocol::WireIdentifier,
+) -> Result<Identifier, IggyError> {
+ match wire {
+ iggy_binary_protocol::WireIdentifier::Numeric(id) => Identifier::numeric(*id),
+ iggy_binary_protocol::WireIdentifier::String(name) => Identifier::named(name.as_str()),
+ }
+}
+
+/// Convert a `WireConsumer` to the domain `Consumer`.
+pub fn wire_consumer_to_consumer(
+ wire: &iggy_binary_protocol::WireConsumer,
+) -> Result<Consumer, IggyError> {
+ let id = wire_id_to_identifier(&wire.id)?;
+ let kind = ConsumerKind::from_code(wire.kind)?;
+ Ok(Consumer { kind, id })
+}
+
+/// Convert a `WirePollingStrategy` to the domain `PollingStrategy`.
+pub fn wire_polling_to_strategy(
+ wire: &iggy_binary_protocol::WirePollingStrategy,
+) -> Result<PollingStrategy, IggyError> {
+ Ok(PollingStrategy {
+ kind: PollingKind::from_code(wire.kind)?,
+ value: wire.value,
+ })
+}
+
+fn wire_topic_to_domain(wt: &WireTopicPermissions) -> TopicPermissions {
+ TopicPermissions {
+ manage_topic: wt.manage_topic,
+ read_topic: wt.read_topic,
+ poll_messages: wt.poll_messages,
+ send_messages: wt.send_messages,
+ }
+}
+
+fn wire_stream_to_domain(ws: &WireStreamPermissions) -> StreamPermissions {
+ let topics = if ws.topics.is_empty() {
+ None
+ } else {
+ let mut map = AHashMap::with_capacity(ws.topics.len());
+ for wt in &ws.topics {
+ map.insert(wt.topic_id as usize, wire_topic_to_domain(wt));
+ }
+ Some(map)
+ };
+ StreamPermissions {
+ manage_stream: ws.manage_stream,
+ read_stream: ws.read_stream,
+ manage_topics: ws.manage_topics,
+ read_topics: ws.read_topics,
+ poll_messages: ws.poll_messages,
+ send_messages: ws.send_messages,
+ topics,
+ }
+}
+
+/// Convert `WirePermissions` to the domain `Permissions`.
+pub fn wire_permissions_to_permissions(wp: &WirePermissions) -> Permissions {
+ let streams = if wp.streams.is_empty() {
+ None
+ } else {
+ let mut map = AHashMap::with_capacity(wp.streams.len());
+ for ws in &wp.streams {
+ map.insert(ws.stream_id as usize, wire_stream_to_domain(ws));
+ }
+ Some(map)
+ };
+ Permissions {
+ global: GlobalPermissions {
+ manage_servers: wp.global.manage_servers,
+ read_servers: wp.global.read_servers,
+ manage_users: wp.global.manage_users,
+ read_users: wp.global.read_users,
+ manage_streams: wp.global.manage_streams,
+ read_streams: wp.global.read_streams,
+ manage_topics: wp.global.manage_topics,
+ read_topics: wp.global.read_topics,
+ poll_messages: wp.global.poll_messages,
+ send_messages: wp.global.send_messages,
+ },
+ streams,
+ }
+}
+
+fn domain_topic_to_wire(topic_id: usize, tp: &TopicPermissions) -> WireTopicPermissions {
+ WireTopicPermissions {
+ topic_id: topic_id as u32,
+ manage_topic: tp.manage_topic,
+ read_topic: tp.read_topic,
+ poll_messages: tp.poll_messages,
+ send_messages: tp.send_messages,
+ }
+}
+
+fn domain_stream_to_wire(stream_id: usize, sp: &StreamPermissions) -> WireStreamPermissions {
+ let topics: Vec<WireTopicPermissions> = sp
+ .topics
+ .as_ref()
+ .map(|map| {
+ map.iter()
+ .map(|(&tid, tp)| domain_topic_to_wire(tid, tp))
+ .collect()
+ })
+ .unwrap_or_default();
+ WireStreamPermissions {
+ stream_id: stream_id as u32,
+ manage_stream: sp.manage_stream,
+ read_stream: sp.read_stream,
+ manage_topics: sp.manage_topics,
+ read_topics: sp.read_topics,
+ poll_messages: sp.poll_messages,
+ send_messages: sp.send_messages,
+ topics,
+ }
+}
+
+/// Convert domain `Permissions` to `WirePermissions`.
+pub fn domain_permissions_to_wire(perms: &Permissions) -> WirePermissions {
+ let streams: Vec<WireStreamPermissions> = perms
+ .streams
+ .as_ref()
+ .map(|map| {
+ map.iter()
+ .map(|(&sid, sp)| domain_stream_to_wire(sid, sp))
+ .collect()
+ })
+ .unwrap_or_default();
+ WirePermissions {
+ global: WireGlobalPermissions {
+ manage_servers: perms.global.manage_servers,
+ read_servers: perms.global.read_servers,
+ manage_users: perms.global.manage_users,
+ read_users: perms.global.read_users,
+ manage_streams: perms.global.manage_streams,
+ read_streams: perms.global.read_streams,
+ manage_topics: perms.global.manage_topics,
+ read_topics: perms.global.read_topics,
+ poll_messages: perms.global.poll_messages,
+ send_messages: perms.global.send_messages,
+ },
+ streams,
+ }
+}
+
+/// Maximum payload size for control-plane commands (non-SendMessages).
+/// Prevents OOM from malicious clients sending `length = u32::MAX`.
+/// SendMessages has its own size validation via `total_payload_size` checks.
+pub const MAX_CONTROL_FRAME_PAYLOAD: u32 = 10 * 1024 * 1024; // 10 MB
+
+/// Dispatch a SendMessages command with staged socket reads (zero-copy path).
+///
+/// Called by transport layers when the command code is `SEND_MESSAGES_CODE`.
+/// The handler reads metadata, indexes, and messages directly from the socket
+/// into separate `PooledBuffer`s for zero-copy partition append.
+pub async fn dispatch_send_messages(
+ sender: &mut SenderKind,
+ payload_length: u32,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ handlers::messages::send_messages_handler::handle_send_messages(
+ sender,
+ payload_length,
+ session,
+ shard,
+ )
+ .await
+}
+
+/// Central command dispatch for a decoded request frame.
+///
+/// Transport layers read the 8-byte header, validate via
+/// `RequestFrame::payload_length()`, read the full payload, construct a
+/// `RequestFrame::from_parts(code, frame.payload)`, and pass it here.
+///
+/// SendMessages is handled separately via `dispatch_send_messages()`.
+#[allow(clippy::too_many_lines)]
+pub async fn dispatch(
+ frame: RequestFrame<'_>,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ match frame.code {
+ // System
+ PING_CODE => {
+ handlers::system::ping_handler::handle_ping(sender, session, shard).await
+ }
+ GET_STATS_CODE => {
+ handlers::system::get_stats_handler::handle_get_stats(sender, session, shard).await
+ }
+ GET_ME_CODE => {
+ handlers::system::get_me_handler::handle_get_me(sender, session, shard).await
+ }
+ GET_CLIENT_CODE => {
+ let req: GetClientRequest = decode(frame.payload)?;
+ handlers::system::get_client_handler::handle_get_client(req, sender, session, shard)
+ .await
+ }
+ GET_CLIENTS_CODE => {
+ handlers::system::get_clients_handler::handle_get_clients(sender, session, shard).await
+ }
+ GET_SNAPSHOT_FILE_CODE => {
+ let req: GetSnapshotRequest = decode(frame.payload)?;
+ handlers::system::get_snapshot_handler::handle_get_snapshot(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ GET_CLUSTER_METADATA_CODE => {
+ handlers::cluster::get_cluster_metadata_handler::handle_get_cluster_metadata(
+ sender, session, shard,
+ )
+ .await
+ }
+
+ // Streams
+ GET_STREAM_CODE => {
+ let req: GetStreamRequest = decode(frame.payload)?;
+ handlers::streams::get_stream_handler::handle_get_stream(req, sender, session, shard)
+ .await
+ }
+ GET_STREAMS_CODE => {
+ handlers::streams::get_streams_handler::handle_get_streams(sender, session, shard)
+ .await
+ }
+ CREATE_STREAM_CODE => {
+ let req: CreateStreamRequest = decode(frame.payload)?;
+ handlers::streams::create_stream_handler::handle_create_stream(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_STREAM_CODE => {
+ let req: DeleteStreamRequest = decode(frame.payload)?;
+ handlers::streams::delete_stream_handler::handle_delete_stream(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ UPDATE_STREAM_CODE => {
+ let req: UpdateStreamRequest = decode(frame.payload)?;
+ handlers::streams::update_stream_handler::handle_update_stream(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ PURGE_STREAM_CODE => {
+ let req: PurgeStreamRequest = decode(frame.payload)?;
+ handlers::streams::purge_stream_handler::handle_purge_stream(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Topics
+ GET_TOPIC_CODE => {
+ let req: GetTopicRequest = decode(frame.payload)?;
+ handlers::topics::get_topic_handler::handle_get_topic(req, sender, session, shard)
+ .await
+ }
+ GET_TOPICS_CODE => {
+ let req: GetTopicsRequest = decode(frame.payload)?;
+ handlers::topics::get_topics_handler::handle_get_topics(req, sender, session, shard)
+ .await
+ }
+ CREATE_TOPIC_CODE => {
+ let req: CreateTopicRequest = decode(frame.payload)?;
+ handlers::topics::create_topic_handler::handle_create_topic(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_TOPIC_CODE => {
+ let req: DeleteTopicRequest = decode(frame.payload)?;
+ handlers::topics::delete_topic_handler::handle_delete_topic(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ UPDATE_TOPIC_CODE => {
+ let req: UpdateTopicRequest = decode(frame.payload)?;
+ handlers::topics::update_topic_handler::handle_update_topic(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ PURGE_TOPIC_CODE => {
+ let req: PurgeTopicRequest = decode(frame.payload)?;
+ handlers::topics::purge_topic_handler::handle_purge_topic(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Partitions
+ CREATE_PARTITIONS_CODE => {
+ let req: CreatePartitionsRequest = decode(frame.payload)?;
+ handlers::partitions::create_partitions_handler::handle_create_partitions(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_PARTITIONS_CODE => {
+ let req: DeletePartitionsRequest = decode(frame.payload)?;
+ handlers::partitions::delete_partitions_handler::handle_delete_partitions(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Segments
+ DELETE_SEGMENTS_CODE => {
+ let req: DeleteSegmentsRequest = decode(frame.payload)?;
+ handlers::segments::delete_segments_handler::handle_delete_segments(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Messages (PollMessages + FlushUnsavedBuffer; SendMessages handled above)
+ POLL_MESSAGES_CODE => {
+ let req: PollMessagesRequest = decode(frame.payload)?;
+ handlers::messages::poll_messages_handler::handle_poll_messages(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ FLUSH_UNSAVED_BUFFER_CODE => {
+ let req: FlushUnsavedBufferRequest = decode(frame.payload)?;
+ handlers::messages::flush_unsaved_buffer_handler::handle_flush_unsaved_buffer(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Consumer Offsets
+ GET_CONSUMER_OFFSET_CODE => {
+ let req: GetConsumerOffsetRequest = decode(frame.payload)?;
+ handlers::consumer_offsets::get_consumer_offset_handler::handle_get_consumer_offset(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ STORE_CONSUMER_OFFSET_CODE => {
+ let req: StoreConsumerOffsetRequest = decode(frame.payload)?;
+ handlers::consumer_offsets::store_consumer_offset_handler::handle_store_consumer_offset(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_CONSUMER_OFFSET_CODE => {
+ let req: DeleteConsumerOffsetRequest = decode(frame.payload)?;
+ handlers::consumer_offsets::delete_consumer_offset_handler::handle_delete_consumer_offset(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Consumer Groups
+ GET_CONSUMER_GROUP_CODE => {
+ let req: GetConsumerGroupRequest = decode(frame.payload)?;
+ handlers::consumer_groups::get_consumer_group_handler::handle_get_consumer_group(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ GET_CONSUMER_GROUPS_CODE => {
+ let req: GetConsumerGroupsRequest = decode(frame.payload)?;
+ handlers::consumer_groups::get_consumer_groups_handler::handle_get_consumer_groups(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ CREATE_CONSUMER_GROUP_CODE => {
+ let req: CreateConsumerGroupRequest = decode(frame.payload)?;
+ handlers::consumer_groups::create_consumer_group_handler::handle_create_consumer_group(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_CONSUMER_GROUP_CODE => {
+ let req: DeleteConsumerGroupRequest = decode(frame.payload)?;
+ handlers::consumer_groups::delete_consumer_group_handler::handle_delete_consumer_group(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ JOIN_CONSUMER_GROUP_CODE => {
+ let req: JoinConsumerGroupRequest = decode(frame.payload)?;
+ handlers::consumer_groups::join_consumer_group_handler::handle_join_consumer_group(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ LEAVE_CONSUMER_GROUP_CODE => {
+ let req: LeaveConsumerGroupRequest = decode(frame.payload)?;
+ handlers::consumer_groups::leave_consumer_group_handler::handle_leave_consumer_group(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ // Users
+ GET_USER_CODE => {
+ let req: GetUserRequest = decode(frame.payload)?;
+ handlers::users::get_user_handler::handle_get_user(req, sender, session, shard).await
+ }
+ GET_USERS_CODE => {
+ handlers::users::get_users_handler::handle_get_users(sender, session, shard).await
+ }
+ CREATE_USER_CODE => {
+ let req: CreateUserRequest = decode(frame.payload)?;
+ handlers::users::create_user_handler::handle_create_user(req, sender, session, shard)
+ .await
+ }
+ DELETE_USER_CODE => {
+ let req: DeleteUserRequest = decode(frame.payload)?;
+ handlers::users::delete_user_handler::handle_delete_user(req, sender, session, shard)
+ .await
+ }
+ UPDATE_USER_CODE => {
+ let req: UpdateUserRequest = decode(frame.payload)?;
+ handlers::users::update_user_handler::handle_update_user(req, sender, session, shard)
+ .await
+ }
+ UPDATE_PERMISSIONS_CODE => {
+ let req: UpdatePermissionsRequest = decode(frame.payload)?;
+ handlers::users::update_permissions_handler::handle_update_permissions(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ CHANGE_PASSWORD_CODE => {
+ let req: ChangePasswordRequest = decode(frame.payload)?;
+ handlers::users::change_password_handler::handle_change_password(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ LOGIN_USER_CODE => {
+ let req: LoginUserRequest = decode(frame.payload)?;
+ handlers::users::login_user_handler::handle_login_user(req, sender, session, shard)
+ .await
+ }
+ LOGOUT_USER_CODE => {
+ handlers::users::logout_user_handler::handle_logout_user(sender, session, shard).await
+ }
+
+ // Personal Access Tokens
+ GET_PERSONAL_ACCESS_TOKENS_CODE => {
+ handlers::personal_access_tokens::get_personal_access_tokens_handler::handle_get_personal_access_tokens(
+ sender, session, shard,
+ )
+ .await
+ }
+ CREATE_PERSONAL_ACCESS_TOKEN_CODE => {
+ let req: CreatePersonalAccessTokenRequest = decode(frame.payload)?;
+ handlers::personal_access_tokens::create_personal_access_token_handler::handle_create_personal_access_token(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ DELETE_PERSONAL_ACCESS_TOKEN_CODE => {
+ let req: DeletePersonalAccessTokenRequest = decode(frame.payload)?;
+ handlers::personal_access_tokens::delete_personal_access_token_handler::handle_delete_personal_access_token(
+ req, sender, session, shard,
+ )
+ .await
+ }
+ LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE => {
+ let req: LoginWithPersonalAccessTokenRequest = decode(frame.payload)?;
+ handlers::personal_access_tokens::login_with_personal_access_token_handler::handle_login_with_personal_access_token(
+ req, sender, session, shard,
+ )
+ .await
+ }
+
+ _ => {
+ error!("Unknown command code: {}", frame.code);
+ Err(IggyError::InvalidCommand)
+ }
+ }
+}
diff --git a/core/server/src/binary/handlers/cluster/get_cluster_metadata_handler.rs b/core/server/src/binary/handlers/cluster/get_cluster_metadata_handler.rs
index aa29f8512..466945f39 100644
--- a/core/server/src/binary/handlers/cluster/get_cluster_metadata_handler.rs
+++ b/core/server/src/binary/handlers/cluster/get_cluster_metadata_handler.rs
@@ -16,49 +16,45 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
-use iggy_common::get_cluster_metadata::GetClusterMetadata;
-use iggy_common::{BytesSerializable, IggyError, SenderKind};
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::system::get_cluster_metadata::{
+ ClusterMetadataResponse, ClusterNodeResponse,
+};
+use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for GetClusterMetadata {
- fn code(&self) -> u32 {
- iggy_common::GET_CLUSTER_METADATA_CODE
- }
-
- #[instrument(skip_all, name = "trace_get_cluster_metadata", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+#[instrument(skip_all, name = "trace_get_cluster_metadata", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_get_cluster_metadata(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_cluster_metadata");
+ shard.ensure_authenticated(session)?;
- let cluster_metadata = shard.get_cluster_metadata();
-
- let response = cluster_metadata.to_bytes();
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let cluster_metadata = shard.get_cluster_metadata();
-impl BinaryServerCommand for GetClusterMetadata {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetClusterMetadata(get_cluster_metadata) => Ok(get_cluster_metadata),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let response = ClusterMetadataResponse {
+ name: cluster_metadata.name,
+ nodes: cluster_metadata
+ .nodes
+ .into_iter()
+ .map(|node| ClusterNodeResponse {
+ name: node.name,
+ ip: node.ip,
+ tcp_port: node.endpoints.tcp,
+ quic_port: node.endpoints.quic,
+ http_port: node.endpoints.http,
+ websocket_port: node.endpoints.websocket,
+ role: node.role as u8,
+ status: node.status as u8,
+ })
+ .collect(),
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/create_consumer_group_handler.rs b/core/server/src/binary/handlers/consumer_groups/create_consumer_group_handler.rs
index 2945bb41a..3dc1e2591 100644
--- a/core/server/src/binary/handlers/consumer_groups/create_consumer_group_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/create_consumer_group_handler.rs
@@ -16,64 +16,60 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::consumer_groups::CreateConsumerGroupRequest;
+use iggy_binary_protocol::responses::consumer_groups::ConsumerGroupResponse;
use iggy_common::create_consumer_group::CreateConsumerGroup;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for CreateConsumerGroup {
- fn code(&self) -> u32 {
- iggy_common::CREATE_CONSUMER_GROUP_CODE
- }
+#[instrument(skip_all, name = "trace_create_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_consumer_group(
+ req: CreateConsumerGroupRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: create_consumer_group, stream_id: {stream_id}, topic_id: {topic_id}, name: {}",
+ req.name.as_str()
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_create_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = CreateConsumerGroup {
+ stream_id,
+ topic_id,
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- let request =
- ShardRequest::control_plane(ShardRequestPayload::CreateConsumerGroupRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::CreateConsumerGroupRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreateConsumerGroupResponse(data) => {
- sender
- .send_ok_response(&mapper::map_consumer_group_from_response(&data))
- .await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreateConsumerGroupResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreateConsumerGroupResponse(data) => {
+ let response = ConsumerGroupResponse {
+ id: data.id,
+ partitions_count: data.partitions_count,
+ members_count: 0,
+ name: WireName::new(data.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreateConsumerGroupResponse"),
}
-}
-impl BinaryServerCommand for CreateConsumerGroup {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreateConsumerGroup(create_consumer_group) => Ok(create_consumer_group),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/delete_consumer_group_handler.rs b/core/server/src/binary/handlers/consumer_groups/delete_consumer_group_handler.rs
index ffb35d0ec..35a1b84a3 100644
--- a/core/server/src/binary/handlers/consumer_groups/delete_consumer_group_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/delete_consumer_group_handler.rs
@@ -16,61 +16,50 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::consumer_groups::DeleteConsumerGroupRequest;
use iggy_common::delete_consumer_group::DeleteConsumerGroup;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeleteConsumerGroup {
- fn code(&self) -> u32 {
- iggy_common::DELETE_CONSUMER_GROUP_CODE
- }
+#[instrument(skip_all, name = "trace_delete_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_consumer_group(
+ req: DeleteConsumerGroupRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let group_id = wire_id_to_identifier(&req.group_id)?;
+ debug!(
+ "session: {session}, command: delete_consumer_group, stream_id: {stream_id}, topic_id: {topic_id}, group_id: {group_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_delete_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = DeleteConsumerGroup {
+ stream_id,
+ topic_id,
+ group_id,
+ };
- let request =
- ShardRequest::control_plane(ShardRequestPayload::DeleteConsumerGroupRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::DeleteConsumerGroupRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteConsumerGroupResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeleteConsumerGroupResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeleteConsumerGroupResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeleteConsumerGroupResponse"),
}
-}
-impl BinaryServerCommand for DeleteConsumerGroup {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteConsumerGroup(delete_consumer_group) => Ok(delete_consumer_group),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/get_consumer_group_handler.rs b/core/server/src/binary/handlers/consumer_groups/get_consumer_group_handler.rs
index c0eddb8af..3a54bd5ab 100644
--- a/core/server/src/binary/handlers/consumer_groups/get_consumer_group_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/get_consumer_group_handler.rs
@@ -16,59 +16,64 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::consumer_groups::GetConsumerGroupRequest;
+use iggy_binary_protocol::responses::consumer_groups::{
+ ConsumerGroupDetailsResponse, ConsumerGroupMemberResponse, ConsumerGroupResponse,
+};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_consumer_group::GetConsumerGroup;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetConsumerGroup {
- fn code(&self) -> u32 {
- iggy_common::GET_CONSUMER_GROUP_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+pub async fn handle_get_consumer_group(
+ req: GetConsumerGroupRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let group_id = wire_id_to_identifier(&req.group_id)?;
+ debug!(
+ "session: {session}, command: get_consumer_group, stream_id: {stream_id}, topic_id: {topic_id}, group_id: {group_id}"
+ );
+ shard.ensure_authenticated(session)?;
- let Some(consumer_group) = shard.metadata.query_consumer_group(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- &self.group_id,
- )?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
-
- let response = mapper::map_consumer_group_from_meta(&consumer_group);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let Some(consumer_group) = shard.metadata.query_consumer_group(
+ session.get_user_id(),
+ &stream_id,
+ &topic_id,
+ &group_id,
+ )?
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
-impl BinaryServerCommand for GetConsumerGroup {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetConsumerGroup(get_consumer_group) => Ok(get_consumer_group),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let members: Vec<ConsumerGroupMemberResponse> = consumer_group
+ .members
+ .iter()
+ .map(|(_, member)| ConsumerGroupMemberResponse {
+ id: member.id as u32,
+ partitions_count: member.partitions.len() as u32,
+ partitions: member.partitions.iter().map(|&p| p as u32).collect(),
+ })
+ .collect();
+ let response = ConsumerGroupDetailsResponse {
+ group: ConsumerGroupResponse {
+ id: consumer_group.id as u32,
+ partitions_count: consumer_group.partitions.len() as u32,
+ members_count: consumer_group.members.len() as u32,
+ name: WireName::new(consumer_group.name.as_ref())
+ .map_err(|_| IggyError::InvalidCommand)?,
+ },
+ members,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/get_consumer_groups_handler.rs b/core/server/src/binary/handlers/consumer_groups/get_consumer_groups_handler.rs
index f798605c5..2d64c8d00 100644
--- a/core/server/src/binary/handlers/consumer_groups/get_consumer_groups_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/get_consumer_groups_handler.rs
@@ -16,58 +16,54 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use bytes::Bytes;
-use iggy_common::get_consumer_groups::GetConsumerGroups;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::consumer_groups::GetConsumerGroupsRequest;
+use iggy_binary_protocol::responses::consumer_groups::{
+ ConsumerGroupResponse, GetConsumerGroupsResponse,
+};
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetConsumerGroups {
- fn code(&self) -> u32 {
- iggy_common::GET_CONSUMER_GROUPS_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+pub async fn handle_get_consumer_groups(
+ req: GetConsumerGroupsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: get_consumer_groups, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- let Some(consumer_groups) = shard.metadata.query_consumer_groups(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- )?
- else {
- sender.send_ok_response(&Bytes::new()).await?;
- return Ok(HandlerResult::Finished);
- };
-
- let response = mapper::map_consumer_groups(&consumer_groups);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let Some(consumer_groups) =
+ shard
+ .metadata
+ .query_consumer_groups(session.get_user_id(), &stream_id, &topic_id)?
+ else {
+ sender.send_ok_response(&Bytes::new()).await?;
+ return Ok(HandlerResult::Finished);
+ };
-impl BinaryServerCommand for GetConsumerGroups {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetConsumerGroups(get_consumer_groups) => Ok(get_consumer_groups),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let groups: Vec<ConsumerGroupResponse> = consumer_groups
+ .iter()
+ .map(|cg| {
+ Ok(ConsumerGroupResponse {
+ id: cg.id as u32,
+ partitions_count: cg.partitions.len() as u32,
+ members_count: cg.members.len() as u32,
+ name: WireName::new(cg.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ })
+ })
+ .collect::<Result<_, IggyError>>()?;
+ let response = GetConsumerGroupsResponse { groups };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/join_consumer_group_handler.rs b/core/server/src/binary/handlers/consumer_groups/join_consumer_group_handler.rs
index 18b220ad1..f3adcb6e6 100644
--- a/core/server/src/binary/handlers/consumer_groups/join_consumer_group_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/join_consumer_group_handler.rs
@@ -16,61 +16,51 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::consumer_groups::JoinConsumerGroupRequest;
use iggy_common::join_consumer_group::JoinConsumerGroup;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for JoinConsumerGroup {
- fn code(&self) -> u32 {
- iggy_common::JOIN_CONSUMER_GROUP_CODE
- }
+#[instrument(skip_all, name = "trace_join_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_join_consumer_group(
+ req: JoinConsumerGroupRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let group_id = wire_id_to_identifier(&req.group_id)?;
+ debug!(
+ "session: {session}, command: join_consumer_group, stream_id: {stream_id}, topic_id: {topic_id}, group_id: {group_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_join_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string(), iggy_group_id = self.group_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = JoinConsumerGroup {
+ stream_id,
+ topic_id,
+ group_id,
+ };
- let request = ShardRequest::control_plane(ShardRequestPayload::JoinConsumerGroupRequest {
- user_id: session.get_user_id(),
- client_id: session.client_id,
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::JoinConsumerGroupRequest {
+ user_id: session.get_user_id(),
+ client_id: session.client_id,
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::JoinConsumerGroupResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected JoinConsumerGroupResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::JoinConsumerGroupResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected JoinConsumerGroupResponse"),
}
-}
-impl BinaryServerCommand for JoinConsumerGroup {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::JoinConsumerGroup(join_consumer_group) => Ok(join_consumer_group),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_groups/leave_consumer_group_handler.rs b/core/server/src/binary/handlers/consumer_groups/leave_consumer_group_handler.rs
index 3134086d6..a522e6fb0 100644
--- a/core/server/src/binary/handlers/consumer_groups/leave_consumer_group_handler.rs
+++ b/core/server/src/binary/handlers/consumer_groups/leave_consumer_group_handler.rs
@@ -16,61 +16,51 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::consumer_groups::LeaveConsumerGroupRequest;
use iggy_common::leave_consumer_group::LeaveConsumerGroup;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for LeaveConsumerGroup {
- fn code(&self) -> u32 {
- iggy_common::LEAVE_CONSUMER_GROUP_CODE
- }
+#[instrument(skip_all, name = "trace_leave_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_leave_consumer_group(
+ req: LeaveConsumerGroupRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let group_id = wire_id_to_identifier(&req.group_id)?;
+ debug!(
+ "session: {session}, command: leave_consumer_group, stream_id: {stream_id}, topic_id: {topic_id}, group_id: {group_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_leave_consumer_group", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string(), iggy_group_id = self.group_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = LeaveConsumerGroup {
+ stream_id,
+ topic_id,
+ group_id,
+ };
- let request = ShardRequest::control_plane(ShardRequestPayload::LeaveConsumerGroupRequest {
- user_id: session.get_user_id(),
- client_id: session.client_id,
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::LeaveConsumerGroupRequest {
+ user_id: session.get_user_id(),
+ client_id: session.client_id,
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::LeaveConsumerGroupResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected LeaveConsumerGroupResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::LeaveConsumerGroupResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected LeaveConsumerGroupResponse"),
}
-}
-impl BinaryServerCommand for LeaveConsumerGroup {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::LeaveConsumerGroup(leave_consumer_group) => Ok(leave_consumer_group),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_offsets/delete_consumer_offset_handler.rs b/core/server/src/binary/handlers/consumer_offsets/delete_consumer_offset_handler.rs
index 0d986e921..459198a06 100644
--- a/core/server/src/binary/handlers/consumer_offsets/delete_consumer_offset_handler.rs
+++ b/core/server/src/binary/handlers/consumer_offsets/delete_consumer_offset_handler.rs
@@ -16,59 +16,41 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::{HandlerResult, wire_consumer_to_consumer, wire_id_to_identifier};
use crate::binary::handlers::consumer_offsets::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
-use iggy_common::delete_consumer_offset::DeleteConsumerOffset;
+use iggy_binary_protocol::requests::consumer_offsets::DeleteConsumerOffsetRequest;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for DeleteConsumerOffset {
- fn code(&self) -> u32 {
- iggy_common::DELETE_CONSUMER_OFFSET_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- let topic = shard.resolve_topic_for_delete_consumer_offset(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- )?;
- shard
- .delete_consumer_offset(session.client_id, self.consumer, topic, self.partition_id)
- .await
- .error(|e: &IggyError| format!("{COMPONENT} (error: {e}) - failed to delete consumer offset for topic with ID: {} in stream with ID: {} partition ID: {:#?}, session: {}",
- self.topic_id, self.stream_id, self.partition_id, session
- ))?;
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for DeleteConsumerOffset {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteConsumerOffset(delete_consumer_offset) => {
- Ok(delete_consumer_offset)
- }
- _ => Err(IggyError::InvalidCommand),
- }
- }
+pub async fn handle_delete_consumer_offset(
+ req: DeleteConsumerOffsetRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let consumer = wire_consumer_to_consumer(&req.consumer)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: delete_consumer_offset, stream_id: {stream_id}, topic_id: {topic_id}, partition_id: {:?}",
+ req.partition_id
+ );
+ shard.ensure_authenticated(session)?;
+ let topic = shard.resolve_topic_for_delete_consumer_offset(
+ session.get_user_id(),
+ &stream_id,
+ &topic_id,
+ )?;
+ shard
+ .delete_consumer_offset(session.client_id, consumer, topic, req.partition_id)
+ .await
+ .error(|e: &IggyError| format!("{COMPONENT} (error: {e}) - failed to delete consumer offset for topic with ID: {} in stream with ID: {} partition ID: {:#?}, session: {}",
+ topic_id, stream_id, req.partition_id, session
+ ))?;
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_offsets/get_consumer_offset_handler.rs b/core/server/src/binary/handlers/consumer_offsets/get_consumer_offset_handler.rs
index 1e207d78a..97eefd75b 100644
--- a/core/server/src/binary/handlers/consumer_offsets/get_consumer_offset_handler.rs
+++ b/core/server/src/binary/handlers/consumer_offsets/get_consumer_offset_handler.rs
@@ -16,77 +16,65 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_consumer_to_consumer, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::message::ResolvedTopic;
use crate::streaming::session::Session;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::consumer_offsets::GetConsumerOffsetRequest;
+use iggy_binary_protocol::responses::consumer_offsets::ConsumerOffsetResponse;
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_consumer_offset::GetConsumerOffset;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetConsumerOffset {
- fn code(&self) -> u32 {
- iggy_common::GET_CONSUMER_OFFSET_CODE
- }
+pub async fn handle_get_consumer_offset(
+ req: GetConsumerOffsetRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let consumer = wire_consumer_to_consumer(&req.consumer)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: get_consumer_offset, stream_id: {stream_id}, topic_id: {topic_id}, partition_id: {:?}",
+ req.partition_id
+ );
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let Some(resolved) =
+ shard
+ .metadata
+ .resolve_for_consumer_offset(session.get_user_id(), &stream_id, &topic_id)?
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- let Some(resolved) = shard.metadata.resolve_for_consumer_offset(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- )?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
+ let topic = ResolvedTopic {
+ stream_id: resolved.stream_id,
+ topic_id: resolved.topic_id,
+ };
- let topic = ResolvedTopic {
- stream_id: resolved.stream_id,
- topic_id: resolved.topic_id,
- };
+ let Ok(offset) = shard
+ .get_consumer_offset(session.client_id, consumer, topic, req.partition_id)
+ .await
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- let Ok(offset) = shard
- .get_consumer_offset(session.client_id, self.consumer, topic, self.partition_id)
- .await
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
+ let Some(offset) = offset else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- let Some(offset) = offset else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
-
- let offset = mapper::map_consumer_offset(&offset);
- sender.send_ok_response(&offset).await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for GetConsumerOffset {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetConsumerOffset(get_consumer_offset) => Ok(get_consumer_offset),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let response = ConsumerOffsetResponse {
+ partition_id: offset.partition_id,
+ current_offset: offset.current_offset,
+ stored_offset: offset.stored_offset,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/consumer_offsets/store_consumer_offset_handler.rs b/core/server/src/binary/handlers/consumer_offsets/store_consumer_offset_handler.rs
index a0d2f2cbd..87eb4c5e5 100644
--- a/core/server/src/binary/handlers/consumer_offsets/store_consumer_offset_handler.rs
+++ b/core/server/src/binary/handlers/consumer_offsets/store_consumer_offset_handler.rs
@@ -18,63 +18,47 @@
use std::rc::Rc;
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::{HandlerResult, wire_consumer_to_consumer, wire_id_to_identifier};
use crate::binary::handlers::consumer_offsets::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
+use iggy_binary_protocol::requests::consumer_offsets::StoreConsumerOffsetRequest;
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::store_consumer_offset::StoreConsumerOffset;
use tracing::debug;
-impl ServerCommandHandler for StoreConsumerOffset {
- fn code(&self) -> u32 {
- iggy_common::STORE_CONSUMER_OFFSET_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- let topic = shard.resolve_topic_for_store_consumer_offset(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- )?;
- shard
- .store_consumer_offset(
- session.client_id,
- self.consumer,
- topic,
- self.partition_id,
- self.offset,
- )
- .await
- .error(|e: &IggyError| format!("{COMPONENT} (error: {e}) - failed to store consumer offset for stream_id: {}, topic_id: {}, partition_id: {:?}, offset: {}, session: {}",
- self.stream_id, self.topic_id, self.partition_id, self.offset, session
- ))?;
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for StoreConsumerOffset {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::StoreConsumerOffset(store_consumer_offset) => Ok(store_consumer_offset),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+pub async fn handle_store_consumer_offset(
+ req: StoreConsumerOffsetRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let consumer = wire_consumer_to_consumer(&req.consumer)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: store_consumer_offset, stream_id: {stream_id}, topic_id: {topic_id}, partition_id: {:?}, offset: {}",
+ req.partition_id, req.offset
+ );
+ shard.ensure_authenticated(session)?;
+ let topic = shard.resolve_topic_for_store_consumer_offset(
+ session.get_user_id(),
+ &stream_id,
+ &topic_id,
+ )?;
+ shard
+ .store_consumer_offset(
+ session.client_id,
+ consumer,
+ topic,
+ req.partition_id,
+ req.offset,
+ )
+ .await
+ .error(|e: &IggyError| format!("{COMPONENT} (error: {e}) - failed to store consumer offset for stream_id: {}, topic_id: {}, partition_id: {:?}, offset: {}, session: {}",
+ stream_id, topic_id, req.partition_id, req.offset, session
+ ))?;
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/messages/flush_unsaved_buffer_handler.rs b/core/server/src/binary/handlers/messages/flush_unsaved_buffer_handler.rs
index 300ecc672..79f8dbd4e 100644
--- a/core/server/src/binary/handlers/messages/flush_unsaved_buffer_handler.rs
+++ b/core/server/src/binary/handlers/messages/flush_unsaved_buffer_handler.rs
@@ -16,71 +16,50 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::binary::handlers::messages::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
use crate::shard::IggyShard;
use crate::shard::transmission::message::ResolvedPartition;
use crate::streaming::session::Session;
use err_trail::ErrContext;
-use iggy_common::{FlushUnsavedBuffer, IggyError, SenderKind};
+use iggy_binary_protocol::requests::messages::FlushUnsavedBufferRequest;
+use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for FlushUnsavedBuffer {
- fn code(&self) -> u32 {
- iggy_common::FLUSH_UNSAVED_BUFFER_CODE
- }
+#[instrument(skip_all, name = "trace_flush_unsaved_buffer", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_partition_id = req.partition_id, iggy_fsync = req.fsync))]
+pub async fn handle_flush_unsaved_buffer(
+ req: FlushUnsavedBufferRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let partition_id = req.partition_id;
+ let fsync = req.fsync;
+ debug!(
+ "session: {session}, command: flush_unsaved_buffer, stream_id: {stream_id}, topic_id: {topic_id}, partition_id: {partition_id}, fsync: {fsync}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_flush_unsaved_buffer", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string(), iggy_partition_id = self.partition_id, iggy_fsync = self.fsync))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let user_id = session.get_user_id();
+ let topic = shard.resolve_topic(&stream_id, &topic_id)?;
+ let partition = ResolvedPartition {
+ stream_id: topic.stream_id,
+ topic_id: topic.topic_id,
+ partition_id: partition_id as usize,
+ };
- let user_id = session.get_user_id();
- let stream_id_log = self.stream_id.clone();
- let topic_id_log = self.topic_id.clone();
- let partition_id = self.partition_id;
- let fsync = self.fsync;
-
- let topic = shard.resolve_topic(&self.stream_id, &self.topic_id)?;
- let partition = ResolvedPartition {
- stream_id: topic.stream_id,
- topic_id: topic.topic_id,
- partition_id: partition_id as usize,
- };
-
- shard
- .flush_unsaved_buffer(user_id, partition, fsync)
- .await
- .error(|e: &IggyError| {
- format!(
- "{COMPONENT} (error: {e}) - failed to flush unsaved buffer for stream_id: {}, topic_id: {}, partition_id: {}, session: {}",
- stream_id_log, topic_id_log, partition_id, session
- )
- })?;
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for FlushUnsavedBuffer {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::FlushUnsavedBuffer(flush_unsaved_buffer) => Ok(flush_unsaved_buffer),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ shard
+ .flush_unsaved_buffer(user_id, partition, fsync)
+ .await
+ .error(|e: &IggyError| {
+ format!(
+ "{COMPONENT} (error: {e}) - failed to flush unsaved buffer for stream_id: {}, topic_id: {}, partition_id: {}, session: {}",
+ stream_id, topic_id, partition_id, session
+ )
+ })?;
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/messages/poll_messages_handler.rs b/core/server/src/binary/handlers/messages/poll_messages_handler.rs
index 71388d75b..ca1b11c14 100644
--- a/core/server/src/binary/handlers/messages/poll_messages_handler.rs
+++ b/core/server/src/binary/handlers/messages/poll_messages_handler.rs
@@ -16,96 +16,72 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
+use crate::binary::dispatch::{
+ HandlerResult, wire_consumer_to_consumer, wire_id_to_identifier, wire_polling_to_strategy,
};
-use crate::binary::handlers::utils::receive_and_validate;
use crate::shard::IggyShard;
use crate::shard::system::messages::PollingArgs;
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::messages::PollMessagesRequest;
use iggy_common::SenderKind;
-use iggy_common::{IggyError, PollMessages, PooledBuffer};
+use iggy_common::{IggyError, PooledBuffer};
use std::rc::Rc;
use tracing::{debug, trace};
-impl ServerCommandHandler for PollMessages {
- fn code(&self) -> u32 {
- iggy_common::POLL_MESSAGES_CODE
- }
+pub async fn handle_poll_messages(
+ req: PollMessagesRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let consumer = wire_consumer_to_consumer(&req.consumer)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ let strategy = wire_polling_to_strategy(&req.strategy)?;
+ let partition_id = req.partition_id;
+ let count = req.count;
+ let auto_commit = req.auto_commit;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- let PollMessages {
- consumer,
- partition_id,
- strategy,
- count,
- auto_commit,
- stream_id,
- topic_id,
- } = self;
- let args = PollingArgs::new(strategy, count, auto_commit);
+ debug!(
+ "session: {session}, command: poll_messages, stream_id: {stream_id}, topic_id: {topic_id}, partition_id: {partition_id:?}"
+ );
+ shard.ensure_authenticated(session)?;
- let user_id = session.get_user_id();
- let client_id = session.client_id;
- let topic = shard.resolve_topic_for_poll(user_id, &stream_id, &topic_id)?;
- let (metadata, mut batch) = shard
- .poll_messages(client_id, topic, consumer, partition_id, args)
- .await?;
+ let args = PollingArgs::new(strategy, count, auto_commit);
- // Collect all chunks first into a Vec to extend their lifetimes.
- // This ensures the Bytes (in reality Arc<[u8]>) references from each IggyMessagesBatch stay alive
- // throughout the async vectored I/O operation, preventing "borrowed value does not live
- // long enough" errors while optimizing transmission by using larger chunks.
+ let user_id = session.get_user_id();
+ let client_id = session.client_id;
+ let topic = shard.resolve_topic_for_poll(user_id, &stream_id, &topic_id)?;
+ let (metadata, mut batch) = shard
+ .poll_messages(client_id, topic, consumer, partition_id, args)
+ .await?;
- // 4 bytes for partition_id + 8 bytes for current_offset + 4 bytes for messages_count + size of all batches.
- let response_length = 4 + 8 + 4 + batch.size();
- let response_length_bytes = response_length.to_le_bytes();
+ let response_length = 4 + 8 + 4 + batch.size();
+ let response_length_bytes = response_length.to_le_bytes();
- let mut bufs = Vec::with_capacity(batch.containers_count() + 3);
- let mut partition_id_buf = PooledBuffer::with_capacity(4);
- let mut current_offset_buf = PooledBuffer::with_capacity(8);
- let mut count_buf = PooledBuffer::with_capacity(4);
- partition_id_buf.put_u32_le(metadata.partition_id);
- current_offset_buf.put_u64_le(metadata.current_offset);
- count_buf.put_u32_le(batch.count());
+ let mut bufs = Vec::with_capacity(batch.containers_count() + 3);
+ let mut partition_id_buf = PooledBuffer::with_capacity(4);
+ let mut current_offset_buf = PooledBuffer::with_capacity(8);
+ let mut count_buf = PooledBuffer::with_capacity(4);
+ partition_id_buf.put_u32_le(metadata.partition_id);
+ current_offset_buf.put_u64_le(metadata.current_offset);
+ count_buf.put_u32_le(batch.count());
- bufs.push(partition_id_buf);
- bufs.push(current_offset_buf);
- bufs.push(count_buf);
+ bufs.push(partition_id_buf);
+ bufs.push(current_offset_buf);
+ bufs.push(count_buf);
- batch.iter_mut().for_each(|m| {
- bufs.push(m.take_messages());
- });
- trace!(
- "Sending {} messages to client ({} bytes) to client",
- batch.count(),
- response_length
- );
+ batch.iter_mut().for_each(|m| {
+ bufs.push(m.take_messages());
+ });
+ trace!(
+ "Sending {} messages to client ({} bytes) to client",
+ batch.count(),
+ response_length
+ );
- sender
- .send_ok_response_vectored(&response_length_bytes, bufs)
- .await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for PollMessages {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::PollMessages(poll_messages) => Ok(poll_messages),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ sender
+ .send_ok_response_vectored(&response_length_bytes, bufs)
+ .await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/messages/send_messages_handler.rs b/core/server/src/binary/handlers/messages/send_messages_handler.rs
index 91a5ea1e7..2964dcfce 100644
--- a/core/server/src/binary/handlers/messages/send_messages_handler.rs
+++ b/core/server/src/binary/handlers/messages/send_messages_handler.rs
@@ -16,7 +16,7 @@
* under the License.
*/
-use crate::binary::command::{BinaryServerCommand, HandlerResult, ServerCommandHandler};
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::shard::transmission::message::{ResolvedPartition, ShardRequest, ShardRequestPayload};
use crate::streaming::segments::{IggyIndexesMut, IggyMessagesBatchMut};
@@ -29,205 +29,176 @@ use iggy_common::SenderKind;
use iggy_common::Sizeable;
use iggy_common::sharding::IggyNamespace;
use iggy_common::{INDEX_SIZE, PartitioningKind};
-use iggy_common::{IggyError, Partitioning, SendMessages, Validatable};
+use iggy_common::{IggyError, Partitioning, Validatable};
use std::rc::Rc;
use tracing::{debug, error, info, instrument};
-impl ServerCommandHandler for SendMessages {
- fn code(&self) -> u32 {
- iggy_common::SEND_MESSAGES_CODE
+#[instrument(skip_all, name = "trace_send_messages", fields(
+ iggy_user_id = session.get_user_id(),
+ iggy_client_id = session.client_id,
+))]
+pub async fn handle_send_messages(
+ sender: &mut SenderKind,
+ length: u32,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ shard.ensure_authenticated(session)?;
+ // `length` is the payload size (frame length minus the 4-byte code,
+ // already subtracted by the transport layer before calling dispatch).
+ let total_payload_size = length as usize;
+ let metadata_len_field_size = std::mem::size_of::<u32>();
+
+ let metadata_length_buffer = PooledBuffer::with_capacity(4);
+ let (result, metadata_len_buf) = sender.read(metadata_length_buffer.slice(0..4)).await;
+ let metadata_len_buf = metadata_len_buf.into_inner();
+ result?;
+ let metadata_size = u32::from_le_bytes(
+ metadata_len_buf[..]
+ .try_into()
+ .map_err(|_| IggyError::InvalidNumberEncoding)?,
+ );
+ if metadata_size as usize > total_payload_size {
+ return Err(IggyError::InvalidCommand);
}
- #[instrument(skip_all, name = "trace_send_messages", fields(
- iggy_user_id = session.get_user_id(),
- iggy_client_id = session.client_id,
- iggy_stream_id = self.stream_id.as_string(),
- iggy_topic_id = self.topic_id.as_string(),
- partitioning = %self.partitioning
- ))]
- async fn handle(
- mut self,
- sender: &mut SenderKind,
- length: u32,
-
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- shard.ensure_authenticated(session)?;
- let total_payload_size = (length as usize)
- .checked_sub(std::mem::size_of::<u32>())
- .ok_or(IggyError::InvalidCommand)?;
- let metadata_len_field_size = std::mem::size_of::<u32>();
-
- let metadata_length_buffer = PooledBuffer::with_capacity(4);
- let (result, metadata_len_buf) = sender.read(metadata_length_buffer.slice(0..4)).await;
- let metadata_len_buf = metadata_len_buf.into_inner();
- result?;
- let metadata_size = u32::from_le_bytes(
- metadata_len_buf[..]
- .try_into()
- .map_err(|_| IggyError::InvalidNumberEncoding)?,
- );
+ let metadata_buffer = PooledBuffer::with_capacity(metadata_size as usize);
+ let (result, metadata_buf) = sender
+ .read(metadata_buffer.slice(0..metadata_size as usize))
+ .await;
+ result?;
+ let metadata_buf = metadata_buf.into_inner();
+
+ let mut element_size = 0;
+
+ let stream_id = Identifier::from_raw_bytes(&metadata_buf)?;
+ element_size += stream_id.get_size_bytes().as_bytes_usize();
+
+ let topic_id = Identifier::from_raw_bytes(
+ metadata_buf
+ .get(element_size..)
+ .ok_or(IggyError::InvalidCommand)?,
+ )?;
+ element_size += topic_id.get_size_bytes().as_bytes_usize();
+
+ let partitioning = Partitioning::from_raw_bytes(
+ metadata_buf
+ .get(element_size..)
+ .ok_or(IggyError::InvalidCommand)?,
+ )?;
+ element_size += partitioning.get_size_bytes().as_bytes_usize();
+
+ let messages_count = u32::from_le_bytes(
+ metadata_buf
+ .get(element_size..element_size + 4)
+ .ok_or(IggyError::InvalidCommand)?
+ .try_into()
+ .map_err(|_| IggyError::InvalidNumberEncoding)?,
+ );
+ let indexes_size = (messages_count as usize)
+ .checked_mul(INDEX_SIZE)
+ .ok_or(IggyError::InvalidCommand)?;
+ if indexes_size > total_payload_size {
+ return Err(IggyError::InvalidCommand);
+ }
- let metadata_buffer = PooledBuffer::with_capacity(metadata_size as usize);
- let (result, metadata_buf) = sender
- .read(metadata_buffer.slice(0..metadata_size as usize))
- .await;
- result?;
- let metadata_buf = metadata_buf.into_inner();
-
- let mut element_size = 0;
-
- let stream_id = Identifier::from_raw_bytes(&metadata_buf)?;
- element_size += stream_id.get_size_bytes().as_bytes_usize();
- self.stream_id = stream_id;
-
- let topic_id = Identifier::from_raw_bytes(
- metadata_buf
- .get(element_size..)
- .ok_or(IggyError::InvalidCommand)?,
- )?;
- element_size += topic_id.get_size_bytes().as_bytes_usize();
- self.topic_id = topic_id;
-
- let partitioning = Partitioning::from_raw_bytes(
- metadata_buf
- .get(element_size..)
- .ok_or(IggyError::InvalidCommand)?,
- )?;
- element_size += partitioning.get_size_bytes().as_bytes_usize();
- self.partitioning = partitioning;
-
- let messages_count = u32::from_le_bytes(
- metadata_buf
- .get(element_size..element_size + 4)
+ let indexes_buffer = PooledBuffer::with_capacity(indexes_size);
+ let (result, indexes_buffer) = sender.read(indexes_buffer.slice(0..indexes_size)).await;
+ result?;
+ let indexes_buffer = indexes_buffer.into_inner();
+
+ let messages_size = total_payload_size
+ .checked_sub(metadata_size as usize)
+ .and_then(|s| s.checked_sub(indexes_size))
+ .and_then(|s| s.checked_sub(metadata_len_field_size))
+ .ok_or(IggyError::InvalidCommand)?;
+ let messages_buffer = PooledBuffer::with_capacity(messages_size);
+ let (result, messages_buffer) = sender.read(messages_buffer.slice(0..messages_size)).await;
+ result?;
+ let messages_buffer = messages_buffer.into_inner();
+
+ let indexes = IggyIndexesMut::from_bytes(indexes_buffer, 0);
+ let batch = IggyMessagesBatchMut::from_indexes_and_messages(indexes, messages_buffer);
+ batch.validate()?;
+
+ let topic = shard.resolve_topic_for_append(session.get_user_id(), &stream_id, &topic_id)?;
+
+ let partition_id = match partitioning.kind {
+ PartitioningKind::Balanced => shard
+ .metadata
+ .get_next_partition_id(topic.stream_id, topic.topic_id)
+ .ok_or(IggyError::TopicIdNotFound(
+ stream_id.clone(),
+ topic_id.clone(),
+ ))?,
+ PartitioningKind::PartitionId => u32::from_le_bytes(
+ partitioning
+ .value
+ .get(..4)
.ok_or(IggyError::InvalidCommand)?
.try_into()
.map_err(|_| IggyError::InvalidNumberEncoding)?,
- );
- let indexes_size = (messages_count as usize)
- .checked_mul(INDEX_SIZE)
- .ok_or(IggyError::InvalidCommand)?;
- if indexes_size > total_payload_size {
- return Err(IggyError::InvalidCommand);
+ ) as usize,
+ PartitioningKind::MessagesKey => {
+ let partitions_count = shard
+ .metadata
+ .partitions_count(topic.stream_id, topic.topic_id);
+ topics::helpers::calculate_partition_id_by_messages_key_hash(
+ partitions_count,
+ &partitioning.value,
+ )
}
+ };
+
+ let namespace = IggyNamespace::new(topic.stream_id, topic.topic_id, partition_id);
+ let user_id = session.get_user_id();
+ let unsupported_socket_transfer = matches!(
+ partitioning.kind,
+ PartitioningKind::Balanced | PartitioningKind::MessagesKey
+ );
+ let enabled_socket_migration = shard.config.tcp.socket_migration;
+
+ if enabled_socket_migration
+ && !(session.is_migrated() || unsupported_socket_transfer)
+ && let Some(target_shard) = shard.find_shard(&namespace)
+ && target_shard.id != shard.id
+ {
+ debug!(
+ "TCP wrong shared detected: migrating from_shard {}, to_shard {}",
+ shard.id, target_shard.id
+ );
- let indexes_buffer = PooledBuffer::with_capacity(indexes_size);
- let (result, indexes_buffer) = sender.read(indexes_buffer.slice(0..indexes_size)).await;
- result?;
- let indexes_buffer = indexes_buffer.into_inner();
-
- let messages_size = total_payload_size
- .checked_sub(metadata_size as usize)
- .and_then(|s| s.checked_sub(indexes_size))
- .and_then(|s| s.checked_sub(metadata_len_field_size))
- .ok_or(IggyError::InvalidCommand)?;
- let messages_buffer = PooledBuffer::with_capacity(messages_size);
-
- let (result, messages_buffer) = sender.read(messages_buffer.slice(0..messages_size)).await;
- result?;
- let messages_buffer = messages_buffer.into_inner();
-
- let indexes = IggyIndexesMut::from_bytes(indexes_buffer, 0);
- let batch = IggyMessagesBatchMut::from_indexes_and_messages(indexes, messages_buffer);
- batch.validate()?;
-
- let topic = shard.resolve_topic_for_append(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- )?;
-
- let partition_id = match self.partitioning.kind {
- PartitioningKind::Balanced => shard
- .metadata
- .get_next_partition_id(topic.stream_id, topic.topic_id)
- .ok_or(IggyError::TopicIdNotFound(
- self.stream_id.clone(),
- self.topic_id.clone(),
- ))?,
- PartitioningKind::PartitionId => u32::from_le_bytes(
- self.partitioning
- .value
- .get(..4)
- .ok_or(IggyError::InvalidCommand)?
- .try_into()
- .map_err(|_| IggyError::InvalidNumberEncoding)?,
- ) as usize,
- PartitioningKind::MessagesKey => {
- let partitions_count = shard
- .metadata
- .partitions_count(topic.stream_id, topic.topic_id);
- topics::helpers::calculate_partition_id_by_messages_key_hash(
- partitions_count,
- &self.partitioning.value,
- )
+ if let Some(fd) = sender.take_and_migrate_tcp() {
+ let payload = ShardRequestPayload::SocketTransfer {
+ fd,
+ from_shard: shard.id,
+ client_id: session.client_id,
+ user_id,
+ address: session.ip_address,
+ initial_data: batch,
+ };
+
+ let request = ShardRequest::data_plane(namespace, payload);
+
+ if let Err(e) = shard.send_to_data_plane(request).await {
+ error!("transfer socket to another shard failed, drop connection. {e:?}");
+ return Ok(HandlerResult::Finished);
}
- };
- let namespace = IggyNamespace::new(topic.stream_id, topic.topic_id, partition_id);
- let user_id = session.get_user_id();
- let unsupported_socket_transfer = matches!(
- self.partitioning.kind,
- PartitioningKind::Balanced | PartitioningKind::MessagesKey
- );
- let enabled_socket_migration = shard.config.tcp.socket_migration;
-
- if enabled_socket_migration
- && !(session.is_migrated() || unsupported_socket_transfer)
- && let Some(target_shard) = shard.find_shard(&namespace)
- && target_shard.id != shard.id
- {
- debug!(
- "TCP wrong shared detected: migrating from_shard {}, to_shard {}",
- shard.id, target_shard.id
- );
-
- if let Some(fd) = sender.take_and_migrate_tcp() {
- let payload = ShardRequestPayload::SocketTransfer {
- fd,
- from_shard: shard.id,
- client_id: session.client_id,
- user_id,
- address: session.ip_address,
- initial_data: batch,
- };
-
- let request = ShardRequest::data_plane(namespace, payload);
-
- if let Err(e) = shard.send_to_data_plane(request).await {
- error!("transfer socket to another shard failed, drop connection. {e:?}");
- return Ok(HandlerResult::Finished);
- }
-
- info!("Sending socket transfer to shard {}", target_shard.id);
- return Ok(HandlerResult::Migrated {
- to_shard: target_shard.id,
- });
- }
+ info!("Sending socket transfer to shard {}", target_shard.id);
+ return Ok(HandlerResult::Migrated {
+ to_shard: target_shard.id,
+ });
}
-
- let partition = ResolvedPartition {
- stream_id: topic.stream_id,
- topic_id: topic.topic_id,
- partition_id,
- };
- shard.append_messages(partition, batch).await?;
-
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
}
-}
-impl BinaryServerCommand for SendMessages {
- async fn from_sender(
- _sender: &mut SenderKind,
- _code: u32,
- _length: u32,
- ) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- Ok(Self::default())
- }
+ let partition = ResolvedPartition {
+ stream_id: topic.stream_id,
+ topic_id: topic.topic_id,
+ partition_id,
+ };
+ shard.append_messages(partition, batch).await?;
+
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/mod.rs b/core/server/src/binary/handlers/mod.rs
index 22807ba85..2c418bf59 100644
--- a/core/server/src/binary/handlers/mod.rs
+++ b/core/server/src/binary/handlers/mod.rs
@@ -27,4 +27,3 @@ pub mod streams;
pub mod system;
pub mod topics;
pub mod users;
-mod utils;
diff --git a/core/server/src/binary/handlers/partitions/create_partitions_handler.rs b/core/server/src/binary/handlers/partitions/create_partitions_handler.rs
index a5113b50a..94f7ed976 100644
--- a/core/server/src/binary/handlers/partitions/create_partitions_handler.rs
+++ b/core/server/src/binary/handlers/partitions/create_partitions_handler.rs
@@ -16,60 +16,50 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::partitions::CreatePartitionsRequest;
use iggy_common::create_partitions::CreatePartitions;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for CreatePartitions {
- fn code(&self) -> u32 {
- iggy_common::CREATE_PARTITIONS_CODE
- }
+#[instrument(skip_all, name = "trace_create_partitions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_partitions(
+ req: CreatePartitionsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: create_partitions, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_create_partitions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = CreatePartitions {
+ stream_id,
+ topic_id,
+ partitions_count: req.partitions_count,
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::CreatePartitionsRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::CreatePartitionsRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreatePartitionsResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreatePartitionsResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreatePartitionsResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreatePartitionsResponse"),
}
-}
-impl BinaryServerCommand for CreatePartitions {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreatePartitions(create_partitions) => Ok(create_partitions),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/partitions/delete_partitions_handler.rs b/core/server/src/binary/handlers/partitions/delete_partitions_handler.rs
index 394f8379f..0b9fdc562 100644
--- a/core/server/src/binary/handlers/partitions/delete_partitions_handler.rs
+++ b/core/server/src/binary/handlers/partitions/delete_partitions_handler.rs
@@ -16,60 +16,50 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::partitions::DeletePartitionsRequest;
use iggy_common::delete_partitions::DeletePartitions;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeletePartitions {
- fn code(&self) -> u32 {
- iggy_common::DELETE_PARTITIONS_CODE
- }
+#[instrument(skip_all, name = "trace_delete_partitions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_partitions(
+ req: DeletePartitionsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: delete_partitions, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_delete_partitions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = DeletePartitions {
+ stream_id,
+ topic_id,
+ partitions_count: req.partitions_count,
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::DeletePartitionsRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::DeletePartitionsRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeletePartitionsResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeletePartitionsResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeletePartitionsResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeletePartitionsResponse"),
}
-}
-impl BinaryServerCommand for DeletePartitions {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeletePartitions(delete_partitions) => Ok(delete_partitions),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/personal_access_tokens/create_personal_access_token_handler.rs b/core/server/src/binary/handlers/personal_access_tokens/create_personal_access_token_handler.rs
index 8036f02df..eca026b4b 100644
--- a/core/server/src/binary/handlers/personal_access_tokens/create_personal_access_token_handler.rs
+++ b/core/server/src/binary/handlers/personal_access_tokens/create_personal_access_token_handler.rs
@@ -16,66 +16,55 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::personal_access_tokens::CreatePersonalAccessTokenRequest;
+use iggy_binary_protocol::responses::personal_access_tokens::RawPersonalAccessTokenResponse;
use iggy_common::create_personal_access_token::CreatePersonalAccessToken;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, IggyExpiry, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for CreatePersonalAccessToken {
- fn code(&self) -> u32 {
- iggy_common::CREATE_PERSONAL_ACCESS_TOKEN_CODE
- }
+#[instrument(skip_all, name = "trace_create_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_personal_access_token(
+ req: CreatePersonalAccessTokenRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: create_personal_access_token, name: {}",
+ req.name.as_str()
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_create_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = CreatePersonalAccessToken {
+ name: req.name.to_string(),
+ expiry: IggyExpiry::from(req.expiry),
+ };
+ command.validate()?;
- let request =
- ShardRequest::control_plane(ShardRequestPayload::CreatePersonalAccessTokenRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request =
+ ShardRequest::control_plane(ShardRequestPayload::CreatePersonalAccessTokenRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreatePersonalAccessTokenResponse(_, token) => {
- sender
- .send_ok_response(&mapper::map_raw_pat(&token))
- .await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreatePersonalAccessTokenResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreatePersonalAccessTokenResponse(_, token) => {
+ let response = RawPersonalAccessTokenResponse {
+ token: WireName::new(token).map_err(|_| IggyError::InvalidCommand)?,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreatePersonalAccessTokenResponse"),
}
-}
-impl BinaryServerCommand for CreatePersonalAccessToken {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreatePersonalAccessToken(create_personal_access_token) => {
- Ok(create_personal_access_token)
- }
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/personal_access_tokens/delete_personal_access_token_handler.rs b/core/server/src/binary/handlers/personal_access_tokens/delete_personal_access_token_handler.rs
index e8753c138..94038daa9 100644
--- a/core/server/src/binary/handlers/personal_access_tokens/delete_personal_access_token_handler.rs
+++ b/core/server/src/binary/handlers/personal_access_tokens/delete_personal_access_token_handler.rs
@@ -16,63 +16,48 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::personal_access_tokens::DeletePersonalAccessTokenRequest;
use iggy_common::delete_personal_access_token::DeletePersonalAccessToken;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeletePersonalAccessToken {
- fn code(&self) -> u32 {
- iggy_common::DELETE_PERSONAL_ACCESS_TOKEN_CODE
- }
+#[instrument(skip_all, name = "trace_delete_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_personal_access_token(
+ req: DeletePersonalAccessTokenRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: delete_personal_access_token, name: {}",
+ req.name.as_str()
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_delete_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = DeletePersonalAccessToken {
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- let request =
- ShardRequest::control_plane(ShardRequestPayload::DeletePersonalAccessTokenRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request =
+ ShardRequest::control_plane(ShardRequestPayload::DeletePersonalAccessTokenRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeletePersonalAccessTokenResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeletePersonalAccessTokenResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeletePersonalAccessTokenResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeletePersonalAccessTokenResponse"),
}
-}
-impl BinaryServerCommand for DeletePersonalAccessToken {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeletePersonalAccessToken(delete_personal_access_token) => {
- Ok(delete_personal_access_token)
- }
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/personal_access_tokens/get_personal_access_tokens_handler.rs b/core/server/src/binary/handlers/personal_access_tokens/get_personal_access_tokens_handler.rs
index e4c3965f2..b04e1490c 100644
--- a/core/server/src/binary/handlers/personal_access_tokens/get_personal_access_tokens_handler.rs
+++ b/core/server/src/binary/handlers/personal_access_tokens/get_personal_access_tokens_handler.rs
@@ -16,59 +16,46 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::HandlerResult;
use crate::binary::handlers::personal_access_tokens::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::personal_access_tokens::{
+ GetPersonalAccessTokensResponse, PersonalAccessTokenResponse,
+};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_personal_access_tokens::GetPersonalAccessTokens;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetPersonalAccessTokens {
- fn code(&self) -> u32 {
- iggy_common::GET_PERSONAL_ACCESS_TOKENS_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- let personal_access_tokens = shard
- .get_personal_access_tokens(session.get_user_id())
- .error(|e: &IggyError| {
- format!(
- "{COMPONENT} (error: {e}) - failed to get personal access tokens for user: {}",
- session.get_user_id()
- )
- })?;
- let personal_access_tokens = mapper::map_personal_access_tokens(personal_access_tokens);
- sender.send_ok_response(&personal_access_tokens).await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for GetPersonalAccessTokens {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetPersonalAccessTokens(get_personal_access_tokens) => {
- Ok(get_personal_access_tokens)
- }
- _ => Err(IggyError::InvalidCommand),
- }
- }
+pub async fn handle_get_personal_access_tokens(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_personal_access_tokens");
+ shard.ensure_authenticated(session)?;
+ let personal_access_tokens = shard
+ .get_personal_access_tokens(session.get_user_id())
+ .error(|e: &IggyError| {
+ format!(
+ "{COMPONENT} (error: {e}) - failed to get personal access tokens for user: {}",
+ session.get_user_id()
+ )
+ })?;
+ let tokens: Vec<PersonalAccessTokenResponse> = personal_access_tokens
+ .iter()
+ .map(|pat| {
+ Ok(PersonalAccessTokenResponse {
+ name: WireName::new(pat.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ expiry_at: pat.expiry_at.map_or(0, |e| e.as_micros()),
+ })
+ })
+ .collect::<Result<_, IggyError>>()?;
+ let response = GetPersonalAccessTokensResponse { tokens };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/personal_access_tokens/login_with_personal_access_token_handler.rs b/core/server/src/binary/handlers/personal_access_tokens/login_with_personal_access_token_handler.rs
index 3a7475d3b..2b32ff51c 100644
--- a/core/server/src/binary/handlers/personal_access_tokens/login_with_personal_access_token_handler.rs
+++ b/core/server/src/binary/handlers/personal_access_tokens/login_with_personal_access_token_handler.rs
@@ -15,60 +15,44 @@
* specific language governing permissions and limitations
* under the License.
*/
-use crate::shard::IggyShard;
-use std::rc::Rc;
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::HandlerResult;
use crate::binary::handlers::personal_access_tokens::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::personal_access_tokens::LoginWithPersonalAccessTokenRequest;
+use iggy_binary_protocol::responses::users::IdentityResponse;
use iggy_common::login_with_personal_access_token::LoginWithPersonalAccessToken;
-use iggy_common::{IggyError, SenderKind};
-use secrecy::ExposeSecret;
+use iggy_common::{IggyError, SenderKind, Validatable};
+use secrecy::SecretString;
+use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for LoginWithPersonalAccessToken {
- fn code(&self) -> u32 {
- iggy_common::LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE
- }
+#[instrument(skip_all, name = "trace_login_with_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_login_with_personal_access_token(
+ req: LoginWithPersonalAccessTokenRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: login_with_personal_access_token");
+ let token = req.token.as_str();
- #[instrument(skip_all, name = "trace_login_with_personal_access_token", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- let token = self.token.expose_secret();
- let user = shard
- .login_with_personal_access_token(token, Some(session))
- .error(|e: &IggyError| {
- format!(
- "{COMPONENT} (error: {e}) - failed to login with personal access token, session: {session}",
- )
- })?;
- let identity_info = mapper::map_identity_info(user.id);
- sender.send_ok_response(&identity_info).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let command = LoginWithPersonalAccessToken {
+ token: SecretString::from(token),
+ };
+ command.validate()?;
-impl BinaryServerCommand for LoginWithPersonalAccessToken {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::LoginWithPersonalAccessToken(login_with_personal_access_token) => {
- Ok(login_with_personal_access_token)
- }
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let user = shard
+ .login_with_personal_access_token(token, Some(session))
+ .error(|e: &IggyError| {
+ format!(
+ "{COMPONENT} (error: {e}) - failed to login with personal access token, session: {session}",
+ )
+ })?;
+ let response = IdentityResponse { user_id: user.id };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/segments/delete_segments_handler.rs b/core/server/src/binary/handlers/segments/delete_segments_handler.rs
index c8aa5ddc1..cf55d875c 100644
--- a/core/server/src/binary/handlers/segments/delete_segments_handler.rs
+++ b/core/server/src/binary/handlers/segments/delete_segments_handler.rs
@@ -16,80 +16,61 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
-use iggy_common::delete_segments::DeleteSegments;
+use iggy_binary_protocol::requests::segments::DeleteSegmentsRequest;
use iggy_common::sharding::IggyNamespace;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeleteSegments {
- fn code(&self) -> u32 {
- iggy_common::DELETE_SEGMENTS_CODE
- }
-
- #[instrument(skip_all, name = "trace_delete_segments", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+#[instrument(skip_all, name = "trace_delete_segments", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_segments(
+ req: DeleteSegmentsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: delete_segments, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- let partition_id = self.partition_id as usize;
- let segments_count = self.segments_count;
+ let partition_id = req.partition_id as usize;
+ let segments_count = req.segments_count;
- let partition = shard.resolve_partition_for_delete_segments(
- session.get_user_id(),
- &self.stream_id,
- &self.topic_id,
- partition_id,
- )?;
+ let partition = shard.resolve_partition_for_delete_segments(
+ session.get_user_id(),
+ &stream_id,
+ &topic_id,
+ partition_id,
+ )?;
- let namespace = IggyNamespace::new(
- partition.stream_id,
- partition.topic_id,
- partition.partition_id,
- );
- let payload = ShardRequestPayload::DeleteSegments { segments_count };
- let request = ShardRequest::data_plane(namespace, payload);
+ let namespace = IggyNamespace::new(
+ partition.stream_id,
+ partition.topic_id,
+ partition.partition_id,
+ );
+ let payload = ShardRequestPayload::DeleteSegments { segments_count };
+ let request = ShardRequest::data_plane(namespace, payload);
- match shard.send_to_data_plane(request).await? {
- ShardResponse::DeleteSegments {
- deleted_segments,
- deleted_messages,
- } => {
- shard.metrics.decrement_segments(deleted_segments as u32);
- shard.metrics.decrement_messages(deleted_messages);
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeleteSegments"),
+ match shard.send_to_data_plane(request).await? {
+ ShardResponse::DeleteSegments {
+ deleted_segments,
+ deleted_messages,
+ } => {
+ shard.metrics.decrement_segments(deleted_segments as u32);
+ shard.metrics.decrement_messages(deleted_messages);
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeleteSegments"),
}
-}
-impl BinaryServerCommand for DeleteSegments {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteSegments(delete_segments) => Ok(delete_segments),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/segments/mod.rs b/core/server/src/binary/handlers/segments/mod.rs
index 551c850b1..b1e454f5b 100644
--- a/core/server/src/binary/handlers/segments/mod.rs
+++ b/core/server/src/binary/handlers/segments/mod.rs
@@ -15,4 +15,4 @@
* specific language governing permissions and limitations
* under the License.
*/
-mod delete_segments_handler;
+pub mod delete_segments_handler;
diff --git a/core/server/src/binary/handlers/streams/create_stream_handler.rs b/core/server/src/binary/handlers/streams/create_stream_handler.rs
index f1a4e5e66..15aa88888 100644
--- a/core/server/src/binary/handlers/streams/create_stream_handler.rs
+++ b/core/server/src/binary/handlers/streams/create_stream_handler.rs
@@ -16,64 +16,58 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::streams::CreateStreamRequest;
+use iggy_binary_protocol::responses::streams::StreamResponse;
use iggy_common::create_stream::CreateStream;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for CreateStream {
- fn code(&self) -> u32 {
- iggy_common::CREATE_STREAM_CODE
- }
+#[instrument(skip_all, name = "trace_create_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_stream(
+ req: CreateStreamRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: create_stream, name: {}",
+ req.name.as_str()
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_create_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = CreateStream {
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::CreateStreamRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::CreateStreamRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreateStreamResponse(data) => {
- sender
- .send_ok_response(&mapper::map_stream_from_response(&data))
- .await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreateStreamResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreateStreamResponse(data) => {
+ let response = StreamResponse {
+ id: data.id,
+ created_at: data.created_at.into(),
+ topics_count: 0,
+ size_bytes: 0,
+ messages_count: 0,
+ name: WireName::new(data.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreateStreamResponse"),
}
-}
-impl BinaryServerCommand for CreateStream {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreateStream(create_stream) => Ok(create_stream),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/streams/delete_stream_handler.rs b/core/server/src/binary/handlers/streams/delete_stream_handler.rs
index 0ff0d06ab..bb142fdd7 100644
--- a/core/server/src/binary/handlers/streams/delete_stream_handler.rs
+++ b/core/server/src/binary/handlers/streams/delete_stream_handler.rs
@@ -16,61 +16,45 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::streams::DeleteStreamRequest;
use iggy_common::delete_stream::DeleteStream;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeleteStream {
- fn code(&self) -> u32 {
- iggy_common::DELETE_STREAM_CODE
- }
+#[instrument(skip_all, name = "trace_delete_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_stream(
+ req: DeleteStreamRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: delete_stream, stream_id: {:?}",
+ req.stream_id
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_delete_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let command = DeleteStream { stream_id };
- let request = ShardRequest::control_plane(ShardRequestPayload::DeleteStreamRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::DeleteStreamRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteStreamResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeleteStreamResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeleteStreamResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeleteStreamResponse"),
}
-}
-impl BinaryServerCommand for DeleteStream {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteStream(delete_stream) => Ok(delete_stream),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/streams/get_stream_handler.rs b/core/server/src/binary/handlers/streams/get_stream_handler.rs
index 1a29a226a..fd451a71d 100644
--- a/core/server/src/binary/handlers/streams/get_stream_handler.rs
+++ b/core/server/src/binary/handlers/streams/get_stream_handler.rs
@@ -16,57 +16,107 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
+use crate::metadata::{StreamMeta, TopicMeta};
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::streams::GetStreamRequest;
+use iggy_binary_protocol::responses::streams::StreamResponse;
+use iggy_binary_protocol::responses::streams::get_stream::{GetStreamResponse, TopicHeader};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_stream::GetStream;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetStream {
- fn code(&self) -> u32 {
- iggy_common::GET_STREAM_CODE
- }
+pub async fn handle_get_stream(
+ req: GetStreamRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: get_stream, stream_id: {:?}",
+ req.stream_id
+ );
+ shard.ensure_authenticated(session)?;
+
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+
+ let Some(stream) = shard
+ .metadata
+ .query_stream(session.get_user_id(), &stream_id)?
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let response = build_get_stream_response(&stream)?;
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
+}
- let Some(stream) = shard
- .metadata
- .query_stream(session.get_user_id(), &self.stream_id)?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
+pub(crate) fn compute_stream_stats(stream: &StreamMeta) -> (u64, u64) {
+ let mut size = 0u64;
+ let mut messages = 0u64;
+ for (_, topic) in stream.topics.iter() {
+ for partition in topic.partitions.iter() {
+ size += partition.stats.size_bytes_inconsistent();
+ messages += partition.stats.messages_count_inconsistent();
+ }
+ }
+ (size, messages)
+}
- let response = mapper::map_stream(&stream);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
+fn compute_topic_stats(topic: &TopicMeta) -> (u64, u64) {
+ let mut size = 0u64;
+ let mut messages = 0u64;
+ for partition in topic.partitions.iter() {
+ size += partition.stats.size_bytes_inconsistent();
+ messages += partition.stats.messages_count_inconsistent();
}
+ (size, messages)
+}
+
+pub(crate) fn build_topic_header(topic: &TopicMeta) -> Result<TopicHeader, IggyError> {
+ let (size, messages) = compute_topic_stats(topic);
+ Ok(TopicHeader {
+ id: topic.id as u32,
+ created_at: topic.created_at.into(),
+ partitions_count: topic.partitions.len() as u32,
+ message_expiry: topic.message_expiry.into(),
+ compression_algorithm: topic.compression_algorithm.as_code(),
+ max_topic_size: topic.max_topic_size.into(),
+ replication_factor: topic.replication_factor,
+ size_bytes: size,
+ messages_count: messages,
+ name: WireName::new(topic.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ })
}
-impl BinaryServerCommand for GetStream {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetStream(get_stream) => Ok(get_stream),
- _ => Err(IggyError::InvalidCommand),
+fn build_get_stream_response(stream: &StreamMeta) -> Result<GetStreamResponse, IggyError> {
+ let mut topic_ids: Vec<_> = stream.topics.iter().map(|(k, _)| k).collect();
+ topic_ids.sort_unstable();
+
+ let (total_size, total_messages) = compute_stream_stats(stream);
+
+ let mut topics = Vec::with_capacity(topic_ids.len());
+ for &topic_id in &topic_ids {
+ if let Some(topic) = stream.topics.get(topic_id) {
+ topics.push(build_topic_header(topic)?);
}
}
+
+ Ok(GetStreamResponse {
+ stream: StreamResponse {
+ id: stream.id as u32,
+ created_at: stream.created_at.into(),
+ topics_count: topic_ids.len() as u32,
+ size_bytes: total_size,
+ messages_count: total_messages,
+ name: WireName::new(stream.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ },
+ topics,
+ })
}
diff --git a/core/server/src/binary/handlers/streams/get_streams_handler.rs b/core/server/src/binary/handlers/streams/get_streams_handler.rs
index 8cea11ba7..ca5524378 100644
--- a/core/server/src/binary/handlers/streams/get_streams_handler.rs
+++ b/core/server/src/binary/handlers/streams/get_streams_handler.rs
@@ -16,50 +16,48 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use super::get_stream_handler::compute_stream_stats;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::streams::StreamResponse;
+use iggy_binary_protocol::responses::streams::get_streams::GetStreamsResponse;
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_streams::GetStreams;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetStreams {
- fn code(&self) -> u32 {
- iggy_common::GET_STREAMS_CODE
- }
+pub async fn handle_get_streams(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_streams");
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let streams = shard.metadata.query_streams(session.get_user_id())?;
- let streams = shard.metadata.query_streams(session.get_user_id())?;
- let response = mapper::map_streams(&streams);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let mut sorted: Vec<_> = streams.iter().collect();
+ sorted.sort_by_key(|s| s.id);
-impl BinaryServerCommand for GetStreams {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetStreams(get_streams) => Ok(get_streams),
- _ => Err(IggyError::InvalidCommand),
- }
+ let mut wire_streams = Vec::with_capacity(sorted.len());
+ for stream in sorted {
+ let (total_size, total_messages) = compute_stream_stats(stream);
+ wire_streams.push(StreamResponse {
+ id: stream.id as u32,
+ created_at: stream.created_at.into(),
+ topics_count: stream.topics.len() as u32,
+ size_bytes: total_size,
+ messages_count: total_messages,
+ name: WireName::new(stream.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ });
}
+
+ let response = GetStreamsResponse {
+ streams: wire_streams,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/streams/purge_stream_handler.rs b/core/server/src/binary/handlers/streams/purge_stream_handler.rs
index 6346fba9e..7bffb6ad4 100644
--- a/core/server/src/binary/handlers/streams/purge_stream_handler.rs
+++ b/core/server/src/binary/handlers/streams/purge_stream_handler.rs
@@ -16,61 +16,45 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::streams::PurgeStreamRequest;
use iggy_common::purge_stream::PurgeStream;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for PurgeStream {
- fn code(&self) -> u32 {
- iggy_common::PURGE_STREAM_CODE
- }
+#[instrument(skip_all, name = "trace_purge_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_purge_stream(
+ req: PurgeStreamRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: purge_stream, stream_id: {:?}",
+ req.stream_id
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_purge_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let command = PurgeStream { stream_id };
- let request = ShardRequest::control_plane(ShardRequestPayload::PurgeStreamRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::PurgeStreamRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::PurgeStreamResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected PurgeStreamResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::PurgeStreamResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected PurgeStreamResponse"),
}
-}
-impl BinaryServerCommand for PurgeStream {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::PurgeStream(purge_stream) => Ok(purge_stream),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/streams/update_stream_handler.rs b/core/server/src/binary/handlers/streams/update_stream_handler.rs
index 2deb7fcd2..d72e383b6 100644
--- a/core/server/src/binary/handlers/streams/update_stream_handler.rs
+++ b/core/server/src/binary/handlers/streams/update_stream_handler.rs
@@ -16,61 +16,49 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::streams::UpdateStreamRequest;
use iggy_common::update_stream::UpdateStream;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for UpdateStream {
- fn code(&self) -> u32 {
- iggy_common::UPDATE_STREAM_CODE
- }
+#[instrument(skip_all, name = "trace_update_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_update_stream(
+ req: UpdateStreamRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: update_stream, stream_id: {:?}",
+ req.stream_id
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_update_stream", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let command = UpdateStream {
+ stream_id,
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::UpdateStreamRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::UpdateStreamRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::UpdateStreamResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected UpdateStreamResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::UpdateStreamResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected UpdateStreamResponse"),
}
-}
-impl BinaryServerCommand for UpdateStream {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::UpdateStream(update_stream) => Ok(update_stream),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/system/get_client_handler.rs b/core/server/src/binary/handlers/system/get_client_handler.rs
index 51bee9e2f..594cf8373 100644
--- a/core/server/src/binary/handlers/system/get_client_handler.rs
+++ b/core/server/src/binary/handlers/system/get_client_handler.rs
@@ -16,55 +16,40 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use super::get_me_handler::build_client_details_response;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::system::GetClientRequest;
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_client::GetClient;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetClient {
- fn code(&self) -> u32 {
- iggy_common::GET_CLIENT_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_get_client(session.get_user_id())?;
-
- let Some(client) = shard.get_client(self.client_id) else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
-
- let bytes = mapper::map_client(&client);
+pub async fn handle_get_client(
+ req: GetClientRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: get_client, client_id: {}",
+ req.client_id
+ );
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_get_client(session.get_user_id())?;
- sender.send_ok_response(&bytes).await?;
- Ok(HandlerResult::Finished)
+ if req.client_id == 0 {
+ return Err(IggyError::InvalidClientId);
}
-}
-impl BinaryServerCommand for GetClient {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetClient(get_client) => Ok(get_client),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let Some(client) = shard.get_client(req.client_id) else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
+
+ let response = build_client_details_response(&client);
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/system/get_clients_handler.rs b/core/server/src/binary/handlers/system/get_clients_handler.rs
index ad5ccdc2d..cceda4622 100644
--- a/core/server/src/binary/handlers/system/get_clients_handler.rs
+++ b/core/server/src/binary/handlers/system/get_clients_handler.rs
@@ -16,51 +16,30 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use super::get_me_handler::build_client_response;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::clients::GetClientsResponse;
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_clients::GetClients;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetClients {
- fn code(&self) -> u32 {
- iggy_common::GET_CLIENTS_CODE
- }
+pub async fn handle_get_clients(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_clients");
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_get_clients(session.get_user_id())?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_get_clients(session.get_user_id())?;
-
- let clients = shard.get_clients();
- let clients = mapper::map_clients(clients).await;
- sender.send_ok_response(&clients).await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for GetClients {
- async fn from_sender(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
- ) -> Result<Self, IggyError> {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetClients(get_clients) => Ok(get_clients),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let clients = shard.get_clients();
+ let response = GetClientsResponse {
+ clients: clients.iter().map(build_client_response).collect(),
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/system/get_me_handler.rs b/core/server/src/binary/handlers/system/get_me_handler.rs
index 6913a7529..0d635bf6a 100644
--- a/core/server/src/binary/handlers/system/get_me_handler.rs
+++ b/core/server/src/binary/handlers/system/get_me_handler.rs
@@ -16,50 +16,63 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
+use crate::streaming::clients::client_manager::Client;
use crate::streaming::session::Session;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::clients::{
+ ClientDetailsResponse, ClientResponse, ConsumerGroupInfoResponse,
+};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_me::GetMe;
use std::rc::Rc;
-impl ServerCommandHandler for GetMe {
- fn code(&self) -> u32 {
- iggy_common::GET_ME_CODE
- }
+pub async fn handle_get_me(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ shard.ensure_authenticated(session)?;
+ let Some(client) = shard.get_client(session.client_id) else {
+ return Err(IggyError::ClientNotFound(session.client_id));
+ };
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- shard.ensure_authenticated(session)?;
- let Some(client) = shard.get_client(session.client_id) else {
- return Err(IggyError::ClientNotFound(session.client_id));
- };
+ let response = build_client_details_response(&client);
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
+}
- let bytes = mapper::map_client(&client);
+pub(crate) fn build_client_details_response(client: &Client) -> ClientDetailsResponse {
+ ClientDetailsResponse {
+ client: build_client_response(client),
+ consumer_groups: client
+ .consumer_groups
+ .iter()
+ .map(|cg| ConsumerGroupInfoResponse {
+ stream_id: cg.stream_id,
+ topic_id: cg.topic_id,
+ group_id: cg.group_id,
+ })
+ .collect(),
+ }
+}
- sender.send_ok_response(&bytes).await?;
- Ok(HandlerResult::Finished)
+pub(crate) fn build_client_response(client: &Client) -> ClientResponse {
+ ClientResponse {
+ client_id: client.session.client_id,
+ user_id: client.user_id.unwrap_or(u32::MAX),
+ transport: transport_to_u8(&client.transport),
+ address: client.session.ip_address.to_string(),
+ consumer_groups_count: client.consumer_groups.len() as u32,
}
}
-impl BinaryServerCommand for GetMe {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetMe(get_me) => Ok(get_me),
- _ => Err(IggyError::InvalidCommand),
- }
+pub(crate) fn transport_to_u8(transport: &iggy_common::TransportProtocol) -> u8 {
+ match transport {
+ iggy_common::TransportProtocol::Tcp => 1,
+ iggy_common::TransportProtocol::Quic => 2,
+ iggy_common::TransportProtocol::Http => 3,
+ iggy_common::TransportProtocol::WebSocket => 4,
}
}
diff --git a/core/server/src/binary/handlers/system/get_snapshot_handler.rs b/core/server/src/binary/handlers/system/get_snapshot_handler.rs
index 082ef8b70..4eabf63ac 100644
--- a/core/server/src/binary/handlers/system/get_snapshot_handler.rs
+++ b/core/server/src/binary/handlers/system/get_snapshot_handler.rs
@@ -16,51 +16,37 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use bytes::Bytes;
-use iggy_common::IggyError;
-use iggy_common::SenderKind;
-use iggy_common::get_snapshot::GetSnapshot;
+use iggy_binary_protocol::requests::system::GetSnapshotRequest;
+use iggy_common::{IggyError, SenderKind, SnapshotCompression, SystemSnapshotType};
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetSnapshot {
- fn code(&self) -> u32 {
- iggy_common::GET_SNAPSHOT_FILE_CODE
- }
+pub async fn handle_get_snapshot(
+ req: GetSnapshotRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_snapshot");
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let compression = SnapshotCompression::from_code(req.compression)?;
+ let snapshot_types: Vec<SystemSnapshotType> = req
+ .snapshot_types
+ .iter()
+ .map(|&code| SystemSnapshotType::from_code(code))
+ .collect::<Result<_, _>>()?;
- let snapshot = shard
- .get_snapshot(self.compression, &self.snapshot_types)
- .await?;
- let bytes = Bytes::copy_from_slice(&snapshot.0);
- sender.send_ok_response(&bytes).await?;
- Ok(HandlerResult::Finished)
+ if snapshot_types.contains(&SystemSnapshotType::All) && snapshot_types.len() > 1 {
+ return Err(IggyError::InvalidCommand);
}
-}
-impl BinaryServerCommand for GetSnapshot {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetSnapshot(get_snapshot) => Ok(get_snapshot),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let snapshot = shard.get_snapshot(compression, &snapshot_types).await?;
+ let bytes = Bytes::copy_from_slice(&snapshot.0);
+ sender.send_ok_response(&bytes).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/system/get_stats_handler.rs b/core/server/src/binary/handlers/system/get_stats_handler.rs
index 6ec310920..26aa2ec23 100644
--- a/core/server/src/binary/handlers/system/get_stats_handler.rs
+++ b/core/server/src/binary/handlers/system/get_stats_handler.rs
@@ -16,62 +16,79 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::system::get_stats::{CacheMetricEntry, StatsResponse};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_stats::GetStats;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetStats {
- fn code(&self) -> u32 {
- iggy_common::GET_STATS_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_get_stats(session.get_user_id())?;
+pub async fn handle_get_stats(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_stats");
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_get_stats(session.get_user_id())?;
- let request = ShardRequest::control_plane(ShardRequestPayload::GetStats {
- user_id: session.get_user_id(),
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::GetStats {
+ user_id: session.get_user_id(),
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::GetStatsResponse(stats) => {
- sender.send_ok_response(&mapper::map_stats(&stats)).await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected GetStatsResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::GetStatsResponse(stats) => {
+ let response = StatsResponse {
+ process_id: stats.process_id,
+ cpu_usage: stats.cpu_usage,
+ total_cpu_usage: stats.total_cpu_usage,
+ memory_usage: stats.memory_usage.as_bytes_u64(),
+ total_memory: stats.total_memory.as_bytes_u64(),
+ available_memory: stats.available_memory.as_bytes_u64(),
+ run_time: stats.run_time.into(),
+ start_time: stats.start_time.into(),
+ read_bytes: stats.read_bytes.as_bytes_u64(),
+ written_bytes: stats.written_bytes.as_bytes_u64(),
+ messages_size_bytes: stats.messages_size_bytes.as_bytes_u64(),
+ streams_count: stats.streams_count,
+ topics_count: stats.topics_count,
+ partitions_count: stats.partitions_count,
+ segments_count: stats.segments_count,
+ messages_count: stats.messages_count,
+ clients_count: stats.clients_count,
+ consumer_groups_count: stats.consumer_groups_count,
+ hostname: stats.hostname,
+ os_name: stats.os_name,
+ os_version: stats.os_version,
+ kernel_version: stats.kernel_version,
+ iggy_server_version: stats.iggy_server_version,
+ iggy_server_semver: stats.iggy_server_semver,
+ cache_metrics: stats
+ .cache_metrics
+ .iter()
+ .map(|(key, metrics)| CacheMetricEntry {
+ stream_id: key.stream_id,
+ topic_id: key.topic_id,
+ partition_id: key.partition_id,
+ hits: metrics.hits,
+ misses: metrics.misses,
+ hit_ratio: metrics.hit_ratio,
+ })
+ .collect(),
+ threads_count: stats.threads_count,
+ free_disk_space: stats.free_disk_space.as_bytes_u64(),
+ total_disk_space: stats.total_disk_space.as_bytes_u64(),
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected GetStatsResponse"),
}
-}
-impl BinaryServerCommand for GetStats {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- let server_command = receive_and_validate(sender, code, length).await?;
- match server_command {
- ServerCommand::GetStats(get_stats) => Ok(get_stats),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/system/ping_handler.rs b/core/server/src/binary/handlers/system/ping_handler.rs
index 746de588f..6633cd0b0 100644
--- a/core/server/src/binary/handlers/system/ping_handler.rs
+++ b/core/server/src/binary/handlers/system/ping_handler.rs
@@ -16,49 +16,27 @@
* under the License.
*/
-use crate::binary::command::{BinaryServerCommand, HandlerResult, ServerCommandHandler};
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use iggy_common::IggyError;
use iggy_common::IggyTimestamp;
use iggy_common::SenderKind;
-use iggy_common::ping::Ping;
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for Ping {
- fn code(&self) -> u32 {
- iggy_common::PING_CODE
+pub async fn handle_ping(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: ping");
+ if let Some(mut client) = shard.client_manager.try_get_client_mut(session.client_id) {
+ let now = IggyTimestamp::now();
+ client.last_heartbeat = now;
+ debug!("Updated last heartbeat to: {now} for session: {session}");
}
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- if let Some(mut client) = shard.client_manager.try_get_client_mut(session.client_id) {
- let now = IggyTimestamp::now();
- client.last_heartbeat = now;
- debug!("Updated last heartbeat to: {now} for session: {session}");
- }
-
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
- }
-}
-
-impl BinaryServerCommand for Ping {
- async fn from_sender(
- _sender: &mut SenderKind,
- _length: u32,
- _code: u32,
- ) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- Ok(Ping {})
- }
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/topics/create_topic_handler.rs b/core/server/src/binary/handlers/topics/create_topic_handler.rs
index dbadbb07f..10c142d9a 100644
--- a/core/server/src/binary/handlers/topics/create_topic_handler.rs
+++ b/core/server/src/binary/handlers/topics/create_topic_handler.rs
@@ -16,63 +16,101 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use bytes::BytesMut;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::topics::CreateTopicRequest;
+use iggy_binary_protocol::responses::streams::get_stream::TopicHeader;
+use iggy_binary_protocol::responses::topics::get_topic::PartitionResponse;
use iggy_common::create_topic::CreateTopic;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{
+ CompressionAlgorithm, IggyError, IggyExpiry, MaxTopicSize, SenderKind, Validatable,
+};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for CreateTopic {
- fn code(&self) -> u32 {
- iggy_common::CREATE_TOPIC_CODE
- }
+#[instrument(skip_all, name = "trace_create_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_topic(
+ req: CreateTopicRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ debug!(
+ "session: {session}, command: create_topic, stream_id: {stream_id}, name: {}",
+ req.name.as_str()
+ );
+ shard.ensure_authenticated(session)?;
+
+ let command = CreateTopic {
+ stream_id,
+ partitions_count: req.partitions_count,
+ compression_algorithm: CompressionAlgorithm::from_code(req.compression_algorithm)?,
+ message_expiry: IggyExpiry::from(req.message_expiry),
+ max_topic_size: MaxTopicSize::from(req.max_topic_size),
+ replication_factor: if req.replication_factor == 0 {
+ None
+ } else {
+ Some(req.replication_factor)
+ },
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- #[instrument(skip_all, name = "trace_create_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let request = ShardRequest::control_plane(ShardRequestPayload::CreateTopicRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- let request = ShardRequest::control_plane(ShardRequestPayload::CreateTopicRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreateTopicResponse(data) => {
+ let header = TopicHeader {
+ id: data.id,
+ created_at: data.created_at.into(),
+ partitions_count: data.partitions.len() as u32,
+ message_expiry: data.message_expiry.into(),
+ compression_algorithm: data.compression_algorithm.as_code(),
+ max_topic_size: data.max_topic_size.into(),
+ replication_factor: data.replication_factor,
+ size_bytes: 0,
+ messages_count: 0,
+ name: WireName::new(data.name.as_ref()).map_err(|_| IggyError::InvalidCommand)?,
+ };
+ let partitions: Vec<PartitionResponse> = data
+ .partitions
+ .iter()
+ .map(|p| PartitionResponse {
+ id: p.id as u32,
+ created_at: p.created_at.into(),
+ segments_count: 0,
+ current_offset: 0,
+ size_bytes: 0,
+ messages_count: 0,
+ })
+ .collect();
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreateTopicResponse(data) => {
- sender
- .send_ok_response(&mapper::map_topic_from_response(&data))
- .await?;
+ let mut buf = BytesMut::with_capacity(
+ header.encoded_size()
+ + partitions
+ .iter()
+ .map(WireEncode::encoded_size)
+ .sum::<usize>(),
+ );
+ header.encode(&mut buf);
+ for partition in &partitions {
+ partition.encode(&mut buf);
}
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreateTopicResponse"),
+ sender.send_ok_response(&buf.freeze()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreateTopicResponse"),
}
-}
-impl BinaryServerCommand for CreateTopic {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreateTopic(create_topic) => Ok(create_topic),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/topics/delete_topic_handler.rs b/core/server/src/binary/handlers/topics/delete_topic_handler.rs
index 6305be292..53cbc9d1c 100644
--- a/core/server/src/binary/handlers/topics/delete_topic_handler.rs
+++ b/core/server/src/binary/handlers/topics/delete_topic_handler.rs
@@ -16,60 +16,48 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::topics::DeleteTopicRequest;
use iggy_common::delete_topic::DeleteTopic;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeleteTopic {
- fn code(&self) -> u32 {
- iggy_common::DELETE_TOPIC_CODE
- }
+#[instrument(skip_all, name = "trace_delete_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_topic(
+ req: DeleteTopicRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: delete_topic, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_delete_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = DeleteTopic {
+ stream_id,
+ topic_id,
+ };
- let request = ShardRequest::control_plane(ShardRequestPayload::DeleteTopicRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::DeleteTopicRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteTopicResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeleteTopicResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeleteTopicResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeleteTopicResponse"),
}
-}
-impl BinaryServerCommand for DeleteTopic {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteTopic(delete_topic) => Ok(delete_topic),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/topics/get_topic_handler.rs b/core/server/src/binary/handlers/topics/get_topic_handler.rs
index 052cebb2c..5e44a4b4a 100644
--- a/core/server/src/binary/handlers/topics/get_topic_handler.rs
+++ b/core/server/src/binary/handlers/topics/get_topic_handler.rs
@@ -16,57 +16,61 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
+use crate::binary::handlers::streams::get_stream_handler::build_topic_header;
+use crate::metadata::TopicMeta;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
-use iggy_common::IggyError;
-use iggy_common::SenderKind;
-use iggy_common::get_topic::GetTopic;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::topics::GetTopicRequest;
+use iggy_binary_protocol::responses::topics::get_topic::{GetTopicResponse, PartitionResponse};
+use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetTopic {
- fn code(&self) -> u32 {
- iggy_common::GET_TOPIC_CODE
- }
+pub async fn handle_get_topic(
+ req: GetTopicRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!("session: {session}, command: get_topic, stream_id: {stream_id}, topic_id: {topic_id}");
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let Some(topic) = shard
+ .metadata
+ .query_topic(session.get_user_id(), &stream_id, &topic_id)?
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- let Some(topic) =
- shard
- .metadata
- .query_topic(session.get_user_id(), &self.stream_id, &self.topic_id)?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
-
- let response = mapper::map_topic(&topic);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
+ let response = build_get_topic_response(&topic)?;
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
-impl BinaryServerCommand for GetTopic {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetTopic(get_topic) => Ok(get_topic),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+fn build_get_topic_response(topic: &TopicMeta) -> Result<GetTopicResponse, IggyError> {
+ let header = build_topic_header(topic)?;
+
+ let partitions: Vec<PartitionResponse> = topic
+ .partitions
+ .iter()
+ .enumerate()
+ .map(|(partition_id, partition)| PartitionResponse {
+ id: partition_id as u32,
+ created_at: partition.created_at.into(),
+ segments_count: partition.stats.segments_count_inconsistent(),
+ current_offset: partition.stats.current_offset(),
+ size_bytes: partition.stats.size_bytes_inconsistent(),
+ messages_count: partition.stats.messages_count_inconsistent(),
+ })
+ .collect();
+
+ Ok(GetTopicResponse {
+ topic: header,
+ partitions,
+ })
}
diff --git a/core/server/src/binary/handlers/topics/get_topics_handler.rs b/core/server/src/binary/handlers/topics/get_topics_handler.rs
index 41987f3f4..e8d0a3d30 100644
--- a/core/server/src/binary/handlers/topics/get_topics_handler.rs
+++ b/core/server/src/binary/handlers/topics/get_topics_handler.rs
@@ -16,56 +16,46 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
+use crate::binary::handlers::streams::get_stream_handler::build_topic_header;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
-use iggy_common::IggyError;
-use iggy_common::SenderKind;
-use iggy_common::get_topics::GetTopics;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::topics::GetTopicsRequest;
+use iggy_binary_protocol::responses::topics::get_topics::GetTopicsResponse;
+use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetTopics {
- fn code(&self) -> u32 {
- iggy_common::GET_TOPICS_CODE
- }
+pub async fn handle_get_topics(
+ req: GetTopicsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ debug!("session: {session}, command: get_topics, stream_id: {stream_id}");
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let Some(topics) = shard
+ .metadata
+ .query_topics(session.get_user_id(), &stream_id)?
+ else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
- let Some(topics) = shard
- .metadata
- .query_topics(session.get_user_id(), &self.stream_id)?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
+ let mut sorted: Vec<_> = topics.iter().collect();
+ sorted.sort_by_key(|t| t.id);
- let response = mapper::map_topics(&topics);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
+ let mut wire_topics = Vec::with_capacity(sorted.len());
+ for topic in sorted {
+ wire_topics.push(build_topic_header(topic)?);
}
-}
-impl BinaryServerCommand for GetTopics {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetTopics(get_topics) => Ok(get_topics),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let response = GetTopicsResponse {
+ topics: wire_topics,
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/topics/purge_topic_handler.rs b/core/server/src/binary/handlers/topics/purge_topic_handler.rs
index 1c1281559..579c3b29e 100644
--- a/core/server/src/binary/handlers/topics/purge_topic_handler.rs
+++ b/core/server/src/binary/handlers/topics/purge_topic_handler.rs
@@ -16,60 +16,48 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::topics::PurgeTopicRequest;
use iggy_common::purge_topic::PurgeTopic;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for PurgeTopic {
- fn code(&self) -> u32 {
- iggy_common::PURGE_TOPIC_CODE
- }
+#[instrument(skip_all, name = "trace_purge_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_purge_topic(
+ req: PurgeTopicRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: purge_topic, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_purge_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = PurgeTopic {
+ stream_id,
+ topic_id,
+ };
- let request = ShardRequest::control_plane(ShardRequestPayload::PurgeTopicRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::PurgeTopicRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::PurgeTopicResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected PurgeTopicResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::PurgeTopicResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected PurgeTopicResponse"),
}
-}
-impl BinaryServerCommand for PurgeTopic {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::PurgeTopic(purge_topic) => Ok(purge_topic),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/topics/update_topic_handler.rs b/core/server/src/binary/handlers/topics/update_topic_handler.rs
index cb481e5c8..fa914f23d 100644
--- a/core/server/src/binary/handlers/topics/update_topic_handler.rs
+++ b/core/server/src/binary/handlers/topics/update_topic_handler.rs
@@ -16,60 +16,60 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::topics::UpdateTopicRequest;
use iggy_common::update_topic::UpdateTopic;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{
+ CompressionAlgorithm, IggyError, IggyExpiry, MaxTopicSize, SenderKind, Validatable,
+};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for UpdateTopic {
- fn code(&self) -> u32 {
- iggy_common::UPDATE_TOPIC_CODE
- }
+#[instrument(skip_all, name = "trace_update_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_update_topic(
+ req: UpdateTopicRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ let stream_id = wire_id_to_identifier(&req.stream_id)?;
+ let topic_id = wire_id_to_identifier(&req.topic_id)?;
+ debug!(
+ "session: {session}, command: update_topic, stream_id: {stream_id}, topic_id: {topic_id}"
+ );
+ shard.ensure_authenticated(session)?;
- #[instrument(skip_all, name = "trace_update_topic", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id, iggy_stream_id = self.stream_id.as_string(), iggy_topic_id = self.topic_id.as_string()))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let command = UpdateTopic {
+ stream_id,
+ topic_id,
+ compression_algorithm: CompressionAlgorithm::from_code(req.compression_algorithm)?,
+ message_expiry: IggyExpiry::from(req.message_expiry),
+ max_topic_size: MaxTopicSize::from(req.max_topic_size),
+ replication_factor: if req.replication_factor == 0 {
+ None
+ } else {
+ Some(req.replication_factor)
+ },
+ name: req.name.to_string(),
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::UpdateTopicRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::UpdateTopicRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::UpdateTopicResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected UpdateTopicResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::UpdateTopicResponse => {
+ sender.send_empty_ok_response().await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected UpdateTopicResponse"),
}
-}
-impl BinaryServerCommand for UpdateTopic {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::UpdateTopic(update_topic) => Ok(update_topic),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/change_password_handler.rs b/core/server/src/binary/handlers/users/change_password_handler.rs
index 9bf1f52b3..99f3b5af4 100644
--- a/core/server/src/binary/handlers/users/change_password_handler.rs
+++ b/core/server/src/binary/handlers/users/change_password_handler.rs
@@ -16,65 +16,57 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::users::ChangePasswordRequest;
use iggy_common::change_password::ChangePassword;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, Validatable};
+use secrecy::SecretString;
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for ChangePassword {
- fn code(&self) -> u32 {
- iggy_common::CHANGE_PASSWORD_CODE
- }
-
- #[instrument(skip_all, name = "trace_change_password", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+#[instrument(skip_all, name = "trace_change_password", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_change_password(
+ req: ChangePasswordRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: change_password, user_id: {:?}",
+ req.user_id
+ );
+ shard.ensure_authenticated(session)?;
- let target_user = shard.get_user(&self.user_id)?;
- if target_user.id != session.get_user_id() {
- shard.metadata.perm_change_password(session.get_user_id())?;
- }
+ let user_id = wire_id_to_identifier(&req.user_id)?;
- let request = ShardRequest::control_plane(ShardRequestPayload::ChangePasswordRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let target_user = shard.get_user(&user_id)?;
+ if target_user.id != session.get_user_id() {
+ shard.metadata.perm_change_password(session.get_user_id())?;
+ }
- match shard.send_to_control_plane(request).await? {
- ShardResponse::ChangePasswordResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected ChangePasswordResponse"),
- }
+ let command = ChangePassword {
+ user_id,
+ current_password: SecretString::from(req.current_password),
+ new_password: SecretString::from(req.new_password),
+ };
+ command.validate()?;
- Ok(HandlerResult::Finished)
- }
-}
+ let request = ShardRequest::control_plane(ShardRequestPayload::ChangePasswordRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
-impl BinaryServerCommand for ChangePassword {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::ChangePassword(change_password) => Ok(change_password),
- _ => Err(IggyError::InvalidCommand),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::ChangePasswordResponse => {
+ sender.send_empty_ok_response().await?;
}
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected ChangePasswordResponse"),
}
+
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/create_user_handler.rs b/core/server/src/binary/handlers/users/create_user_handler.rs
index f4994d6f5..68990933d 100644
--- a/core/server/src/binary/handlers/users/create_user_handler.rs
+++ b/core/server/src/binary/handlers/users/create_user_handler.rs
@@ -16,63 +16,70 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
+use crate::binary::dispatch::{
+ HandlerResult, domain_permissions_to_wire, wire_permissions_to_permissions,
};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::users::CreateUserRequest;
+use iggy_binary_protocol::responses::users::{UserDetailsResponse, UserResponse};
use iggy_common::create_user::CreateUser;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, UserStatus, Validatable};
+use secrecy::SecretString;
use std::rc::Rc;
-use tracing::debug;
-use tracing::instrument;
+use tracing::{debug, instrument};
-impl ServerCommandHandler for CreateUser {
- fn code(&self) -> u32 {
- iggy_common::CREATE_USER_CODE
- }
+#[instrument(skip_all, name = "trace_create_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_create_user(
+ req: CreateUserRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: create_user, username: {}",
+ req.username.as_str()
+ );
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_create_user(session.get_user_id())?;
- #[instrument(skip_all, name = "trace_create_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_create_user(session.get_user_id())?;
+ let command = CreateUser {
+ username: req.username.to_string(),
+ password: SecretString::from(req.password),
+ status: UserStatus::from_code(req.status)?,
+ permissions: req
+ .permissions
+ .as_ref()
+ .map(wire_permissions_to_permissions),
+ };
+ command.validate()?;
- let request = ShardRequest::control_plane(ShardRequestPayload::CreateUserRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let request = ShardRequest::control_plane(ShardRequestPayload::CreateUserRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
- match shard.send_to_control_plane(request).await? {
- ShardResponse::CreateUserResponse(user) => {
- sender.send_ok_response(&mapper::map_user(&user)).await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected CreateUserResponse"),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::CreateUserResponse(user) => {
+ let response = UserDetailsResponse {
+ user: UserResponse {
+ id: user.id,
+ created_at: user.created_at.as_micros(),
+ status: user.status.as_code(),
+ username: WireName::new(&user.username)
+ .map_err(|_| IggyError::InvalidCommand)?,
+ },
+ permissions: user.permissions.as_ref().map(domain_permissions_to_wire),
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
}
-
- Ok(HandlerResult::Finished)
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected CreateUserResponse"),
}
-}
-impl BinaryServerCommand for CreateUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::CreateUser(create_user) => Ok(create_user),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/delete_user_handler.rs b/core/server/src/binary/handlers/users/delete_user_handler.rs
index 7164aab70..db8358f77 100644
--- a/core/server/src/binary/handlers/users/delete_user_handler.rs
+++ b/core/server/src/binary/handlers/users/delete_user_handler.rs
@@ -16,61 +16,47 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::users::DeleteUserRequest;
use iggy_common::delete_user::DeleteUser;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for DeleteUser {
- fn code(&self) -> u32 {
- iggy_common::DELETE_USER_CODE
- }
-
- #[instrument(skip_all, name = "trace_delete_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_delete_user(session.get_user_id())?;
+#[instrument(skip_all, name = "trace_delete_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_delete_user(
+ req: DeleteUserRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: delete_user, user_id: {:?}",
+ req.user_id
+ );
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_delete_user(session.get_user_id())?;
- let request = ShardRequest::control_plane(ShardRequestPayload::DeleteUserRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let user_id = wire_id_to_identifier(&req.user_id)?;
- match shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteUserResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected DeleteUserResponse"),
- }
+ let command = DeleteUser { user_id };
- Ok(HandlerResult::Finished)
- }
-}
+ let request = ShardRequest::control_plane(ShardRequestPayload::DeleteUserRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
-impl BinaryServerCommand for DeleteUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::DeleteUser(delete_user) => Ok(delete_user),
- _ => Err(IggyError::InvalidCommand),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::DeleteUserResponse(_) => {
+ sender.send_empty_ok_response().await?;
}
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected DeleteUserResponse"),
}
+
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/get_user_handler.rs b/core/server/src/binary/handlers/users/get_user_handler.rs
index 5a40a1171..66251e46c 100644
--- a/core/server/src/binary/handlers/users/get_user_handler.rs
+++ b/core/server/src/binary/handlers/users/get_user_handler.rs
@@ -16,57 +16,47 @@
* under the License.
*/
-use std::rc::Rc;
-
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::{HandlerResult, domain_permissions_to_wire, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::users::GetUserRequest;
+use iggy_binary_protocol::responses::users::{UserDetailsResponse, UserResponse};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_user::GetUser;
+use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetUser {
- fn code(&self) -> u32 {
- iggy_common::GET_USER_CODE
- }
+pub async fn handle_get_user(
+ req: GetUserRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: get_user, user_id: {:?}",
+ req.user_id
+ );
+ shard.ensure_authenticated(session)?;
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
+ let user_id = wire_id_to_identifier(&req.user_id)?;
- let Some(user) = shard
- .metadata
- .query_user(session.get_user_id(), &self.user_id)?
- else {
- sender.send_empty_ok_response().await?;
- return Ok(HandlerResult::Finished);
- };
-
- let bytes = mapper::map_user_meta(&user);
- sender.send_ok_response(&bytes).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ let Some(user) = shard.metadata.query_user(session.get_user_id(), &user_id)? else {
+ sender.send_empty_ok_response().await?;
+ return Ok(HandlerResult::Finished);
+ };
-impl BinaryServerCommand for GetUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetUser(get_user) => Ok(get_user),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let response = UserDetailsResponse {
+ user: UserResponse {
+ id: user.id,
+ created_at: user.created_at.as_micros(),
+ status: user.status.as_code(),
+ username: WireName::new(user.username.as_ref())
+ .map_err(|_| IggyError::InvalidCommand)?,
+ },
+ permissions: user.permissions.as_deref().map(domain_permissions_to_wire),
+ };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/get_users_handler.rs b/core/server/src/binary/handlers/users/get_users_handler.rs
index df0d4bbff..aaccdeaa5 100644
--- a/core/server/src/binary/handlers/users/get_users_handler.rs
+++ b/core/server/src/binary/handlers/users/get_users_handler.rs
@@ -16,50 +16,39 @@
* under the License.
*/
-use std::rc::Rc;
-
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
+use crate::binary::dispatch::HandlerResult;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
+use iggy_binary_protocol::WireName;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::responses::users::{GetUsersResponse, UserResponse};
use iggy_common::IggyError;
use iggy_common::SenderKind;
-use iggy_common::get_users::GetUsers;
+use std::rc::Rc;
use tracing::debug;
-impl ServerCommandHandler for GetUsers {
- fn code(&self) -> u32 {
- iggy_common::GET_USERS_CODE
- }
-
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
-
- let users = shard.metadata.query_users(session.get_user_id())?;
- let response = mapper::map_users_meta(&users);
- sender.send_ok_response(&response).await?;
- Ok(HandlerResult::Finished)
- }
-}
+pub async fn handle_get_users(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: get_users");
+ shard.ensure_authenticated(session)?;
-impl BinaryServerCommand for GetUsers {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::GetUsers(get_users) => Ok(get_users),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let users = shard.metadata.query_users(session.get_user_id())?;
+ let wire_users: Vec<UserResponse> = users
+ .iter()
+ .map(|u| {
+ Ok(UserResponse {
+ id: u.id,
+ created_at: u.created_at.as_micros(),
+ status: u.status.as_code(),
+ username: WireName::new(u.username.as_ref())
+ .map_err(|_| IggyError::InvalidCommand)?,
+ })
+ })
+ .collect::<Result<_, IggyError>>()?;
+ let response = GetUsersResponse { users: wire_users };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/login_user_handler.rs b/core/server/src/binary/handlers/users/login_user_handler.rs
index 4836ffc7c..c9e42fdb3 100644
--- a/core/server/src/binary/handlers/users/login_user_handler.rs
+++ b/core/server/src/binary/handlers/users/login_user_handler.rs
@@ -16,68 +16,54 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::HandlerResult;
use crate::binary::handlers::users::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
-use crate::binary::mapper;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
+use iggy_binary_protocol::codec::WireEncode;
+use iggy_binary_protocol::requests::users::LoginUserRequest;
+use iggy_binary_protocol::responses::users::IdentityResponse;
use iggy_common::login_user::LoginUser;
-use iggy_common::{IggyError, SenderKind};
-use secrecy::ExposeSecret;
+use iggy_common::{IggyError, SenderKind, Validatable};
+use secrecy::SecretString;
use std::rc::Rc;
use tracing::{debug, info, instrument, warn};
-impl ServerCommandHandler for LoginUser {
- fn code(&self) -> u32 {
- iggy_common::LOGIN_USER_CODE
+#[instrument(skip_all, name = "trace_login_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_login_user(
+ req: LoginUserRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ if shard.is_shutting_down() {
+ warn!("Rejecting login request during shutdown");
+ return Err(IggyError::Disconnected);
}
- #[instrument(skip_all, name = "trace_login_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- if shard.is_shutting_down() {
- warn!("Rejecting login request during shutdown");
- return Err(IggyError::Disconnected);
- }
+ let username = req.username.as_str();
+ debug!("session: {session}, command: login_user, username: {username}");
- debug!("session: {session}, command: {self}");
- let LoginUser {
- username, password, ..
- } = self;
- info!("Logging in user: {} ...", &username);
- let user = shard
- .login_user(&username, password.expose_secret(), Some(session))
- .error(|e: &IggyError| {
- format!(
- "{COMPONENT} (error: {e}) - failed to login user with name: {}, session: {session}",
- username
- )
- })?;
- info!("Logged in user: {} with ID: {}.", username, user.id);
+ let command = LoginUser {
+ username: username.to_string(),
+ password: SecretString::from(req.password.as_str()),
+ version: req.version.clone(),
+ context: req.context.clone(),
+ };
+ command.validate()?;
- let identity_info = mapper::map_identity_info(user.id);
- sender.send_ok_response(&identity_info).await?;
- Ok(HandlerResult::Finished)
- }
-}
+ info!("Logging in user: {username} ...");
+ let user = shard
+ .login_user(username, &req.password, Some(session))
+ .error(|e: &IggyError| {
+ format!(
+ "{COMPONENT} (error: {e}) - failed to login user with name: {username}, session: {session}",
+ )
+ })?;
+ info!("Logged in user: {username} with ID: {}.", user.id);
-impl BinaryServerCommand for LoginUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::LoginUser(login_user) => Ok(login_user),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+ let response = IdentityResponse { user_id: user.id };
+ sender.send_ok_response(&response.to_bytes()).await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/logout_user_handler.rs b/core/server/src/binary/handlers/users/logout_user_handler.rs
index 39689cfa6..d3f5111f7 100644
--- a/core/server/src/binary/handlers/users/logout_user_handler.rs
+++ b/core/server/src/binary/handlers/users/logout_user_handler.rs
@@ -16,56 +16,29 @@
* under the License.
*/
-use std::rc::Rc;
-
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
+use crate::binary::dispatch::HandlerResult;
use crate::binary::handlers::users::COMPONENT;
-use crate::binary::handlers::utils::receive_and_validate;
-
use crate::shard::IggyShard;
use crate::streaming::session::Session;
use err_trail::ErrContext;
-use iggy_common::logout_user::LogoutUser;
use iggy_common::{IggyError, SenderKind};
-use tracing::info;
-use tracing::{debug, instrument};
-
-impl ServerCommandHandler for LogoutUser {
- fn code(&self) -> u32 {
- iggy_common::LOGOUT_USER_CODE
- }
-
- #[instrument(skip_all, name = "trace_logout_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- info!("Logging out user with ID: {}...", session.get_user_id());
- shard.logout_user(session).error(|e: &IggyError| {
- format!("{COMPONENT} (error: {e}) - failed to logout user, session: {session}")
- })?;
- info!("Logged out user with ID: {}.", session.get_user_id());
- session.clear_user_id();
- sender.send_empty_ok_response().await?;
- Ok(HandlerResult::Finished)
- }
-}
+use std::rc::Rc;
+use tracing::{debug, info, instrument};
-impl BinaryServerCommand for LogoutUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::LogoutUser(logout_user) => Ok(logout_user),
- _ => Err(IggyError::InvalidCommand),
- }
- }
+#[instrument(skip_all, name = "trace_logout_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_logout_user(
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!("session: {session}, command: logout_user");
+ shard.ensure_authenticated(session)?;
+ info!("Logging out user with ID: {}...", session.get_user_id());
+ shard.logout_user(session).error(|e: &IggyError| {
+ format!("{COMPONENT} (error: {e}) - failed to logout user, session: {session}")
+ })?;
+ info!("Logged out user with ID: {}.", session.get_user_id());
+ session.clear_user_id();
+ sender.send_empty_ok_response().await?;
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/update_permissions_handler.rs b/core/server/src/binary/handlers/users/update_permissions_handler.rs
index 0680b5b5d..b3243f3e7 100644
--- a/core/server/src/binary/handlers/users/update_permissions_handler.rs
+++ b/core/server/src/binary/handlers/users/update_permissions_handler.rs
@@ -16,68 +16,62 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
+use crate::binary::dispatch::{
+ HandlerResult, wire_id_to_identifier, wire_permissions_to_permissions,
};
-use crate::binary::handlers::utils::receive_and_validate;
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::users::UpdatePermissionsRequest;
use iggy_common::update_permissions::UpdatePermissions;
use iggy_common::{IggyError, SenderKind};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for UpdatePermissions {
- fn code(&self) -> u32 {
- iggy_common::UPDATE_PERMISSIONS_CODE
- }
-
- #[instrument(skip_all, name = "trace_update_permissions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard
- .metadata
- .perm_update_permissions(session.get_user_id())?;
+#[instrument(skip_all, name = "trace_update_permissions", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_update_permissions(
+ req: UpdatePermissionsRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: update_permissions, user_id: {:?}",
+ req.user_id
+ );
+ shard.ensure_authenticated(session)?;
+ shard
+ .metadata
+ .perm_update_permissions(session.get_user_id())?;
- let target_user = shard.get_user(&self.user_id)?;
- if target_user.is_root() {
- return Err(IggyError::CannotChangePermissions(target_user.id));
- }
+ let user_id = wire_id_to_identifier(&req.user_id)?;
- let request = ShardRequest::control_plane(ShardRequestPayload::UpdatePermissionsRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let target_user = shard.get_user(&user_id)?;
+ if target_user.is_root() {
+ return Err(IggyError::CannotChangePermissions(target_user.id));
+ }
- match shard.send_to_control_plane(request).await? {
- ShardResponse::UpdatePermissionsResponse => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected UpdatePermissionsResponse"),
- }
+ let command = UpdatePermissions {
+ user_id,
+ permissions: req
+ .permissions
+ .as_ref()
+ .map(wire_permissions_to_permissions),
+ };
- Ok(HandlerResult::Finished)
- }
-}
+ let request = ShardRequest::control_plane(ShardRequestPayload::UpdatePermissionsRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
-impl BinaryServerCommand for UpdatePermissions {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::UpdatePermissions(update_permissions) => Ok(update_permissions),
- _ => Err(IggyError::InvalidCommand),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::UpdatePermissionsResponse => {
+ sender.send_empty_ok_response().await?;
}
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected UpdatePermissionsResponse"),
}
+
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/users/update_user_handler.rs b/core/server/src/binary/handlers/users/update_user_handler.rs
index d6ea576f4..397490319 100644
--- a/core/server/src/binary/handlers/users/update_user_handler.rs
+++ b/core/server/src/binary/handlers/users/update_user_handler.rs
@@ -16,61 +16,53 @@
* under the License.
*/
-use crate::binary::command::{
- BinaryServerCommand, HandlerResult, ServerCommand, ServerCommandHandler,
-};
-use crate::binary::handlers::utils::receive_and_validate;
+use crate::binary::dispatch::{HandlerResult, wire_id_to_identifier};
use crate::shard::IggyShard;
use crate::shard::transmission::frame::ShardResponse;
use crate::shard::transmission::message::{ShardRequest, ShardRequestPayload};
use crate::streaming::session::Session;
+use iggy_binary_protocol::requests::users::UpdateUserRequest;
use iggy_common::update_user::UpdateUser;
-use iggy_common::{IggyError, SenderKind};
+use iggy_common::{IggyError, SenderKind, UserStatus, Validatable};
use std::rc::Rc;
use tracing::{debug, instrument};
-impl ServerCommandHandler for UpdateUser {
- fn code(&self) -> u32 {
- iggy_common::UPDATE_USER_CODE
- }
-
- #[instrument(skip_all, name = "trace_update_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
- async fn handle(
- self,
- sender: &mut SenderKind,
- _length: u32,
- session: &Session,
- shard: &Rc<IggyShard>,
- ) -> Result<HandlerResult, IggyError> {
- debug!("session: {session}, command: {self}");
- shard.ensure_authenticated(session)?;
- shard.metadata.perm_update_user(session.get_user_id())?;
+#[instrument(skip_all, name = "trace_update_user", fields(iggy_user_id = session.get_user_id(), iggy_client_id = session.client_id))]
+pub async fn handle_update_user(
+ req: UpdateUserRequest,
+ sender: &mut SenderKind,
+ session: &Session,
+ shard: &Rc<IggyShard>,
+) -> Result<HandlerResult, IggyError> {
+ debug!(
+ "session: {session}, command: update_user, user_id: {:?}",
+ req.user_id
+ );
+ shard.ensure_authenticated(session)?;
+ shard.metadata.perm_update_user(session.get_user_id())?;
- let request = ShardRequest::control_plane(ShardRequestPayload::UpdateUserRequest {
- user_id: session.get_user_id(),
- command: self,
- });
+ let user_id = wire_id_to_identifier(&req.user_id)?;
+ let status = req.status.map(UserStatus::from_code).transpose()?;
- match shard.send_to_control_plane(request).await? {
- ShardResponse::UpdateUserResponse(_) => {
- sender.send_empty_ok_response().await?;
- }
- ShardResponse::ErrorResponse(err) => return Err(err),
- _ => unreachable!("Expected UpdateUserResponse"),
- }
+ let command = UpdateUser {
+ user_id,
+ username: req.username.map(|n| n.to_string()),
+ status,
+ };
+ command.validate()?;
- Ok(HandlerResult::Finished)
- }
-}
+ let request = ShardRequest::control_plane(ShardRequestPayload::UpdateUserRequest {
+ user_id: session.get_user_id(),
+ command,
+ });
-impl BinaryServerCommand for UpdateUser {
- async fn from_sender(sender: &mut SenderKind, code: u32, length: u32) -> Result<Self, IggyError>
- where
- Self: Sized,
- {
- match receive_and_validate(sender, code, length).await? {
- ServerCommand::UpdateUser(update_user) => Ok(update_user),
- _ => Err(IggyError::InvalidCommand),
+ match shard.send_to_control_plane(request).await? {
+ ShardResponse::UpdateUserResponse(_) => {
+ sender.send_empty_ok_response().await?;
}
+ ShardResponse::ErrorResponse(err) => return Err(err),
+ _ => unreachable!("Expected UpdateUserResponse"),
}
+
+ Ok(HandlerResult::Finished)
}
diff --git a/core/server/src/binary/handlers/utils.rs b/core/server/src/binary/handlers/utils.rs
deleted file mode 100644
index d07939333..000000000
--- a/core/server/src/binary/handlers/utils.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-
-use crate::binary::command::ServerCommand;
-use bytes::BytesMut;
-use iggy_common::{IggyError, SenderKind};
-
-pub async fn receive_and_validate(
- sender: &mut SenderKind,
- code: u32,
- length: u32,
-) -> Result<ServerCommand, IggyError> {
- let mut buffer = BytesMut::with_capacity(length as usize);
- unsafe {
- buffer.set_len(length as usize);
- }
- let buffer = if length == 0 {
- buffer
- } else {
- let (result, buffer) = sender.read(buffer).await;
- result?;
- buffer
- };
-
- let command = ServerCommand::from_code_and_payload(code, buffer.freeze())?;
- command.validate()?;
- Ok(command)
-}
diff --git a/core/server/src/binary/macros.rs b/core/server/src/binary/macros.rs
deleted file mode 100644
index 655705088..000000000
--- a/core/server/src/binary/macros.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-
-/// This macro does 4 expansions in one go:
-///
-/// 1) The `#[enum_dispatch(ServerCommandHandler)] pub enum ServerCommand`
-/// with all variants.
-/// 2) The `from_code_and_payload(code, payload)` function that matches each code.
-/// 3) The `to_bytes()` function that matches each variant.
-/// 4) The `validate()` function that matches each variant.
-#[macro_export]
-macro_rules! define_server_command_enum {
- (
- $(
- // Macro pattern:
- // variant name inner type numeric code display string show_payload?
- $variant:ident ( $ty:ty ), $code:ident, $display_str:expr, $show_payload:expr
- );* $(;)?
- ) => {
- #[enum_dispatch(ServerCommandHandler)]
- #[derive(Debug, EnumString, EnumIter)]
- pub enum ServerCommand {
- $(
- $variant($ty),
- )*
- }
-
- impl ServerCommand {
- /// Constructs a `ServerCommand` from its numeric code and payload.
- pub fn from_code_and_payload(code: u32, payload: Bytes) -> Result<Self, IggyError> {
- match code {
- $(
- $code => Ok(ServerCommand::$variant(
- <$ty>::from_bytes(payload)?
- )),
- )*
- _ => {
- error!("Invalid server command: {}", code);
- Err(IggyError::InvalidCommand)
- }
- }
- }
-
- /// Constructs a ServerCommand from its numeric code by reading from the provided async reader.
- pub async fn from_code_and_reader(
- code: u32,
- sender: &mut SenderKind,
- length: u32,
- ) -> Result<Self, IggyError> {
- match code {
- $(
- $code => Ok(ServerCommand::$variant(
- <$ty as BinaryServerCommand>::from_sender(sender, code, length).await?
- )),
- )*
- _ => Err(IggyError::InvalidCommand),
- }
- }
-
- /// Converts the command into raw bytes.
- pub fn to_bytes(&self) -> Bytes {
- match self {
- $(
- ServerCommand::$variant(payload) => as_bytes(payload),
- )*
- }
- }
-
- /// Validate the command by delegating to the inner command's implementation.
- pub fn validate(&self) -> Result<(), IggyError> {
- match self {
- $(
- ServerCommand::$variant(cmd) => <$ty as iggy_common::Validatable<iggy_common::IggyError>>::validate(cmd),
- )*
- }
- }
- }
-
-
- impl std::fmt::Display for ServerCommand {
- fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- $(
- // If $show_payload is true, we display the command name + payload;
- // otherwise, we just display the command name.
- ServerCommand::$variant(payload) => {
- if $show_payload {
- write!(formatter, "{}|{payload:?}", $display_str)
- } else {
- write!(formatter, "{}", $display_str)
- }
- },
- )*
- }
- }
- }
- };
-
-}
diff --git a/core/server/src/binary/mapper.rs b/core/server/src/binary/mapper.rs
deleted file mode 100644
index d439700d5..000000000
--- a/core/server/src/binary/mapper.rs
+++ /dev/null
@@ -1,431 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- */
-
-use crate::metadata::{ConsumerGroupMeta, StreamMeta, TopicMeta, UserMeta};
-use crate::shard::transmission::frame::{
- ConsumerGroupResponseData, StreamResponseData, TopicResponseData,
-};
-use crate::streaming::clients::client_manager::Client;
-use crate::streaming::users::user::User;
-use bytes::{BufMut, Bytes, BytesMut};
-use iggy_common::{
- BytesSerializable, ConsumerOffsetInfo, PersonalAccessToken, Stats, TransportProtocol, UserId,
-};
-
-pub fn map_stats(stats: &Stats) -> Bytes {
- let mut bytes = BytesMut::with_capacity(104);
- bytes.put_u32_le(stats.process_id);
- bytes.put_f32_le(stats.cpu_usage);
- bytes.put_f32_le(stats.total_cpu_usage);
- bytes.put_u64_le(stats.memory_usage.as_bytes_u64());
- bytes.put_u64_le(stats.total_memory.as_bytes_u64());
- bytes.put_u64_le(stats.available_memory.as_bytes_u64());
- bytes.put_u64_le(stats.run_time.into());
- bytes.put_u64_le(stats.start_time.into());
- bytes.put_u64_le(stats.read_bytes.as_bytes_u64());
- bytes.put_u64_le(stats.written_bytes.as_bytes_u64());
- bytes.put_u64_le(stats.messages_size_bytes.as_bytes_u64());
- bytes.put_u32_le(stats.streams_count);
- bytes.put_u32_le(stats.topics_count);
- bytes.put_u32_le(stats.partitions_count);
- bytes.put_u32_le(stats.segments_count);
- bytes.put_u64_le(stats.messages_count);
- bytes.put_u32_le(stats.clients_count);
- bytes.put_u32_le(stats.consumer_groups_count);
- bytes.put_u32_le(stats.hostname.len() as u32);
- bytes.put_slice(stats.hostname.as_bytes());
- bytes.put_u32_le(stats.os_name.len() as u32);
- bytes.put_slice(stats.os_name.as_bytes());
- bytes.put_u32_le(stats.os_version.len() as u32);
- bytes.put_slice(stats.os_version.as_bytes());
- bytes.put_u32_le(stats.kernel_version.len() as u32);
- bytes.put_slice(stats.kernel_version.as_bytes());
- bytes.put_u32_le(stats.iggy_server_version.len() as u32);
- bytes.put_slice(stats.iggy_server_version.as_bytes());
- if let Some(semver) = stats.iggy_server_semver {
- bytes.put_u32_le(semver);
- }
-
- bytes.put_u32_le(stats.cache_metrics.len() as u32);
- for (key, metrics) in &stats.cache_metrics {
- bytes.put_u32_le(key.stream_id);
- bytes.put_u32_le(key.topic_id);
- bytes.put_u32_le(key.partition_id);
-
- bytes.put_u64_le(metrics.hits);
- bytes.put_u64_le(metrics.misses);
- bytes.put_f32_le(metrics.hit_ratio);
- }
-
- bytes.put_u32_le(stats.threads_count);
- bytes.put_u64_le(stats.free_disk_space.as_bytes_u64());
- bytes.put_u64_le(stats.total_disk_space.as_bytes_u64());
-
- bytes.freeze()
-}
-
-pub fn map_consumer_offset(offset: &ConsumerOffsetInfo) -> Bytes {
- let mut bytes = BytesMut::with_capacity(20);
- bytes.put_u32_le(offset.partition_id);
- bytes.put_u64_le(offset.current_offset);
- bytes.put_u64_le(offset.stored_offset);
- bytes.freeze()
-}
-
-pub fn map_client(client: &Client) -> Bytes {
- let mut bytes = BytesMut::new();
- extend_client(client, &mut bytes);
- for consumer_group in &client.consumer_groups {
- bytes.put_u32_le(consumer_group.stream_id);
- bytes.put_u32_le(consumer_group.topic_id);
- bytes.put_u32_le(consumer_group.group_id);
- }
- bytes.freeze()
-}
-
-pub async fn map_clients(clients: Vec<Client>) -> Bytes {
- let mut bytes = BytesMut::new();
- for client in clients.iter() {
- extend_client(client, &mut bytes);
- }
- bytes.freeze()
-}
-
-pub fn map_user(user: &User) -> Bytes {
- let mut bytes = BytesMut::new();
- extend_user(user, &mut bytes);
- if let Some(permissions) = &user.permissions {
- bytes.put_u8(1);
- let permissions = permissions.to_bytes();
- #[allow(clippy::cast_possible_truncation)]
- bytes.put_u32_le(permissions.len() as u32);
- bytes.put_slice(&permissions);
- } else {
- bytes.put_u32_le(0);
- }
- bytes.freeze()
-}
-
-pub fn map_users(users: Vec<User>) -> Bytes {
- let mut bytes = BytesMut::new();
- for user in users.iter() {
- extend_user(user, &mut bytes);
- }
- bytes.freeze()
-}
-
-pub fn map_identity_info(user_id: UserId) -> Bytes {
- let mut bytes = BytesMut::with_capacity(4);
- bytes.put_u32_le(user_id);
- bytes.freeze()
-}
-
-pub fn map_raw_pat(token: &str) -> Bytes {
- let mut bytes = BytesMut::with_capacity(1 + token.len());
- bytes.put_u8(token.len() as u8);
- bytes.put_slice(token.as_bytes());
- bytes.freeze()
-}
-
-pub fn map_personal_access_tokens(personal_access_tokens: Vec<PersonalAccessToken>) -> Bytes {
- let mut bytes = BytesMut::new();
- for personal_access_token in personal_access_tokens.iter() {
- extend_pat(personal_access_token, &mut bytes);
- }
- bytes.freeze()
-}
-
-pub fn map_stream_from_response(data: &StreamResponseData) -> Bytes {
- let mut bytes = BytesMut::new();
- bytes.put_u32_le(data.id);
- bytes.put_u64_le(data.created_at.into());
- bytes.put_u32_le(0); // topics_count = 0 for new stream
- bytes.put_u64_le(0); // total_size = 0
- bytes.put_u64_le(0); // total_messages = 0
- bytes.put_u8(data.name.len() as u8);
- bytes.put_slice(data.name.as_bytes());
- bytes.freeze()
-}
-
-pub fn map_topic_from_response(data: &TopicResponseData) -> Bytes {
- let mut bytes = BytesMut::new();
- bytes.put_u32_le(data.id);
- bytes.put_u64_le(data.created_at.into());
- bytes.put_u32_le(data.partitions.len() as u32);
- bytes.put_u64_le(data.message_expiry.into());
- bytes.put_u8(data.compression_algorithm.as_code());
- bytes.put_u64_le(data.max_topic_size.into());
- bytes.put_u8(data.replication_factor);
- bytes.put_u64_le(0); // topic_size = 0
- bytes.put_u64_le(0); // topic_messages = 0
- bytes.put_u8(data.name.len() as u8);
- bytes.put_slice(data.name.as_bytes());
-
- for partition in &data.partitions {
- bytes.put_u32_le(partition.id as u32);
- bytes.put_u64_le(partition.created_at.into());
- bytes.put_u32_le(0); // segments_count = 0 for new partition
- bytes.put_u64_le(0); // current_offset = 0
- bytes.put_u64_le(0); // size_bytes = 0
- bytes.put_u64_le(0); // messages_count = 0
- }
-
- bytes.freeze()
-}
-
-pub fn map_consumer_group_from_response(data: &ConsumerGroupResponseData) -> Bytes {
- let mut bytes = BytesMut::new();
- bytes.put_u32_le(data.id);
- bytes.put_u32_le(data.partitions_count);
- bytes.put_u32_le(0); // members_count = 0 for new group
- bytes.put_u8(data.name.len() as u8);
- bytes.put_slice(data.name.as_bytes());
- bytes.freeze()
-}
-
-/// Map consumer group from SharedMetadata format.
-pub fn map_consumer_group_from_meta(meta: &ConsumerGroupMeta) -> Bytes {
- let mut bytes = BytesMut::new();
-
- // Header: id, partitions_count, members_count, name_len, name
- bytes.put_u32_le(meta.id as u32);
- bytes.put_u32_le(meta.partitions.len() as u32);
- bytes.put_u32_le(meta.members.len() as u32);
- bytes.put_u8(meta.name.len() as u8);
- bytes.put_slice(meta.name.as_bytes());
-
- // Members
- for (_, member) in meta.members.iter() {
- bytes.put_u32_le(member.id as u32);
- bytes.put_u32_le(member.partitions.len() as u32);
- for &partition_id in &member.partitions {
- bytes.put_u32_le(partition_id as u32);
- }
- }
-
- bytes.freeze()
-}
-
-fn extend_client(client: &Client, bytes: &mut BytesMut) {
- bytes.put_u32_le(client.session.client_id);
- bytes.put_u32_le(client.user_id.unwrap_or(u32::MAX));
- let transport: u8 = match client.transport {
- TransportProtocol::Tcp => 1,
- TransportProtocol::Quic => 2,
- TransportProtocol::Http => 3,
- TransportProtocol::WebSocket => 4,
- };
- bytes.put_u8(transport);
- let address = client.session.ip_address.to_string();
- bytes.put_u32_le(address.len() as u32);
- bytes.put_slice(address.as_bytes());
- bytes.put_u32_le(client.consumer_groups.len() as u32);
-}
-
-fn extend_user(user: &User, bytes: &mut BytesMut) {
- bytes.put_u32_le(user.id);
- bytes.put_u64_le(user.created_at.into());
- bytes.put_u8(user.status.as_code());
- bytes.put_u8(user.username.len() as u8);
- bytes.put_slice(user.username.as_bytes());
-}
-
-fn extend_pat(personal_access_token: &PersonalAccessToken, bytes: &mut BytesMut) {
- bytes.put_u8(personal_access_token.name.len() as u8);
- bytes.put_slice(personal_access_token.name.as_bytes());
- match &personal_access_token.expiry_at {
- Some(expiry_at) => {
- bytes.put_u64_le(expiry_at.as_micros());
- }
- None => {
- bytes.put_u64_le(0);
- }
- }
-}
-
-fn compute_stream_stats(stream: &StreamMeta) -> (u64, u64) {
- let mut size = 0u64;
- let mut messages = 0u64;
- for (_, topic) in stream.topics.iter() {
- for partition in topic.partitions.iter() {
- size += partition.stats.size_bytes_inconsistent();
- messages += partition.stats.messages_count_inconsistent();
- }
- }
- (size, messages)
-}
-
-fn compute_topic_stats(topic: &TopicMeta) -> (u64, u64) {
- let mut size = 0u64;
- let mut messages = 0u64;
- for partition in topic.partitions.iter() {
- size += partition.stats.size_bytes_inconsistent();
- messages += partition.stats.messages_count_inconsistent();
- }
- (size, messages)
-}
-
-fn extend_topic_header(topic: &TopicMeta, bytes: &mut BytesMut) {
- let (size, messages) = compute_topic_stats(topic);
- bytes.put_u32_le(topic.id as u32);
- bytes.put_u64_le(topic.created_at.into());
- bytes.put_u32_le(topic.partitions.len() as u32);
- bytes.put_u64_le(topic.message_expiry.into());
- bytes.put_u8(topic.compression_algorithm.as_code());
- bytes.put_u64_le(topic.max_topic_size.into());
- bytes.put_u8(topic.replication_factor);
- bytes.put_u64_le(size);
- bytes.put_u64_le(messages);
- bytes.put_u8(topic.name.len() as u8);
- bytes.put_slice(topic.name.as_bytes());
-}
-
-fn extend_topic_with_partitions(topic: &TopicMeta, bytes: &mut BytesMut) {
- extend_topic_header(topic, bytes);
-
- for (partition_id, partition) in topic.partitions.iter().enumerate() {
- let created_at = partition.created_at;
- let segments_count = partition.stats.segments_count_inconsistent();
- let offset = partition.stats.current_offset();
- let size_bytes = partition.stats.size_bytes_inconsistent();
- let messages_count = partition.stats.messages_count_inconsistent();
-
- bytes.put_u32_le(partition_id as u32);
- bytes.put_u64_le(created_at.into());
- bytes.put_u32_le(segments_count);
- bytes.put_u64_le(offset);
- bytes.put_u64_le(size_bytes);
- bytes.put_u64_le(messages_count);
- }
-}
-
-/// Map a single stream metadata to wire format (includes nested topics).
-pub fn map_stream(stream: &StreamMeta) -> Bytes {
- let mut bytes = BytesMut::new();
-
- let mut topic_ids: Vec<_> = stream.topics.iter().map(|(k, _)| k).collect();
- topic_ids.sort_unstable();
-
- let (total_size, total_messages) = compute_stream_stats(stream);
-
- bytes.put_u32_le(stream.id as u32);
- bytes.put_u64_le(stream.created_at.into());
- bytes.put_u32_le(topic_ids.len() as u32);
- bytes.put_u64_le(total_size);
- bytes.put_u64_le(total_messages);
- bytes.put_u8(stream.name.len() as u8);
- bytes.put_slice(stream.name.as_bytes());
-
- for &topic_id in &topic_ids {
- if let Some(topic) = stream.topics.get(topic_id) {
- extend_topic_header(topic, &mut bytes);
- }
- }
-
- bytes.freeze()
-}
-
-/// Map multiple streams to wire format (header only, no nested topics).
-pub fn map_streams(streams: &[StreamMeta]) -> Bytes {
- let mut bytes = BytesMut::new();
-
- let mut sorted: Vec<_> = streams.iter().collect();
- sorted.sort_by_key(|s| s.id);
-
- for stream in sorted {
- let (total_size, total_messages) = compute_stream_stats(stream);
- let topics_count = stream.topics.len();
-
- bytes.put_u32_le(stream.id as u32);
- bytes.put_u64_le(stream.created_at.into());
- bytes.put_u32_le(topics_count as u32);
- bytes.put_u64_le(total_size);
- bytes.put_u64_le(total_messages);
- bytes.put_u8(stream.name.len() as u8);
- bytes.put_slice(stream.name.as_bytes());
- }
-
- bytes.freeze()
-}
-
-/// Map a single topic metadata to wire format (includes partitions).
-pub fn map_topic(topic: &TopicMeta) -> Bytes {
- let mut bytes = BytesMut::new();
- extend_topic_with_partitions(topic, &mut bytes);
- bytes.freeze()
-}
-
-/// Map multiple topics to wire format (header only, no partitions).
-pub fn map_topics(topics: &[TopicMeta]) -> Bytes {
- let mut bytes = BytesMut::new();
-
- let mut sorted: Vec<_> = topics.iter().collect();
- sorted.sort_by_key(|t| t.id);
-
- for topic in sorted {
- extend_topic_header(topic, &mut bytes);
- }
-
- bytes.freeze()
-}
-
-/// Map multiple consumer groups to wire format.
-pub fn map_consumer_groups(groups: &[ConsumerGroupMeta]) -> Bytes {
- let mut bytes = BytesMut::new();
- for cg in groups {
- bytes.put_u32_le(cg.id as u32);
- bytes.put_u32_le(cg.partitions.len() as u32);
- bytes.put_u32_le(cg.members.len() as u32);
- bytes.put_u8(cg.name.len() as u8);
- bytes.put_slice(cg.name.as_bytes());
- }
- bytes.freeze()
-}
-
-fn extend_user_meta(user: &UserMeta, bytes: &mut BytesMut) {
- bytes.put_u32_le(user.id);
- bytes.put_u64_le(user.created_at.into());
- bytes.put_u8(user.status.as_code());
- bytes.put_u8(user.username.len() as u8);
- bytes.put_slice(user.username.as_bytes());
-}
-
-/// Map a single user metadata to wire format.
-pub fn map_user_meta(user: &UserMeta) -> Bytes {
- let mut bytes = BytesMut::new();
- extend_user_meta(user, &mut bytes);
- if let Some(permissions) = &user.permissions {
- bytes.put_u8(1);
- let permissions = permissions.to_bytes();
- #[allow(clippy::cast_possible_truncation)]
- bytes.put_u32_le(permissions.len() as u32);
- bytes.put_slice(&permissions);
- } else {
- bytes.put_u32_le(0);
- }
- bytes.freeze()
-}
-
-/// Map multiple user metadata to wire format.
-pub fn map_users_meta(users: &[UserMeta]) -> Bytes {
- let mut bytes = BytesMut::new();
- for user in users {
- extend_user_meta(user, &mut bytes);
- }
- bytes.freeze()
-}
diff --git a/core/server/src/binary/mod.rs b/core/server/src/binary/mod.rs
index ebca4eb7b..f46213b29 100644
--- a/core/server/src/binary/mod.rs
+++ b/core/server/src/binary/mod.rs
@@ -16,11 +16,7 @@
* under the License.
*/
-pub mod command;
+pub mod dispatch;
pub mod handlers;
-pub mod mapper;
-
-#[macro_use]
-mod macros;
pub const COMPONENT: &str = "BINARY";
diff --git a/core/server/src/http/partitions.rs b/core/server/src/http/partitions.rs
index 96570a711..fd2930731 100644
--- a/core/server/src/http/partitions.rs
+++ b/core/server/src/http/partitions.rs
@@ -59,7 +59,7 @@ async fn create_partitions(
});
match state.shard.send_to_control_plane(request).await? {
- ShardResponse::CreatePartitionsResponse(_) => Ok(StatusCode::CREATED),
+ ShardResponse::CreatePartitionsResponse => Ok(StatusCode::CREATED),
ShardResponse::ErrorResponse(err) => Err(err.into()),
_ => unreachable!("Expected CreatePartitionsResponse"),
}
@@ -87,7 +87,7 @@ async fn delete_partitions(
});
match state.shard.send_to_control_plane(request).await? {
- ShardResponse::DeletePartitionsResponse(_) => Ok(StatusCode::NO_CONTENT),
+ ShardResponse::DeletePartitionsResponse => Ok(StatusCode::NO_CONTENT),
ShardResponse::ErrorResponse(err) => Err(err.into()),
_ => unreachable!("Expected DeletePartitionsResponse"),
}
diff --git a/core/server/src/http/streams.rs b/core/server/src/http/streams.rs
index 92a623c66..80f216283 100644
--- a/core/server/src/http/streams.rs
+++ b/core/server/src/http/streams.rs
@@ -158,7 +158,7 @@ async fn delete_stream(
});
match state.shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteStreamResponse(_) => Ok(StatusCode::NO_CONTENT),
+ ShardResponse::DeleteStreamResponse => Ok(StatusCode::NO_CONTENT),
ShardResponse::ErrorResponse(err) => Err(err.into()),
_ => unreachable!("Expected DeleteStreamResponse"),
}
diff --git a/core/server/src/http/topics.rs b/core/server/src/http/topics.rs
index 7e170313b..857385bee 100644
--- a/core/server/src/http/topics.rs
+++ b/core/server/src/http/topics.rs
@@ -210,7 +210,7 @@ async fn delete_topic(
});
match state.shard.send_to_control_plane(request).await? {
- ShardResponse::DeleteTopicResponse(_) => Ok(StatusCode::NO_CONTENT),
+ ShardResponse::DeleteTopicResponse => Ok(StatusCode::NO_CONTENT),
ShardResponse::ErrorResponse(err) => Err(err.into()),
_ => unreachable!("Expected DeleteTopicResponse"),
}
diff --git a/core/server/src/quic/listener.rs b/core/server/src/quic/listener.rs
index b9d14a9a8..a39abae56 100644
--- a/core/server/src/quic/listener.rs
+++ b/core/server/src/quic/listener.rs
@@ -16,7 +16,7 @@
* under the License.
*/
-use crate::binary::command::{ServerCommand, ServerCommandHandler};
+use crate::binary::dispatch::{self, HandlerResult, MAX_CONTROL_FRAME_PAYLOAD};
use crate::server_error::ConnectionError;
use crate::shard::IggyShard;
use crate::shard::task_registry::ShutdownToken;
@@ -25,11 +25,11 @@ use anyhow::anyhow;
use compio::io::AsyncReadExt;
use compio::quic::{Connection, Endpoint, RecvStream, SendStream};
use futures::FutureExt;
+use iggy_binary_protocol::RequestFrame;
+use iggy_binary_protocol::codes::{SEND_MESSAGES_CODE, command_name};
use iggy_common::{GET_CLUSTER_METADATA_CODE, IggyError, SenderKind, TransportProtocol};
use std::rc::Rc;
-use tracing::{debug, error, info, trace};
-
-const INITIAL_BYTES_LENGTH: usize = 4;
+use tracing::{debug, error, info, trace, warn};
pub async fn start(
endpoint: Endpoint,
@@ -165,39 +165,47 @@ async fn handle_stream(
) -> anyhow::Result<()> {
let (send_stream, mut recv_stream) = stream;
- let length_buffer = [0u8; INITIAL_BYTES_LENGTH];
- let code_buffer = [0u8; INITIAL_BYTES_LENGTH];
-
- let compio::BufResult(result, length_buffer) = recv_stream.read_exact(length_buffer).await;
- result?;
- let compio::BufResult(result, code_buffer) = recv_stream.read_exact(code_buffer).await;
+ let header_buf = [0u8; RequestFrame::HEADER_SIZE];
+ let compio::BufResult(result, header_buf) = recv_stream.read_exact(header_buf).await;
result?;
- let length = u32::from_le_bytes(length_buffer);
- let code = u32::from_le_bytes(code_buffer);
+ let length = u32::from_le_bytes(header_buf[0..4].try_into().unwrap());
+ let code = u32::from_le_bytes(header_buf[4..8].try_into().unwrap());
+
+ let cmd_name = command_name(code).unwrap_or("unknown");
+ trace!("Received a QUIC request, length: {length}, code: {code} ({cmd_name})");
- trace!("Received a QUIC request, length: {length}, code: {code}");
+ let payload_length = RequestFrame::payload_length(length)
+ .map_err(|_| anyhow!("Invalid frame length: {length}"))?;
let mut sender = SenderKind::get_quic_sender(send_stream, recv_stream);
- let command = match ServerCommand::from_code_and_reader(code, &mut sender, length - 4).await {
- Ok(cmd) => cmd,
- Err(e) => {
- sender.send_error_response(e.clone()).await?;
- return Err(anyhow!("Failed to parse command: {e}"));
+ let result = if code == SEND_MESSAGES_CODE {
+ dispatch::dispatch_send_messages(&mut sender, payload_length, session, &shard).await
+ } else {
+ if payload_length > MAX_CONTROL_FRAME_PAYLOAD {
+ sender
+ .send_error_response(IggyError::InvalidCommand)
+ .await?;
+ return Ok(());
}
+ let payload = dispatch::read_payload(&mut sender, payload_length).await?;
+ let frame = RequestFrame::from_parts(code, &payload);
+ dispatch::dispatch(frame, &mut sender, session, &shard).await
};
- trace!("Received a QUIC command: {command}, payload size: {length}");
-
- match command.handle(&mut sender, length, session, &shard).await {
- Ok(_) => {
+ match result {
+ Ok(HandlerResult::Finished) => {
trace!(
"Command was handled successfully, session: {:?}. QUIC response was sent.",
session
);
Ok(())
}
+ Ok(HandlerResult::Migrated { to_shard }) => {
+ warn!("Unexpected migration on QUIC: to_shard {to_shard}, session: {session:?}");
+ Ok(())
+ }
Err(e) => {
// Special handling for GetClusterMetadata when clustering is disabled
if code == GET_CLUSTER_METADATA_CODE && matches!(e, IggyError::FeatureUnavailable) {
diff --git a/core/server/src/shard/execution.rs b/core/server/src/shard/execution.rs
index 1cdaea83d..5e425d7af 100644
--- a/core/server/src/shard/execution.rs
+++ b/core/server/src/shard/execution.rs
@@ -47,21 +47,6 @@ use iggy_common::{
update_stream::UpdateStream, update_topic::UpdateTopic, update_user::UpdateUser,
};
use secrecy::{ExposeSecret, SecretString};
-pub struct DeleteStreamResult {
- pub stream_id: usize,
-}
-
-pub struct DeleteTopicResult {
- pub topic_id: usize,
-}
-
-pub struct CreatePartitionsResult {
- pub partition_ids: Vec<usize>,
-}
-
-pub struct DeletePartitionsResult {
- pub partition_ids: Vec<usize>,
-}
pub async fn execute_create_stream(
shard: &IggyShard,
@@ -121,7 +106,7 @@ pub async fn execute_delete_stream(
shard: &IggyShard,
user_id: u32,
command: DeleteStream,
-) -> Result<DeleteStreamResult, IggyError> {
+) -> Result<(), IggyError> {
let stream = shard.resolve_stream(&command.stream_id)?;
shard.metadata.perm_delete_stream(user_id, stream.id())?;
@@ -136,7 +121,7 @@ pub async fn execute_delete_stream(
})
.collect();
- let stream_info = shard.delete_stream(stream).await?;
+ shard.delete_stream(stream).await?;
shard
.state
@@ -161,9 +146,7 @@ pub async fn execute_delete_stream(
}
}
- Ok(DeleteStreamResult {
- stream_id: stream_info.id,
- })
+ Ok(())
}
pub async fn execute_purge_stream(
@@ -293,7 +276,7 @@ pub async fn execute_delete_topic(
shard: &IggyShard,
user_id: u32,
command: DeleteTopic,
-) -> Result<DeleteTopicResult, IggyError> {
+) -> Result<(), IggyError> {
let topic = shard.resolve_topic(&command.stream_id, &command.topic_id)?;
shard
.metadata
@@ -304,7 +287,7 @@ pub async fn execute_delete_topic(
.metadata
.get_partition_ids(topic.stream_id, topic.topic_id);
- let topic_info = shard.delete_topic(topic).await?;
+ shard.delete_topic(topic).await?;
shard
.state
@@ -324,9 +307,7 @@ pub async fn execute_delete_topic(
tracing::warn!("Broadcast failed: {e}. Shards will sync on restart.");
}
- Ok(DeleteTopicResult {
- topic_id: topic_info.id,
- })
+ Ok(())
}
pub async fn execute_purge_topic(
@@ -364,7 +345,7 @@ pub async fn execute_create_partitions(
shard: &IggyShard,
user_id: u32,
command: CreatePartitions,
-) -> Result<CreatePartitionsResult, IggyError> {
+) -> Result<(), IggyError> {
let topic = shard.resolve_topic(&command.stream_id, &command.topic_id)?;
shard
.metadata
@@ -373,8 +354,6 @@ pub async fn execute_create_partitions(
let partition_infos = shard
.create_partitions(topic, command.partitions_count)
.await?;
- let partition_ids = partition_infos.iter().map(|p| p.id).collect::<Vec<_>>();
-
let total_partition_count = shard
.metadata
.partitions_count(topic.stream_id, topic.topic_id) as u32;
@@ -400,14 +379,14 @@ pub async fn execute_create_partitions(
tracing::warn!("Broadcast failed: {e}. Shards will sync on restart.");
}
- Ok(CreatePartitionsResult { partition_ids })
+ Ok(())
}
pub async fn execute_delete_partitions(
shard: &IggyShard,
user_id: u32,
command: DeletePartitions,
-) -> Result<DeletePartitionsResult, IggyError> {
+) -> Result<(), IggyError> {
let topic = shard.resolve_topic(&command.stream_id, &command.topic_id)?;
shard
.metadata
@@ -444,9 +423,7 @@ pub async fn execute_delete_partitions(
tracing::warn!("Broadcast failed: {e}. Shards will sync on restart.");
}
- Ok(DeletePartitionsResult {
- partition_ids: deleted_partition_ids,
- })
+ Ok(())
}
pub async fn execute_create_consumer_group(
diff --git a/core/server/src/shard/handlers.rs b/core/server/src/shard/handlers.rs
index 505eb50f1..5d4de789c 100644
--- a/core/server/src/shard/handlers.rs
+++ b/core/server/src/shard/handlers.rs
@@ -152,10 +152,8 @@ async fn handle_request(
"CreatePartitionsRequest should only be handled by shard0"
);
- let result = execution::execute_create_partitions(shard, user_id, command).await?;
- Ok(ShardResponse::CreatePartitionsResponse(
- result.partition_ids,
- ))
+ execution::execute_create_partitions(shard, user_id, command).await?;
+ Ok(ShardResponse::CreatePartitionsResponse)
}
ShardRequestPayload::DeletePartitionsRequest { user_id, command } => {
assert_eq!(
@@ -163,10 +161,8 @@ async fn handle_request(
"DeletePartitionsRequest should only be handled by shard0"
);
- let result = execution::execute_delete_partitions(shard, user_id, command).await?;
- Ok(ShardResponse::DeletePartitionsResponse(
- result.partition_ids,
- ))
+ execution::execute_delete_partitions(shard, user_id, command).await?;
+ Ok(ShardResponse::DeletePartitionsResponse)
}
ShardRequestPayload::CreateStreamRequest { user_id, command } => {
assert_eq!(
@@ -201,8 +197,8 @@ async fn handle_request(
"DeleteTopicRequest should only be handled by shard0"
);
- let result = execution::execute_delete_topic(shard, user_id, command).await?;
- Ok(ShardResponse::DeleteTopicResponse(result.topic_id))
+ execution::execute_delete_topic(shard, user_id, command).await?;
+ Ok(ShardResponse::DeleteTopicResponse)
}
ShardRequestPayload::CreateUserRequest { user_id, command } => {
assert_eq!(
@@ -240,8 +236,8 @@ async fn handle_request(
"DeleteStreamRequest should only be handled by shard0"
);
- let result = execution::execute_delete_stream(shard, user_id, command).await?;
- Ok(ShardResponse::DeleteStreamResponse(result.stream_id))
+ execution::execute_delete_stream(shard, user_id, command).await?;
+ Ok(ShardResponse::DeleteStreamResponse)
}
ShardRequestPayload::UpdatePermissionsRequest { user_id, command } => {
assert_eq!(
diff --git a/core/server/src/shard/transmission/frame.rs b/core/server/src/shard/transmission/frame.rs
index ab4351011..a89a34617 100644
--- a/core/server/src/shard/transmission/frame.rs
+++ b/core/server/src/shard/transmission/frame.rs
@@ -73,15 +73,15 @@ pub enum ShardResponse {
},
Event,
CreateStreamResponse(StreamResponseData),
- DeleteStreamResponse(usize),
+ DeleteStreamResponse,
CreateTopicResponse(TopicResponseData),
UpdateTopicResponse,
- DeleteTopicResponse(usize),
+ DeleteTopicResponse,
CreateUserResponse(User),
DeleteUserResponse(User),
GetStatsResponse(Stats),
- CreatePartitionsResponse(Vec<usize>),
- DeletePartitionsResponse(Vec<usize>),
+ CreatePartitionsResponse,
+ DeletePartitionsResponse,
UpdateStreamResponse,
SocketTransferResponse,
UpdatePermissionsResponse,
diff --git a/core/server/src/tcp/connection_handler.rs b/core/server/src/tcp/connection_handler.rs
index d3a58db57..d80835d46 100644
--- a/core/server/src/tcp/connection_handler.rs
+++ b/core/server/src/tcp/connection_handler.rs
@@ -16,22 +16,20 @@
* under the License.
*/
-use crate::binary::command;
-use crate::binary::command::ServerCommandHandler;
+use crate::binary::dispatch::{self, HandlerResult, MAX_CONTROL_FRAME_PAYLOAD};
use crate::server_error::ConnectionError;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
-use crate::tcp::connection_handler::command::ServerCommand;
use async_channel::Receiver;
use bytes::BytesMut;
use futures::FutureExt;
+use iggy_binary_protocol::RequestFrame;
+use iggy_binary_protocol::codes::{SEND_MESSAGES_CODE, command_name};
use iggy_common::{GET_CLUSTER_METADATA_CODE, IggyError, SenderKind};
use std::io::ErrorKind;
use std::rc::Rc;
use tracing::{debug, error, info};
-const INITIAL_BYTES_LENGTH: usize = 4;
-
/// Connection lifecycle action after command handling.
pub enum ConnectionAction {
/// Continue handling connection on current shard.
@@ -47,14 +45,13 @@ pub(crate) async fn handle_connection(
shard: &Rc<IggyShard>,
stop_receiver: Receiver<()>,
) -> Result<ConnectionAction, ConnectionError> {
- let mut length_buffer = BytesMut::with_capacity(INITIAL_BYTES_LENGTH);
- let mut code_buffer = BytesMut::with_capacity(INITIAL_BYTES_LENGTH);
+ let mut header_buffer = BytesMut::with_capacity(RequestFrame::HEADER_SIZE);
loop {
- let read_future = sender.read(length_buffer);
+ let read_future = sender.read(header_buffer);
// TODO(hubcio): this futures::select! call is translated to epoll_wait syscall for every
// message, which adds around 100 us median latency. We could instead just call sender.shutdown()
// if some atomic bool is set, since this is all happenng within single thread.
- let (_, mut initial_buffer) = futures::select! {
+ let (_, mut header_buf) = futures::select! {
_ = stop_receiver.recv().fuse() => {
info!("Connection stop signal received for session: {}", session);
let _ = sender.send_error_response(IggyError::Disconnected).await;
@@ -62,9 +59,9 @@ pub(crate) async fn handle_connection(
}
result = read_future.fuse() => {
match result {
- (Ok(read_length), initial_buffer) => (read_length, initial_buffer),
- (Err(error), initial_buffer) => {
- length_buffer = initial_buffer;
+ (Ok(_), buf) => (Ok::<(), IggyError>(()), buf),
+ (Err(error), buf) => {
+ header_buffer = buf;
if error.as_code() == IggyError::ConnectionClosed.as_code() {
return Err(ConnectionError::from(error));
} else {
@@ -77,38 +74,55 @@ pub(crate) async fn handle_connection(
}
};
- let length =
- u32::from_le_bytes(initial_buffer[0..INITIAL_BYTES_LENGTH].try_into().unwrap());
- let (res, mut code_buffer_out) = sender.read(code_buffer).await;
- res?;
- let code: u32 =
- u32::from_le_bytes(code_buffer_out[0..INITIAL_BYTES_LENGTH].try_into().unwrap());
- initial_buffer.clear();
- code_buffer_out.clear();
- length_buffer = initial_buffer;
- code_buffer = code_buffer_out;
- debug!("Received a TCP request, length: {length}, code: {code}");
- let command = ServerCommand::from_code_and_reader(code, sender, length - 4).await?;
- debug!("Received a TCP command: {command}, payload size: {length}");
- let cmd_code = command.code();
- match command.handle(sender, length, session, shard).await {
+ let length = u32::from_le_bytes(header_buf[0..4].try_into().unwrap());
+ let code = u32::from_le_bytes(header_buf[4..8].try_into().unwrap());
+ header_buf.clear();
+ header_buffer = header_buf;
+
+ let cmd_name = command_name(code).unwrap_or("unknown");
+ debug!("Received a TCP request, length: {length}, code: {code} ({cmd_name})");
+
+ let payload_length = match RequestFrame::payload_length(length) {
+ Ok(len) => len,
+ Err(_) => {
+ sender
+ .send_error_response(IggyError::InvalidCommand)
+ .await?;
+ continue;
+ }
+ };
+
+ let result = if code == SEND_MESSAGES_CODE {
+ dispatch::dispatch_send_messages(sender, payload_length, session, shard).await
+ } else {
+ if payload_length > MAX_CONTROL_FRAME_PAYLOAD {
+ sender
+ .send_error_response(IggyError::InvalidCommand)
+ .await?;
+ continue;
+ }
+ let payload = dispatch::read_payload(sender, payload_length).await?;
+ let frame = RequestFrame::from_parts(code, &payload);
+ dispatch::dispatch(frame, sender, session, shard).await
+ };
+
+ match result {
Ok(handler_result) => match handler_result {
- command::HandlerResult::Finished => {
+ HandlerResult::Finished => {
debug!(
- "Command {cmd_code} was handled successfully, session: {session}. TCP response was sent."
+ "Command {code} ({cmd_name}) was handled successfully, session: {session}. TCP response was sent."
);
}
- command::HandlerResult::Migrated { to_shard } => {
+ HandlerResult::Migrated { to_shard } => {
info!(
- "Command {cmd_code} was transfer to shard {to_shard}, session: {session}. TCP response was sent."
+ "Command {code} ({cmd_name}) was transferred to shard {to_shard}, session: {session}."
);
return Ok(ConnectionAction::Migrated { to_shard });
}
},
Err(error) => {
- // Special handling for GetClusterMetadata when clustering is disabled
- if cmd_code == GET_CLUSTER_METADATA_CODE
+ if code == GET_CLUSTER_METADATA_CODE
&& matches!(error, IggyError::FeatureUnavailable)
{
debug!(
@@ -118,7 +132,7 @@ pub(crate) async fn handle_connection(
debug!("TCP error response was sent to: {session}.");
} else {
error!(
- "Command with code {cmd_code} was not handled successfully, session: {session}, error: {error}."
+ "Command with code {code} ({cmd_name}) was not handled successfully, session: {session}, error: {error}."
);
if matches!(error, IggyError::ClientNotFound(_) | IggyError::StaleClient) {
diff --git a/core/server/src/websocket/connection_handler.rs b/core/server/src/websocket/connection_handler.rs
index 2ee97196f..e68bcc902 100644
--- a/core/server/src/websocket/connection_handler.rs
+++ b/core/server/src/websocket/connection_handler.rs
@@ -16,34 +16,31 @@
* under the License.
*/
-use crate::binary::command;
-use crate::binary::command::ServerCommandHandler;
+use crate::binary::dispatch::{self, HandlerResult, MAX_CONTROL_FRAME_PAYLOAD};
use crate::server_error::ConnectionError;
use crate::shard::IggyShard;
use crate::streaming::session::Session;
-use crate::websocket::connection_handler::command::ServerCommand;
use async_channel::Receiver;
use bytes::BytesMut;
use futures::FutureExt;
+use iggy_binary_protocol::RequestFrame;
+use iggy_binary_protocol::codes::{SEND_MESSAGES_CODE, command_name};
use iggy_common::{IggyError, SenderKind};
use std::io::ErrorKind;
use std::rc::Rc;
use tracing::{debug, error, info, warn};
-const INITIAL_BYTES_LENGTH: usize = 4;
-
pub(crate) async fn handle_connection(
session: &Session,
sender: &mut SenderKind,
shard: &Rc<IggyShard>,
stop_receiver: Receiver<()>,
) -> Result<(), ConnectionError> {
- let mut length_buffer = BytesMut::with_capacity(INITIAL_BYTES_LENGTH);
- let mut code_buffer = BytesMut::with_capacity(INITIAL_BYTES_LENGTH);
+ let mut header_buffer = BytesMut::with_capacity(RequestFrame::HEADER_SIZE);
loop {
- let read_future = sender.read(length_buffer);
- let (_, mut initial_buffer) = futures::select! {
+ let read_future = sender.read(header_buffer);
+ let (_, mut header_buf) = futures::select! {
_ = stop_receiver.recv().fuse() => {
info!("Connection stop signal received for session: {}", session);
let _ = sender.send_error_response(IggyError::Disconnected).await;
@@ -51,9 +48,9 @@ pub(crate) async fn handle_connection(
}
result = read_future.fuse() => {
match result {
- (Ok(read_length), initial_buffer) => (read_length, initial_buffer),
- (Err(error), initial_buffer) => {
- length_buffer = initial_buffer;
+ (Ok(_), buf) => (Ok::<(), IggyError>(()), buf),
+ (Err(error), buf) => {
+ header_buffer = buf;
if error.as_code() == IggyError::ConnectionClosed.as_code() {
return Err(ConnectionError::from(error));
} else {
@@ -66,63 +63,80 @@ pub(crate) async fn handle_connection(
}
};
- let length =
- u32::from_le_bytes(initial_buffer[0..INITIAL_BYTES_LENGTH].try_into().unwrap());
- let (res, mut code_buffer_out) = sender.read(code_buffer).await;
- res?;
- let code: u32 =
- u32::from_le_bytes(code_buffer_out[0..INITIAL_BYTES_LENGTH].try_into().unwrap());
- initial_buffer.clear();
- code_buffer_out.clear();
- length_buffer = initial_buffer;
- code_buffer = code_buffer_out;
+ let length = u32::from_le_bytes(header_buf[0..4].try_into().unwrap());
+ let code = u32::from_le_bytes(header_buf[4..8].try_into().unwrap());
+ header_buf.clear();
+ header_buffer = header_buf;
+
+ let cmd_name = command_name(code).unwrap_or("unknown");
+ debug!("Received a WebSocket request, length: {length}, code: {code} ({cmd_name})");
+
+ let payload_length = match RequestFrame::payload_length(length) {
+ Ok(len) => len,
+ Err(_) => {
+ sender
+ .send_error_response(IggyError::InvalidCommand)
+ .await?;
+ continue;
+ }
+ };
- debug!("Received a WebSocket request, length: {length}, code: {code}");
- let command = ServerCommand::from_code_and_reader(code, sender, length - 4).await?;
- debug!("Received a WebSocket command: {command}, payload size: {length}");
+ let result = if code == SEND_MESSAGES_CODE {
+ dispatch::dispatch_send_messages(sender, payload_length, session, shard).await
+ } else {
+ if payload_length > MAX_CONTROL_FRAME_PAYLOAD {
+ sender
+ .send_error_response(IggyError::InvalidCommand)
+ .await?;
+ continue;
+ }
+ let payload = dispatch::read_payload(sender, payload_length).await?;
+ let frame = RequestFrame::from_parts(code, &payload);
+ dispatch::dispatch(frame, sender, session, shard).await
+ };
- match command.handle(sender, length, session, shard).await {
- Ok(_) => {
+ match result {
+ Ok(HandlerResult::Finished) => {
debug!(
- "Command was handled successfully, session: {session}. WebSocket response was sent."
+ "Command {code} ({cmd_name}) was handled successfully, session: {session}. WebSocket response was sent."
);
}
- Err(error) => {
- match error {
- IggyError::TcpError | IggyError::ConnectionClosed | IggyError::Disconnected => {
- warn!(
- "Client {} closed connection during request processing",
- session.client_id
- );
- return Err(ConnectionError::from(IggyError::ConnectionClosed));
- }
- IggyError::ClientNotFound(_) | IggyError::StaleClient => {
- error!("Command failed for session: {session}, error: {error}.");
- sender.send_error_response(error.clone()).await?;
- return Err(ConnectionError::from(error));
- }
- _ => {
- error!("Command failed for session: {session}, error: {error}.");
- // try to send error response, but if it fails due to broken pipe, that's ok?
- match sender.send_error_response(error).await {
- Ok(_) => {
- debug!("WebSocket error response was sent to: {session}.");
- }
- Err(IggyError::ConnectionClosed) => {
- warn!(
- "Could not send error response to {} - client already disconnected",
- session.client_id
- );
- return Err(ConnectionError::from(IggyError::ConnectionClosed));
- }
- Err(send_err) => {
- error!("Failed to send error response: {send_err}");
- return Err(ConnectionError::from(send_err));
- }
+ Ok(HandlerResult::Migrated { to_shard }) => {
+ warn!("Unexpected migration on WebSocket: to_shard {to_shard}, session: {session}");
+ }
+ Err(error) => match error {
+ IggyError::TcpError | IggyError::ConnectionClosed | IggyError::Disconnected => {
+ warn!(
+ "Client {} closed connection during request processing",
+ session.client_id
+ );
+ return Err(ConnectionError::from(IggyError::ConnectionClosed));
+ }
+ IggyError::ClientNotFound(_) | IggyError::StaleClient => {
+ error!("Command failed for session: {session}, error: {error}.");
+ sender.send_error_response(error.clone()).await?;
+ return Err(ConnectionError::from(error));
+ }
+ _ => {
+ error!("Command failed for session: {session}, error: {error}.");
+ match sender.send_error_response(error).await {
+ Ok(_) => {
+ debug!("WebSocket error response was sent to: {session}.");
+ }
+ Err(IggyError::ConnectionClosed) => {
+ warn!(
+ "Could not send error response to {} - client already disconnected",
+ session.client_id
+ );
+ return Err(ConnectionError::from(IggyError::ConnectionClosed));
+ }
+ Err(send_err) => {
+ error!("Failed to send error response: {send_err}");
+ return Err(ConnectionError::from(send_err));
}
}
}
- }
+ },
}
}
}