You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by pi...@apache.org on 2026/04/02 07:41:16 UTC

(iggy) branch master updated: refactor(server,sdk): replace ServerCommand/BytesSerializable with wire types (#3041)

This is an automated email from the ASF dual-hosted git repository.

piotr 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 cea344cd8 refactor(server,sdk): replace ServerCommand/BytesSerializable with wire types (#3041)
cea344cd8 is described below

commit cea344cd8837f75e32f24f2ad3aaa315b60b12f5
Author: Hubert Gruszecki <h....@gmail.com>
AuthorDate: Thu Apr 2 09:41:12 2026 +0200

    refactor(server,sdk): replace ServerCommand/BytesSerializable with wire types (#3041)
    
    The binary protocol layer had three redundant serialization
    paths: ServerCommand enum + binary_mapper (hand-rolled
    command dispatch), BytesSerializable trait (domain type
    serialization), and WireEncode/WireDecode (wire protocol
    types). All three did the same job with different APIs.
    
    This PR collapses them into one: wire types flow from
    transport through shard dispatch to handlers. The
    ServerCommand enum, binary_mapper, 45+ command structs,
    and BytesSerializable trait are deleted entirely.
    
    Changes:
    - Route wire types through ShardRequestPayload and SDK
      binary impls, eliminating ServerCommand dispatch
    - Delete 45+ command structs from iggy_common/commands/,
      replace with thin HTTP DTOs in iggy_common/http/
    - Switch WAL (EntryCommand/StateEntry) to WireEncode/
      WireDecode, delete BytesSerializable from WAL path
    - Remove all 21 BytesSerializable impls and delete the
      trait itself
    - SDK poll/send now use PollMessagesRequest and
      SendMessagesEncoder (zero-copy) instead of
      intermediate BytesSerializable allocations
    - Add wire conversion functions and response-to-domain
      conversions in wire_conversions.rs
---
 Cargo.lock                                         |  217 ++--
 DEPENDENCIES.md                                    |   86 +-
 core/binary_protocol/src/lib.rs                    |    7 +
 core/binary_protocol/src/primitives/mod.rs         |    1 +
 .../binary_protocol/src/primitives/user_headers.rs |  544 +++++++++++
 core/cli/Cargo.toml                                |    2 +-
 core/cli/src/args/permissions/mod.rs               |    8 +-
 core/cli/src/args/permissions/stream.rs            |   10 +-
 core/cli/src/commands/binary_client/get_client.rs  |   20 +-
 core/cli/src/commands/binary_client/get_clients.rs |    8 +-
 .../binary_consumer_groups/get_consumer_group.rs   |   23 +-
 .../binary_consumer_groups/get_consumer_groups.rs  |   19 +-
 .../binary_consumer_offsets/get_consumer_offset.rs |   53 +-
 .../binary_consumer_offsets/set_consumer_offset.rs |   43 +-
 .../src/commands/binary_message/poll_messages.rs   |   13 +-
 .../src/commands/binary_message/send_messages.rs   |    8 +-
 .../get_personal_access_tokens.rs                  |    7 +-
 core/cli/src/commands/binary_streams/get_stream.rs |   20 +-
 .../cli/src/commands/binary_streams/get_streams.rs |    8 +-
 core/cli/src/commands/binary_system/me.rs          |   14 +-
 core/cli/src/commands/binary_system/ping.rs        |    7 +-
 core/cli/src/commands/binary_system/snapshot.rs    |   36 +-
 core/cli/src/commands/binary_system/stats.rs       |    8 +-
 core/cli/src/commands/binary_topics/get_topic.rs   |   18 +-
 core/cli/src/commands/binary_topics/get_topics.rs  |   19 +-
 core/cli/src/commands/binary_users/get_user.rs     |   15 +-
 core/cli/src/commands/binary_users/get_users.rs    |    8 +-
 core/common/Cargo.toml                             |    1 -
 .../consumer_groups/create_consumer_group.rs       |  189 ----
 .../consumer_groups/delete_consumer_group.rs       |  147 ---
 .../commands/consumer_groups/get_consumer_group.rs |  147 ---
 .../consumer_groups/get_consumer_groups.rs         |  126 ---
 .../consumer_groups/join_consumer_group.rs         |  147 ---
 .../consumer_groups/leave_consumer_group.rs        |  147 ---
 .../consumer_offsets/delete_consumer_offset.rs     |  220 -----
 .../consumer_offsets/get_consumer_offset.rs        |  225 -----
 .../consumer_offsets/store_consumer_offset.rs      |  240 -----
 .../src/commands/messages/flush_unsaved_buffer.rs  |  132 ---
 core/common/src/commands/messages/poll_messages.rs |  341 -------
 .../src/commands/partitions/create_partitions.rs   |  167 ----
 .../src/commands/partitions/delete_partitions.rs   |  167 ----
 .../create_personal_access_token.rs                |  157 ---
 .../delete_personal_access_token.rs                |  156 ---
 .../get_personal_access_tokens.rs                  |   86 --
 .../login_with_personal_access_token.rs            |  173 ----
 .../src/commands/segments/delete_segments.rs       |  202 ----
 core/common/src/commands/streams/create_stream.rs  |  153 ---
 core/common/src/commands/streams/delete_stream.rs  |   99 --
 core/common/src/commands/streams/get_stream.rs     |   99 --
 core/common/src/commands/streams/get_streams.rs    |   86 --
 core/common/src/commands/streams/purge_stream.rs   |  102 --
 core/common/src/commands/streams/update_stream.rs  |  151 ---
 core/common/src/commands/system/get_client.rs      |  113 ---
 core/common/src/commands/system/get_clients.rs     |   86 --
 .../src/commands/system/get_cluster_metadata.rs    |   59 --
 core/common/src/commands/system/get_me.rs          |   86 --
 core/common/src/commands/system/get_snapshot.rs    |  168 ----
 core/common/src/commands/system/get_stats.rs       |   86 --
 core/common/src/commands/system/ping.rs            |   87 --
 core/common/src/commands/topics/create_topic.rs    |  301 ------
 core/common/src/commands/topics/delete_topic.rs    |  126 ---
 core/common/src/commands/topics/get_topic.rs       |  126 ---
 core/common/src/commands/topics/get_topics.rs      |   99 --
 core/common/src/commands/topics/purge_topic.rs     |  124 ---
 core/common/src/commands/topics/update_topic.rs    |  302 ------
 core/common/src/commands/users/change_password.rs  |  246 -----
 core/common/src/commands/users/create_user.rs      |  347 -------
 core/common/src/commands/users/delete_user.rs      |   99 --
 core/common/src/commands/users/get_user.rs         |   99 --
 core/common/src/commands/users/get_users.rs        |   88 --
 core/common/src/commands/users/login_user.rs       |  399 --------
 core/common/src/commands/users/logout_user.rs      |   87 --
 .../src/commands/users/update_permissions.rs       |  189 ----
 core/common/src/commands/users/update_user.rs      |  231 -----
 core/common/src/error/iggy_error.rs                |    2 +
 .../http/consumer_groups/create_consumer_group.rs  |   60 ++
 .../http/consumer_groups/delete_consumer_group.rs  |   46 +
 .../src/{commands => http}/consumer_groups/mod.rs  |    6 +-
 .../consumer_offsets/delete_consumer_offset.rs}    |   13 +-
 .../consumer_offsets/get_consumer_offset.rs}       |   16 +-
 .../src/{commands => http}/consumer_offsets/mod.rs |    0
 .../consumer_offsets/store_consumer_offset.rs}     |   16 +-
 .../messages/flush_unsaved_buffer.rs}              |    9 +-
 core/common/src/{commands => http}/messages/mod.rs |    0
 core/common/src/http/messages/poll_messages.rs     |   89 ++
 .../{commands => http}/messages/send_messages.rs   |  131 +--
 core/common/src/{commands => http}/mod.rs          |    0
 .../src/http/partitions/create_partitions.rs       |   60 ++
 .../src/http/partitions/delete_partitions.rs       |   60 ++
 .../src/{commands => http}/partitions/mod.rs       |    2 +-
 .../create_personal_access_token.rs                |   57 ++
 .../delete_personal_access_token.rs                |   52 +
 .../login_with_personal_access_token.rs}           |   14 +-
 .../personal_access_tokens/mod.rs                  |    1 -
 core/common/src/http/segments/delete_segments.rs   |   59 ++
 core/common/src/{commands => http}/segments/mod.rs |    0
 core/common/src/http/streams/create_stream.rs      |   49 +
 .../cluster.rs => http/streams/delete_stream.rs}   |   27 +-
 core/common/src/{commands => http}/streams/mod.rs  |    4 +-
 .../cluster.rs => http/streams/purge_stream.rs}    |   27 +-
 core/common/src/http/streams/update_stream.rs      |   55 ++
 .../mod.rs => http/system/get_snapshot.rs}         |   15 +-
 core/common/src/{commands => http}/system/mod.rs   |    6 -
 core/common/src/http/topics/create_topic.rs        |   89 ++
 .../topics/delete_topic.rs}                        |   39 +-
 core/common/src/{commands => http}/topics/mod.rs   |    5 +-
 .../segments.rs => http/topics/purge_topic.rs}     |   41 +-
 core/common/src/http/topics/update_topic.rs        |   86 ++
 core/common/src/http/users/change_password.rs      |   74 ++
 core/common/src/http/users/create_user.rs          |   76 ++
 .../src/{commands => http}/users/defaults.rs       |    0
 .../cluster.rs => http/users/delete_user.rs}       |   27 +-
 core/common/src/http/users/login_user.rs           |   55 ++
 core/common/src/{commands => http}/users/mod.rs    |    3 -
 .../users/update_permissions.rs}                   |   41 +-
 core/common/src/http/users/update_user.rs          |   55 ++
 core/common/src/lib.rs                             |   29 +-
 core/common/src/traits/binary_impls/cluster.rs     |   19 +-
 .../src/traits/binary_impls/consumer_groups.rs     |  138 ++-
 .../src/traits/binary_impls/consumer_offsets.rs    |   82 +-
 core/common/src/traits/binary_impls/messages.rs    |   82 +-
 core/common/src/traits/binary_impls/mod.rs         |   11 +
 core/common/src/traits/binary_impls/partitions.rs  |   43 +-
 .../traits/binary_impls/personal_access_tokens.rs  |   71 +-
 core/common/src/traits/binary_impls/segments.rs    |   26 +-
 core/common/src/traits/binary_impls/streams.rs     |   86 +-
 core/common/src/traits/binary_impls/system.rs      |   75 +-
 core/common/src/traits/binary_impls/topics.rs      |  140 ++-
 core/common/src/traits/binary_impls/users.rs       |  151 ++-
 core/common/src/traits/binary_mapper.rs            | 1033 --------------------
 core/common/src/traits/binary_transport.rs         |    4 +-
 core/common/src/traits/mod.rs                      |    2 -
 core/common/src/types/cluster/metadata.rs          |   95 +-
 core/common/src/types/cluster/node.rs              |   98 +-
 core/common/src/types/cluster/role.rs              |   27 +-
 core/common/src/types/cluster/status.rs            |   27 +-
 .../src/types/cluster/transport_endpoints.rs       |   69 --
 core/common/src/types/command/mod.rs               |  173 ----
 core/common/src/types/configuration/transport.rs   |   23 +-
 core/common/src/types/consumer/consumer_kind.rs    |   27 -
 core/common/src/types/identifier/mod.rs            |   76 +-
 core/common/src/types/message/iggy_message.rs      |  201 ++--
 core/common/src/types/message/message_header.rs    |    8 +-
 .../src/types/message/message_header_view.rs       |   25 +-
 core/common/src/types/message/message_view.rs      |   44 +-
 core/common/src/types/message/messages_batch.rs    |   24 +-
 .../common/src/types/message/messages_batch_mut.rs |    6 +-
 core/common/src/types/message/mod.rs               |    5 +-
 core/common/src/types/message/partitioning.rs      |   71 +-
 core/common/src/types/message/polled_messages.rs   |   12 +-
 core/common/src/types/message/polling_strategy.rs  |   27 -
 core/common/src/types/message/user_headers.rs      |  176 +---
 core/common/src/types/mod.rs                       |    1 -
 .../src/types/permissions/permissions_global.rs    |  264 +----
 core/common/src/utils/crypto.rs                    |    4 +-
 core/common/src/wire_conversions.rs                |  785 +++++++++++++++
 core/connectors/runtime/src/sink.rs                |    2 +-
 core/connectors/runtime/src/source.rs              |    4 +-
 core/connectors/sdk/src/lib.rs                     |   10 +-
 core/connectors/sinks/http_sink/src/lib.rs         |    3 +-
 core/integration/Cargo.toml                        |    1 -
 .../tests/cli/message/test_message_poll_command.rs |    4 +-
 .../message/test_message_poll_to_file_command.rs   |    8 +-
 .../cli/message/test_message_reply_via_file.rs     |    4 +-
 .../tests/cli/message/test_message_send_command.rs |    8 +-
 .../message/test_message_send_from_file_command.rs |    8 +-
 .../tests/cli/user/test_user_create_command.rs     |   12 +-
 .../tests/cli/user/test_user_get_command.rs        |    6 +-
 .../cli/user/test_user_permissions_command.rs      |   12 +-
 .../server/scenarios/create_message_payload.rs     |    6 +-
 .../tests/server/scenarios/encryption_scenario.rs  |    8 +-
 .../server/scenarios/message_headers_scenario.rs   |    6 +-
 .../server/scenarios/message_size_scenario.rs      |    6 +-
 .../tests/server/scenarios/offset_scenario.rs      |    8 +-
 .../tests/server/scenarios/permissions_scenario.rs |   36 +-
 .../tests/server/scenarios/timestamp_scenario.rs   |    8 +-
 core/integration/tests/state/file.rs               |   35 +-
 core/integration/tests/state/system.rs             |  154 +--
 core/metadata/Cargo.toml                           |    1 -
 core/metadata/src/stm/consumer_group.rs            |  115 +--
 core/metadata/src/stm/mod.rs                       |   10 +-
 core/metadata/src/stm/stream.rs                    |  107 +-
 core/metadata/src/stm/user.rs                      |   84 +-
 core/sdk/src/http/consumer_offsets.rs              |    4 -
 core/sdk/src/http/messages.rs                      |    7 +-
 core/sdk/src/prelude.rs                            |   18 +-
 core/sdk/src/quic/quic_client.rs                   |    8 +-
 core/sdk/src/tcp/tcp_client.rs                     |   12 +-
 core/sdk/src/websocket/websocket_client.rs         |    8 +-
 core/server/src/binary/dispatch.rs                 |  125 +--
 .../create_consumer_group_handler.rs               |   20 +-
 .../delete_consumer_group_handler.rs               |   17 +-
 .../consumer_groups/join_consumer_group_handler.rs |   17 +-
 .../leave_consumer_group_handler.rs                |   17 +-
 .../partitions/create_partitions_handler.rs        |   22 +-
 .../partitions/delete_partitions_handler.rs        |   21 +-
 .../create_personal_access_token_handler.rs        |   19 +-
 .../delete_personal_access_token_handler.rs        |   10 +-
 .../login_with_personal_access_token_handler.rs    |    9 +-
 .../handlers/streams/create_stream_handler.rs      |   10 +-
 .../handlers/streams/delete_stream_handler.rs      |    8 +-
 .../handlers/streams/purge_stream_handler.rs       |    8 +-
 .../handlers/streams/update_stream_handler.rs      |   14 +-
 .../binary/handlers/topics/create_topic_handler.rs |   31 +-
 .../binary/handlers/topics/delete_topic_handler.rs |   15 +-
 .../binary/handlers/topics/purge_topic_handler.rs  |   15 +-
 .../binary/handlers/topics/update_topic_handler.rs |   29 +-
 .../handlers/users/change_password_handler.rs      |   28 +-
 .../binary/handlers/users/create_user_handler.rs   |   34 +-
 .../binary/handlers/users/delete_user_handler.rs   |    9 +-
 .../src/binary/handlers/users/get_user_handler.rs  |    5 +-
 .../binary/handlers/users/login_user_handler.rs    |   25 +-
 .../handlers/users/update_permissions_handler.rs   |   22 +-
 .../binary/handlers/users/update_user_handler.rs   |   23 +-
 core/server/src/http/consumer_groups.rs            |   27 +-
 core/server/src/http/consumer_offsets.rs           |   38 +-
 core/server/src/http/partitions.rs                 |   23 +-
 core/server/src/http/personal_access_tokens.rs     |   20 +-
 core/server/src/http/segments.rs                   |   10 +-
 core/server/src/http/streams.rs                    |   29 +-
 core/server/src/http/system.rs                     |   10 +-
 core/server/src/http/topics.rs                     |   42 +-
 core/server/src/http/users.rs                      |   50 +-
 core/server/src/quic/listener.rs                   |    4 +-
 core/server/src/shard/execution.rs                 |  273 +++---
 core/server/src/shard/system/messages.rs           |    3 +-
 core/server/src/shard/transmission/message.rs      |   55 +-
 core/server/src/state/command.rs                   |  342 ++++---
 core/server/src/state/entry.rs                     |   70 +-
 core/server/src/state/file.rs                      |   13 +-
 core/server/src/state/models.rs                    |  511 +++++-----
 core/server/src/state/system.rs                    |  193 ++--
 core/server/src/tcp/connection_handler.rs          |    4 +-
 core/simulator/src/client.rs                       |   26 +-
 core/tools/src/data-seeder/seeder.rs               |    4 +-
 examples/rust/Cargo.toml                           |    2 +-
 .../message-compression/consumer/main.rs           |   12 +-
 .../message-compression/producer/main.rs           |    4 +-
 .../message-headers/message-type/producer/main.rs  |    4 +-
 .../message-headers/typed-headers/producer/main.rs |    4 +-
 examples/rust/src/multi-tenant/consumer/main.rs    |    6 +-
 examples/rust/src/multi-tenant/producer/main.rs    |    4 +-
 242 files changed, 5063 insertions(+), 12353 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2433404ee..b5c76c369 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -990,9 +990,9 @@ dependencies = [
 
 [[package]]
 name = "aws-lc-sys"
-version = "0.39.0"
+version = "0.39.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1fa7e52a4c5c547c741610a2c6f123f3881e409b714cd27e6798ef020c514f0a"
+checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399"
 dependencies = [
  "cc",
  "cmake",
@@ -1349,16 +1349,16 @@ dependencies = [
 
 [[package]]
 name = "blake3"
-version = "1.8.3"
+version = "1.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d"
+checksum = "4d2d5991425dfd0785aed03aedcf0b321d61975c9b5b3689c774a2610ae0b51e"
 dependencies = [
  "arrayref",
  "arrayvec",
  "cc",
  "cfg-if",
  "constant_time_eq",
- "cpufeatures 0.2.17",
+ "cpufeatures 0.3.0",
 ]
 
 [[package]]
@@ -1730,9 +1730,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.57"
+version = "1.2.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
+checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
 dependencies = [
  "find-msvc-tools",
  "jobserver",
@@ -1904,9 +1904,9 @@ dependencies = [
 
 [[package]]
 name = "cmake"
-version = "0.1.57"
+version = "0.1.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
 dependencies = [
  "cc",
 ]
@@ -1994,9 +1994,9 @@ dependencies = [
 
 [[package]]
 name = "compio-driver"
-version = "0.11.3"
+version = "0.11.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8557d3804525f0e97594681f675929931782c9f8783a51c9f86da86b819150"
+checksum = "74d42d98dc890ee4db00c1e68a723391711aab6d67085880d716b72830f7c715"
 dependencies = [
  "cfg-if",
  "cfg_aliases",
@@ -2093,9 +2093,9 @@ dependencies = [
 
 [[package]]
 name = "compio-quic"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3864d7362ba5ec270178690e72f854e9360fa3163036fe8b88a3c4475321f8be"
+checksum = "ad9efdad81b920108b9de57148e1b9d73dc408b6d06a59ee64836dde651cf026"
 dependencies = [
  "cfg_aliases",
  "compio-buf",
@@ -3165,9 +3165,9 @@ dependencies = [
 
 [[package]]
 name = "dircpy"
-version = "0.3.19"
+version = "0.3.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a88521b0517f5f9d51d11925d8ab4523497dcf947073fa3231a311b63941131c"
+checksum = "ebcbec2b9a580ddee352ac38523d2ecd4dcaad53532957034394556909e27f4b"
 dependencies = [
  "jwalk",
  "log",
@@ -4860,9 +4860,9 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "1.8.1"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -4875,7 +4875,6 @@ dependencies = [
  "httpdate",
  "itoa",
  "pin-project-lite",
- "pin-utils",
  "smallvec",
  "tokio",
  "want",
@@ -5296,7 +5295,6 @@ dependencies = [
 name = "iggy-cli"
 version = "0.12.1-edge.1"
 dependencies = [
- "ahash 0.8.12",
  "anyhow",
  "async-trait",
  "bytes",
@@ -5307,6 +5305,7 @@ dependencies = [
  "dirs",
  "figlet-rs",
  "iggy",
+ "iggy_binary_protocol",
  "iggy_common",
  "keyring",
  "passterm",
@@ -5316,7 +5315,7 @@ dependencies = [
  "tempfile",
  "thiserror 2.0.18",
  "tokio",
- "toml 1.1.0+spec-1.1.0",
+ "toml 1.1.1+spec-1.1.0",
  "tracing",
  "tracing-appender",
  "tracing-subscriber",
@@ -5367,7 +5366,7 @@ dependencies = [
  "tempfile",
  "thiserror 2.0.18",
  "tokio",
- "toml 1.1.0+spec-1.1.0",
+ "toml 1.1.1+spec-1.1.0",
  "tower-http",
  "tracing",
  "tracing-opentelemetry",
@@ -5423,7 +5422,6 @@ name = "iggy_common"
 version = "0.9.5-edge.1"
 dependencies = [
  "aes-gcm",
- "ahash 0.8.12",
  "aligned-vec",
  "async-broadcast",
  "async-trait",
@@ -5523,7 +5521,7 @@ dependencies = [
  "simd-json",
  "strum_macros 0.28.0",
  "tokio",
- "toml 1.1.0+spec-1.1.0",
+ "toml 1.1.1+spec-1.1.0",
  "tracing",
 ]
 
@@ -5736,13 +5734,13 @@ dependencies = [
 name = "iggy_examples"
 version = "0.0.6"
 dependencies = [
- "ahash 0.8.12",
  "anyhow",
  "bytes",
  "chrono",
  "clap",
  "futures-util",
  "iggy",
+ "iggy_common",
  "lz4_flex 0.13.0",
  "rand 0.10.0",
  "serde",
@@ -5790,7 +5788,7 @@ dependencies = [
  "rgb",
  "tiff",
  "zune-core 0.5.1",
- "zune-jpeg 0.5.14",
+ "zune-jpeg 0.5.15",
 ]
 
 [[package]]
@@ -5915,7 +5913,6 @@ checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
 name = "integration"
 version = "0.0.1"
 dependencies = [
- "ahash 0.8.12",
  "assert_cmd",
  "async-trait",
  "base64 0.22.1",
@@ -5958,7 +5955,7 @@ dependencies = [
  "test-case",
  "testcontainers-modules",
  "tokio",
- "toml 1.1.0+spec-1.1.0",
+ "toml 1.1.1+spec-1.1.0",
  "tracing",
  "tracing-subscriber",
  "twox-hash",
@@ -5979,9 +5976,9 @@ dependencies = [
 
 [[package]]
 name = "inventory"
-version = "0.3.22"
+version = "0.3.24"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227"
+checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
 dependencies = [
  "rustversion",
 ]
@@ -6037,9 +6034,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
 
 [[package]]
 name = "iri-string"
-version = "0.7.11"
+version = "0.7.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb"
+checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
 dependencies = [
  "memchr",
  "serde",
@@ -6183,10 +6180,12 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.91"
+version = "0.3.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
+checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
 dependencies = [
+ "cfg-if",
+ "futures-util",
  "once_cell",
  "wasm-bindgen",
 ]
@@ -6404,9 +6403,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
 
 [[package]]
 name = "libc"
-version = "0.2.183"
+version = "0.2.184"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
+checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
 
 [[package]]
 name = "libdbus-sys"
@@ -6488,9 +6487,9 @@ dependencies = [
 
 [[package]]
 name = "libredox"
-version = "0.1.14"
+version = "0.1.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
+checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08"
 dependencies = [
  "bitflags 2.11.0",
  "libc",
@@ -6803,7 +6802,6 @@ dependencies = [
  "message_bus",
  "paste",
  "rmp-serde",
- "secrecy",
  "serde",
  "slab",
  "tempfile",
@@ -6875,9 +6873,9 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "1.1.1"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
 dependencies = [
  "libc",
  "log",
@@ -7220,9 +7218,9 @@ dependencies = [
 
 [[package]]
 name = "num-conv"
-version = "0.2.0"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
 
 [[package]]
 name = "num-derive"
@@ -7346,9 +7344,9 @@ dependencies = [
 
 [[package]]
 name = "octocrab"
-version = "0.49.6"
+version = "0.49.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "481d01ffe3fa4347e55474798e16d8d678aab19b8d7ca631ebb3c607cc87f9db"
+checksum = "63f6687a23731011d0117f9f4c3cdabaa7b5e42ca671f42b5cc0657c492540e3"
 dependencies = [
  "arc-swap",
  "async-trait",
@@ -7671,9 +7669,9 @@ dependencies = [
 
 [[package]]
 name = "papaya"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f92dd0b07c53a0a0c764db2ace8c541dc47320dad97c2200c2a637ab9dd2328f"
+checksum = "997ee03cd38c01469a7046643714f0ad28880bcb9e6679ff0666e24817ca19b7"
 dependencies = [
  "equivalent",
  "seize",
@@ -8211,7 +8209,7 @@ version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
 dependencies = [
- "toml_edit 0.25.8+spec-1.1.0",
+ "toml_edit 0.25.9+spec-1.1.0",
 ]
 
 [[package]]
@@ -8304,9 +8302,9 @@ dependencies = [
 
 [[package]]
 name = "prometheus-client"
-version = "0.24.0"
+version = "0.24.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4500adecd7af8e0e9f4dbce15cfee07ce913fbf6ad605cc468b83f2d531ee94"
+checksum = "cca3d75b4566b9a29fe1ed623587fb058e826eb329a0be4b7c4da1ebb2d7a6ca"
 dependencies = [
  "dtoa",
  "itoa",
@@ -9100,9 +9098,9 @@ dependencies = [
 
 [[package]]
 name = "rmcp"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba6b9d2f0efe2258b23767f1f9e0054cfbcac9c2d6f81a031214143096d7864f"
+checksum = "2231b2c085b371c01bc90c0e6c1cab8834711b6394533375bdbf870b0166d419"
 dependencies = [
  "async-trait",
  "base64 0.22.1",
@@ -9132,9 +9130,9 @@ dependencies = [
 
 [[package]]
 name = "rmcp-macros"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab9d95d7ed26ad8306352b0d5f05b593222b272790564589790d210aa15caa9e"
+checksum = "36ea0e100fadf81be85d7ff70f86cd805c7572601d4ab2946207f36540854b43"
 dependencies = [
  "darling 0.23.0",
  "proc-macro2",
@@ -9259,9 +9257,9 @@ dependencies = [
 
 [[package]]
 name = "rust_decimal"
-version = "1.40.0"
+version = "1.41.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0"
+checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a"
 dependencies = [
  "arrayvec",
  "borsh",
@@ -9271,13 +9269,14 @@ dependencies = [
  "rkyv",
  "serde",
  "serde_json",
+ "wasm-bindgen",
 ]
 
 [[package]]
 name = "rustc-hash"
-version = "2.1.1"
+version = "2.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
 
 [[package]]
 name = "rustc_version"
@@ -9749,9 +9748,9 @@ dependencies = [
 
 [[package]]
 name = "serde_spanned"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
 dependencies = [
  "serde_core",
 ]
@@ -9913,7 +9912,7 @@ dependencies = [
  "sysinfo 0.38.4",
  "tempfile",
  "thiserror 2.0.18",
- "toml 1.1.0+spec-1.1.0",
+ "toml 1.1.1+spec-1.1.0",
  "tower-http",
  "tracing",
  "tracing-appender",
@@ -10012,9 +10011,9 @@ dependencies = [
 
 [[package]]
 name = "simd-adler32"
-version = "0.3.8"
+version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
 
 [[package]]
 name = "simd-json"
@@ -10611,9 +10610,9 @@ dependencies = [
 
 [[package]]
 name = "synchrony"
-version = "0.1.6"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6d5f5d3091c2d998f6ab4c8b495c0d232ad0aecbc1fa9ac52c247a79d497e16"
+checksum = "c174d82fd56da8214ec095cfe4568e59e5ccb49d060e70c2f98e3ba352b23e45"
 dependencies = [
  "futures-util",
  "loom",
@@ -10665,9 +10664,9 @@ dependencies = [
 
 [[package]]
 name = "sys_traits"
-version = "0.1.26"
+version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe719c6bb50991671073b1469be68ccbcb5a2a2730873eab49b88c35a908037e"
+checksum = "5a79feaa49de4a6c8191bdbd5fb3eada50671e9367d874d1c12e3d36db131414"
 dependencies = [
  "sys_traits_macros",
 ]
@@ -10930,7 +10929,7 @@ dependencies = [
  "half",
  "quick-error",
  "weezl",
- "zune-jpeg 0.5.14",
+ "zune-jpeg 0.5.15",
 ]
 
 [[package]]
@@ -11136,17 +11135,17 @@ dependencies = [
 
 [[package]]
 name = "toml"
-version = "1.1.0+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc"
+checksum = "994b95d9e7bae62b34bab0e2a4510b801fa466066a6a8b2b57361fa1eba068ee"
 dependencies = [
  "indexmap 2.13.0",
  "serde_core",
- "serde_spanned 1.1.0",
- "toml_datetime 1.1.0+spec-1.1.0",
+ "serde_spanned 1.1.1",
+ "toml_datetime 1.1.1+spec-1.1.0",
  "toml_parser",
  "toml_writer",
- "winnow 1.0.0",
+ "winnow 1.0.1",
 ]
 
 [[package]]
@@ -11160,9 +11159,9 @@ dependencies = [
 
 [[package]]
 name = "toml_datetime"
-version = "1.1.0+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
 dependencies = [
  "serde_core",
 ]
@@ -11194,23 +11193,23 @@ dependencies = [
 
 [[package]]
 name = "toml_edit"
-version = "0.25.8+spec-1.1.0"
+version = "0.25.9+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c"
+checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5"
 dependencies = [
  "indexmap 2.13.0",
- "toml_datetime 1.1.0+spec-1.1.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
  "toml_parser",
- "winnow 1.0.0",
+ "winnow 1.0.1",
 ]
 
 [[package]]
 name = "toml_parser"
-version = "1.1.0+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011"
+checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
 dependencies = [
- "winnow 1.0.0",
+ "winnow 1.0.1",
 ]
 
 [[package]]
@@ -11221,9 +11220,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
 
 [[package]]
 name = "toml_writer"
-version = "1.1.0+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed"
+checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
 
 [[package]]
 name = "tonic"
@@ -11687,9 +11686,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.13.0"
+version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a559e63b5d8004e12f9bce88af5c6d939c58de839b7532cfe9653846cedd2a9e"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
 
 [[package]]
 name = "unicode-vo"
@@ -11844,9 +11843,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uuid"
-version = "1.22.0"
+version = "1.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
+checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
 dependencies = [
  "getrandom 0.4.2",
  "js-sys",
@@ -12034,9 +12033,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.114"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
+checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -12047,23 +12046,19 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.64"
+version = "0.4.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8"
+checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
 dependencies = [
- "cfg-if",
- "futures-util",
  "js-sys",
- "once_cell",
  "wasm-bindgen",
- "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.114"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
+checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -12071,9 +12066,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.114"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
+checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
 dependencies = [
  "bumpalo",
  "proc-macro2",
@@ -12084,9 +12079,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.114"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
+checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
 dependencies = [
  "unicode-ident",
 ]
@@ -12177,9 +12172,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.91"
+version = "0.3.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
+checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -12815,9 +12810,9 @@ dependencies = [
 
 [[package]]
 name = "winnow"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8"
+checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
 dependencies = [
  "memchr",
 ]
@@ -13080,18 +13075,18 @@ dependencies = [
 
 [[package]]
 name = "zerocopy"
-version = "0.8.47"
+version = "0.8.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
+checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.47"
+version = "0.8.48"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
+checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -13282,9 +13277,9 @@ dependencies = [
 
 [[package]]
 name = "zune-jpeg"
-version = "0.5.14"
+version = "0.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6"
+checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
 dependencies = [
  "zune-core 0.5.1",
 ]
diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md
index 9b019c26b..0135e7ed2 100644
--- a/DEPENDENCIES.md
+++ b/DEPENDENCIES.md
@@ -80,7 +80,7 @@ av-scenechange: 0.14.1, "MIT",
 av1-grain: 0.2.5, "BSD-2-Clause",
 avif-serialize: 0.8.8, "BSD-3-Clause",
 aws-lc-rs: 1.16.2, "(Apache-2.0 OR ISC) AND ISC",
-aws-lc-sys: 0.39.0, "(Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0) AND (Apache-2.0 OR ISC) AND Apache-2.0 AND BSD-3-Clause AND ISC AND MIT",
+aws-lc-sys: 0.39.1, "(Apache-2.0 OR ISC OR MIT) AND (Apache-2.0 OR ISC OR MIT-0) AND (Apache-2.0 OR ISC) AND Apache-2.0 AND BSD-3-Clause AND ISC AND MIT",
 axum: 0.8.8, "MIT",
 axum-core: 0.5.6, "MIT",
 axum-macros: 0.5.0, "MIT",
@@ -112,7 +112,7 @@ bitflags: 2.11.0, "Apache-2.0 OR MIT",
 bitstream-io: 4.9.0, "Apache-2.0 OR MIT",
 bitvec: 1.0.1, "MIT",
 blake2: 0.10.6, "Apache-2.0 OR MIT",
-blake3: 1.8.3, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR CC0-1.0",
+blake3: 1.8.4, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR CC0-1.0",
 block-buffer: 0.10.4, "Apache-2.0 OR MIT",
 block2: 0.6.2, "MIT",
 bnum: 0.12.1, "Apache-2.0 OR MIT",
@@ -145,7 +145,7 @@ capacity_builder: 0.5.0, "MIT",
 capacity_builder_macros: 0.3.0, "MIT",
 cargo-platform: 0.3.2, "Apache-2.0 OR MIT",
 cargo_metadata: 0.23.1, "MIT",
-cc: 1.2.57, "Apache-2.0 OR MIT",
+cc: 1.2.58, "Apache-2.0 OR MIT",
 cesu8: 1.1.0, "Apache-2.0 OR MIT",
 cexpr: 0.6.0, "Apache-2.0 OR MIT",
 cfg-if: 1.0.4, "Apache-2.0 OR MIT",
@@ -162,7 +162,7 @@ clap_complete: 4.6.0, "Apache-2.0 OR MIT",
 clap_derive: 4.6.0, "Apache-2.0 OR MIT",
 clap_lex: 1.1.0, "Apache-2.0 OR MIT",
 clock: 0.1.0, "N/A",
-cmake: 0.1.57, "Apache-2.0 OR MIT",
+cmake: 0.1.58, "Apache-2.0 OR MIT",
 cobs: 0.3.0, "Apache-2.0 OR MIT",
 color_quant: 1.1.0, "MIT",
 colorchoice: 1.0.5, "Apache-2.0 OR MIT",
@@ -171,13 +171,13 @@ combine: 4.6.7, "MIT",
 comfy-table: 7.2.2, "MIT",
 compio: 0.18.0, "MIT",
 compio-buf: 0.8.1, "MIT",
-compio-driver: 0.11.3, "MIT",
+compio-driver: 0.11.4, "MIT",
 compio-fs: 0.11.0, "MIT",
 compio-io: 0.9.1, "MIT",
 compio-log: 0.1.0, "MIT",
 compio-macros: 0.1.2, "MIT",
 compio-net: 0.11.1, "MIT",
-compio-quic: 0.7.1, "MIT",
+compio-quic: 0.7.2, "MIT",
 compio-runtime: 0.11.0, "MIT",
 compio-tls: 0.9.0, "MIT",
 compio-ws: 0.3.0, "MIT",
@@ -270,7 +270,7 @@ derive_more: 2.1.1, "MIT",
 derive_more-impl: 2.1.1, "MIT",
 difflib: 0.4.0, "MIT",
 digest: 0.10.7, "Apache-2.0 OR MIT",
-dircpy: 0.3.19, "MIT",
+dircpy: 0.3.20, "MIT",
 dirs: 6.0.0, "Apache-2.0 OR MIT",
 dirs-sys: 0.5.0, "Apache-2.0 OR MIT",
 dispatch2: 0.3.1, "Apache-2.0 OR MIT OR Zlib",
@@ -430,7 +430,7 @@ human-repr: 1.1.0, "MIT",
 humantime: 2.3.0, "Apache-2.0 OR MIT",
 hwlocality: 1.0.0-alpha.11, "MIT",
 hwlocality-sys: 0.6.4, "MIT",
-hyper: 1.8.1, "MIT",
+hyper: 1.9.0, "MIT",
 hyper-named-pipe: 0.1.0, "Apache-2.0",
 hyper-rustls: 0.27.7, "Apache-2.0 OR ISC OR MIT",
 hyper-timeout: 0.5.2, "Apache-2.0 OR MIT",
@@ -493,13 +493,13 @@ inout: 0.1.4, "Apache-2.0 OR MIT",
 integer-encoding: 3.0.4, "MIT",
 integration: 0.0.1, "Apache-2.0",
 interpolate_name: 0.2.4, "MIT",
-inventory: 0.3.22, "Apache-2.0 OR MIT",
+inventory: 0.3.24, "Apache-2.0 OR MIT",
 io-uring: 0.7.11, "Apache-2.0 OR MIT",
 io_uring_buf_ring: 0.2.3, "MIT",
 iobuf: 0.1.0, "Apache-2.0",
 ipconfig: 0.3.4, "Apache-2.0 OR MIT",
 ipnet: 2.12.0, "Apache-2.0 OR MIT",
-iri-string: 0.7.11, "Apache-2.0 OR MIT",
+iri-string: 0.7.12, "Apache-2.0 OR MIT",
 is_terminal_polyfill: 1.70.2, "Apache-2.0 OR MIT",
 itertools: 0.13.0, "Apache-2.0 OR MIT",
 itertools: 0.14.0, "Apache-2.0 OR MIT",
@@ -514,7 +514,7 @@ jni-sys: 0.4.1, "Apache-2.0 OR MIT",
 jni-sys-macros: 0.4.1, "Apache-2.0 OR MIT",
 jobserver: 0.1.34, "Apache-2.0 OR MIT",
 journal: 0.1.0, "Apache-2.0",
-js-sys: 0.3.91, "Apache-2.0 OR MIT",
+js-sys: 0.3.94, "Apache-2.0 OR MIT",
 jsonwebtoken: 10.3.0, "MIT",
 jwalk: 0.8.1, "MIT",
 keccak: 0.1.6, "Apache-2.0 OR MIT",
@@ -536,7 +536,7 @@ lexical-util: 1.0.7, "Apache-2.0 OR MIT",
 lexical-write-float: 1.0.6, "Apache-2.0 OR MIT",
 lexical-write-integer: 1.0.6, "Apache-2.0 OR MIT",
 libbz2-rs-sys: 0.2.2, "bzip2-1.0.6",
-libc: 0.2.183, "Apache-2.0 OR MIT",
+libc: 0.2.184, "Apache-2.0 OR MIT",
 libdbus-sys: 0.2.7, "Apache-2.0 OR MIT",
 libfuzzer-sys: 0.4.12, "(Apache-2.0 OR MIT) AND NCSA",
 libgit2-sys: 0.18.3+1.9.2, "Apache-2.0 OR MIT",
@@ -545,7 +545,7 @@ liblzma: 0.4.6, "Apache-2.0 OR MIT",
 liblzma-sys: 0.4.5, "Apache-2.0 OR MIT",
 libm: 0.2.16, "MIT",
 libmimalloc-sys: 0.1.44, "MIT",
-libredox: 0.1.14, "MIT",
+libredox: 0.1.15, "MIT",
 libsqlite3-sys: 0.30.1, "MIT",
 libz-sys: 1.1.25, "Apache-2.0 OR MIT",
 linked-hash-map: 0.5.6, "Apache-2.0 OR MIT",
@@ -586,7 +586,7 @@ mime: 0.3.17, "Apache-2.0 OR MIT",
 mime_guess: 2.0.5, "MIT",
 minimal-lexical: 0.2.1, "Apache-2.0 OR MIT",
 miniz_oxide: 0.8.9, "Apache-2.0 OR MIT OR Zlib",
-mio: 1.1.1, "MIT",
+mio: 1.2.0, "MIT",
 mockall: 0.14.0, "Apache-2.0 OR MIT",
 mockall_derive: 0.14.0, "Apache-2.0 OR MIT",
 moka: 0.12.15, "(Apache-2.0 OR MIT) AND Apache-2.0",
@@ -616,7 +616,7 @@ num: 0.4.3, "Apache-2.0 OR MIT",
 num-bigint: 0.4.6, "Apache-2.0 OR MIT",
 num-bigint-dig: 0.8.6, "Apache-2.0 OR MIT",
 num-complex: 0.4.6, "Apache-2.0 OR MIT",
-num-conv: 0.2.0, "Apache-2.0 OR MIT",
+num-conv: 0.2.1, "Apache-2.0 OR MIT",
 num-derive: 0.4.2, "Apache-2.0 OR MIT",
 num-integer: 0.1.46, "Apache-2.0 OR MIT",
 num-iter: 0.1.45, "Apache-2.0 OR MIT",
@@ -630,7 +630,7 @@ objc2: 0.6.4, "MIT",
 objc2-core-foundation: 0.3.2, "Apache-2.0 OR MIT OR Zlib",
 objc2-encode: 4.1.0, "MIT",
 objc2-io-kit: 0.3.2, "Apache-2.0 OR MIT OR Zlib",
-octocrab: 0.49.6, "Apache-2.0 OR MIT",
+octocrab: 0.49.7, "Apache-2.0 OR MIT",
 oid-registry: 0.8.1, "Apache-2.0 OR MIT",
 once_cell: 1.21.4, "Apache-2.0 OR MIT",
 once_cell_polyfill: 1.70.2, "Apache-2.0 OR MIT",
@@ -656,7 +656,7 @@ os_pipe: 1.2.3, "MIT",
 outref: 0.5.2, "MIT",
 p256: 0.13.2, "Apache-2.0 OR MIT",
 p384: 0.13.1, "Apache-2.0 OR MIT",
-papaya: 0.2.3, "MIT",
+papaya: 0.2.4, "MIT",
 parking: 2.2.1, "Apache-2.0 OR MIT",
 parking_lot: 0.12.5, "Apache-2.0 OR MIT",
 parking_lot_core: 0.9.12, "Apache-2.0 OR MIT",
@@ -718,7 +718,7 @@ proc-macro2: 1.0.106, "Apache-2.0 OR MIT",
 proc-macro2-diagnostics: 0.10.1, "Apache-2.0 OR MIT",
 profiling: 1.0.17, "Apache-2.0 OR MIT",
 profiling-procmacros: 1.0.17, "Apache-2.0 OR MIT",
-prometheus-client: 0.24.0, "Apache-2.0 OR MIT",
+prometheus-client: 0.24.1, "Apache-2.0 OR MIT",
 prometheus-client-derive-encode: 0.5.0, "Apache-2.0 OR MIT",
 prost: 0.14.3, "Apache-2.0",
 prost-derive: 0.14.3, "Apache-2.0",
@@ -782,8 +782,8 @@ ring: 0.17.14, "Apache-2.0 AND ISC",
 ringbuffer: 0.16.0, "MIT",
 rkyv: 0.7.46, "MIT",
 rkyv_derive: 0.7.46, "MIT",
-rmcp: 1.2.0, "Apache-2.0",
-rmcp-macros: 1.2.0, "Apache-2.0",
+rmcp: 1.3.0, "Apache-2.0",
+rmcp-macros: 1.3.0, "Apache-2.0",
 rmp: 0.8.15, "MIT",
 rmp-serde: 1.3.1, "MIT",
 roaring: 0.11.3, "Apache-2.0 OR MIT",
@@ -795,8 +795,8 @@ rust-embed: 8.11.0, "MIT",
 rust-embed-impl: 8.11.0, "MIT",
 rust-embed-utils: 8.11.0, "MIT",
 rust-ini: 0.21.3, "MIT",
-rust_decimal: 1.40.0, "MIT",
-rustc-hash: 2.1.1, "Apache-2.0 OR MIT",
+rust_decimal: 1.41.0, "MIT",
+rustc-hash: 2.1.2, "Apache-2.0 OR MIT",
 rustc_version: 0.4.1, "Apache-2.0 OR MIT",
 rustc_version_runtime: 0.3.0, "MIT",
 rusticata-macros: 4.1.0, "Apache-2.0 OR MIT",
@@ -842,7 +842,7 @@ serde_json: 1.0.149, "Apache-2.0 OR MIT",
 serde_path_to_error: 0.1.20, "Apache-2.0 OR MIT",
 serde_repr: 0.1.20, "Apache-2.0 OR MIT",
 serde_spanned: 0.6.9, "Apache-2.0 OR MIT",
-serde_spanned: 1.1.0, "Apache-2.0 OR MIT",
+serde_spanned: 1.1.1, "Apache-2.0 OR MIT",
 serde_urlencoded: 0.7.1, "Apache-2.0 OR MIT",
 serde_v8: 0.260.0, "MIT",
 serde_with: 3.18.0, "Apache-2.0 OR MIT",
@@ -859,7 +859,7 @@ sharded-slab: 0.1.7, "MIT",
 shlex: 1.3.0, "Apache-2.0 OR MIT",
 signal-hook-registry: 1.4.8, "Apache-2.0 OR MIT",
 signature: 2.2.0, "Apache-2.0 OR MIT",
-simd-adler32: 0.3.8, "MIT",
+simd-adler32: 0.3.9, "MIT",
 simd-json: 0.17.0, "Apache-2.0 OR MIT",
 simd_helpers: 0.1.0, "MIT",
 simdutf8: 0.1.5, "Apache-2.0 OR MIT",
@@ -907,12 +907,12 @@ svgtypes: 0.15.3, "Apache-2.0 OR MIT",
 syn: 1.0.109, "Apache-2.0 OR MIT",
 syn: 2.0.117, "Apache-2.0 OR MIT",
 sync_wrapper: 1.0.2, "Apache-2.0",
-synchrony: 0.1.6, "MIT",
+synchrony: 0.1.7, "MIT",
 synstructure: 0.13.2, "MIT",
 synthez: 0.4.0, "BlueOak-1.0.0",
 synthez-codegen: 0.4.0, "BlueOak-1.0.0",
 synthez-core: 0.4.0, "BlueOak-1.0.0",
-sys_traits: 0.1.26, "MIT",
+sys_traits: 0.1.27, "MIT",
 sys_traits_macros: 0.1.0, "MIT",
 sysinfo: 0.37.2, "MIT",
 sysinfo: 0.38.4, "MIT",
@@ -954,15 +954,15 @@ tokio-tungstenite: 0.29.0, "MIT",
 tokio-util: 0.7.18, "MIT",
 tokise: 0.2.1, "Apache-2.0 OR MIT",
 toml: 0.8.23, "Apache-2.0 OR MIT",
-toml: 1.1.0+spec-1.1.0, "Apache-2.0 OR MIT",
+toml: 1.1.1+spec-1.1.0, "Apache-2.0 OR MIT",
 toml_datetime: 0.6.11, "Apache-2.0 OR MIT",
-toml_datetime: 1.1.0+spec-1.1.0, "Apache-2.0 OR MIT",
+toml_datetime: 1.1.1+spec-1.1.0, "Apache-2.0 OR MIT",
 toml_edit: 0.19.15, "Apache-2.0 OR MIT",
 toml_edit: 0.22.27, "Apache-2.0 OR MIT",
-toml_edit: 0.25.8+spec-1.1.0, "Apache-2.0 OR MIT",
-toml_parser: 1.1.0+spec-1.1.0, "Apache-2.0 OR MIT",
+toml_edit: 0.25.9+spec-1.1.0, "Apache-2.0 OR MIT",
+toml_parser: 1.1.1+spec-1.1.0, "Apache-2.0 OR MIT",
 toml_write: 0.1.2, "Apache-2.0 OR MIT",
-toml_writer: 1.1.0+spec-1.1.0, "Apache-2.0 OR MIT",
+toml_writer: 1.1.1+spec-1.1.0, "Apache-2.0 OR MIT",
 tonic: 0.14.5, "MIT",
 tonic-prost: 0.14.5, "MIT",
 tools: 0.1.0, "Apache-2.0",
@@ -1007,7 +1007,7 @@ unicode-linebreak: 0.1.5, "Apache-2.0",
 unicode-normalization: 0.1.25, "Apache-2.0 OR MIT",
 unicode-properties: 0.1.4, "Apache-2.0 OR MIT",
 unicode-script: 0.5.8, "Apache-2.0 OR MIT",
-unicode-segmentation: 1.13.0, "Apache-2.0 OR MIT",
+unicode-segmentation: 1.13.2, "Apache-2.0 OR MIT",
 unicode-vo: 0.1.0, "Apache-2.0 OR MIT",
 unicode-width: 0.1.14, "Apache-2.0 OR MIT",
 unicode-width: 0.2.2, "Apache-2.0 OR MIT",
@@ -1025,7 +1025,7 @@ utf8-width: 0.1.8, "MIT",
 utf8-zero: 0.8.1, "Apache-2.0 OR MIT",
 utf8_iter: 1.0.4, "Apache-2.0 OR MIT",
 utf8parse: 0.2.2, "Apache-2.0 OR MIT",
-uuid: 1.22.0, "Apache-2.0 OR MIT",
+uuid: 1.23.0, "Apache-2.0 OR MIT",
 v8: 137.3.0, "MIT",
 v_frame: 0.3.9, "BSD-2-Clause",
 v_htmlescape: 0.15.8, "Apache-2.0 OR MIT",
@@ -1045,11 +1045,11 @@ wasi: 0.11.1+wasi-snapshot-preview1, "Apache-2.0 OR Apache-2.0 WITH LLVM-excepti
 wasip2: 1.0.2+wasi-0.2.9, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wasip3: 0.4.0+wasi-0.3.0-rc-2026-01-06, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wasite: 0.1.0, "Apache-2.0 OR BSL-1.0 OR MIT",
-wasm-bindgen: 0.2.114, "Apache-2.0 OR MIT",
-wasm-bindgen-futures: 0.4.64, "Apache-2.0 OR MIT",
-wasm-bindgen-macro: 0.2.114, "Apache-2.0 OR MIT",
-wasm-bindgen-macro-support: 0.2.114, "Apache-2.0 OR MIT",
-wasm-bindgen-shared: 0.2.114, "Apache-2.0 OR MIT",
+wasm-bindgen: 0.2.117, "Apache-2.0 OR MIT",
+wasm-bindgen-futures: 0.4.67, "Apache-2.0 OR MIT",
+wasm-bindgen-macro: 0.2.117, "Apache-2.0 OR MIT",
+wasm-bindgen-macro-support: 0.2.117, "Apache-2.0 OR MIT",
+wasm-bindgen-shared: 0.2.117, "Apache-2.0 OR MIT",
 wasm-encoder: 0.244.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wasm-metadata: 0.244.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wasm-streams: 0.4.2, "Apache-2.0 OR MIT",
@@ -1057,7 +1057,7 @@ wasm-streams: 0.5.0, "Apache-2.0 OR MIT",
 wasm_dep_analyzer: 0.3.0, "MIT",
 wasmparser: 0.244.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wasmtimer: 0.4.3, "MIT",
-web-sys: 0.3.91, "Apache-2.0 OR MIT",
+web-sys: 0.3.94, "Apache-2.0 OR MIT",
 web-time: 1.1.0, "Apache-2.0 OR MIT",
 webpki-root-certs: 1.0.6, "CDLA-Permissive-2.0",
 webpki-roots: 0.26.11, "CDLA-Permissive-2.0",
@@ -1133,7 +1133,7 @@ windows_x86_64_msvc: 0.52.6, "Apache-2.0 OR MIT",
 windows_x86_64_msvc: 0.53.1, "Apache-2.0 OR MIT",
 winnow: 0.5.40, "MIT",
 winnow: 0.7.15, "MIT",
-winnow: 1.0.0, "MIT",
+winnow: 1.0.1, "MIT",
 winsafe: 0.0.19, "MIT",
 wit-bindgen: 0.51.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
 wit-bindgen-core: 0.51.0, "Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT",
@@ -1155,8 +1155,8 @@ yew-router: 0.20.0, "Apache-2.0 OR MIT",
 yew-router-macro: 0.20.0, "Apache-2.0 OR MIT",
 yoke: 0.8.1, "Unicode-3.0",
 yoke-derive: 0.8.1, "Unicode-3.0",
-zerocopy: 0.8.47, "Apache-2.0 OR BSD-2-Clause OR MIT",
-zerocopy-derive: 0.8.47, "Apache-2.0 OR BSD-2-Clause OR MIT",
+zerocopy: 0.8.48, "Apache-2.0 OR BSD-2-Clause OR MIT",
+zerocopy-derive: 0.8.48, "Apache-2.0 OR BSD-2-Clause OR MIT",
 zerofrom: 0.1.6, "Unicode-3.0",
 zerofrom-derive: 0.1.6, "Unicode-3.0",
 zeroize: 1.8.2, "Apache-2.0 OR MIT",
@@ -1176,4 +1176,4 @@ zune-core: 0.4.12, "Apache-2.0 OR MIT OR Zlib",
 zune-core: 0.5.1, "Apache-2.0 OR MIT OR Zlib",
 zune-inflate: 0.2.54, "Apache-2.0 OR MIT OR Zlib",
 zune-jpeg: 0.4.21, "Apache-2.0 OR MIT OR Zlib",
-zune-jpeg: 0.5.14, "Apache-2.0 OR MIT OR Zlib",
+zune-jpeg: 0.5.15, "Apache-2.0 OR MIT OR Zlib",
diff --git a/core/binary_protocol/src/lib.rs b/core/binary_protocol/src/lib.rs
index 8e5541516..a63aad99a 100644
--- a/core/binary_protocol/src/lib.rs
+++ b/core/binary_protocol/src/lib.rs
@@ -87,3 +87,10 @@ pub use primitives::permissions::{
     WireGlobalPermissions, WirePermissions, WireStreamPermissions, WireTopicPermissions,
 };
 pub use primitives::polling_strategy::WirePollingStrategy;
+pub use primitives::user_headers::{
+    WireHeaderKind, WireUserHeaderEntry, WireUserHeaderIterator, WireUserHeaders,
+    encode_user_headers, user_headers_encoded_size, validate_user_headers,
+};
+
+/// Maximum number of partitions allowed in a single create/delete request.
+pub const MAX_PARTITIONS_PER_REQUEST: u32 = 1000;
diff --git a/core/binary_protocol/src/primitives/mod.rs b/core/binary_protocol/src/primitives/mod.rs
index dcbef623e..d1d7ee56a 100644
--- a/core/binary_protocol/src/primitives/mod.rs
+++ b/core/binary_protocol/src/primitives/mod.rs
@@ -22,3 +22,4 @@ pub mod identifier;
 pub mod partitioning;
 pub mod permissions;
 pub mod polling_strategy;
+pub mod user_headers;
diff --git a/core/binary_protocol/src/primitives/user_headers.rs b/core/binary_protocol/src/primitives/user_headers.rs
new file mode 100644
index 000000000..2f49b39d6
--- /dev/null
+++ b/core/binary_protocol/src/primitives/user_headers.rs
@@ -0,0 +1,544 @@
+// 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.
+
+//! Wire-level primitives for user header TLV validation, iteration, and encoding.
+//!
+//! User headers are encoded as a sequence of key-value TLV pairs:
+//! ```text
+//! [key_kind:u8][key_len:u32_le][key_data:N][val_kind:u8][val_len:u32_le][val_data:M] ...
+//! ```
+//!
+//! This module provides structural validation and zero-copy iteration
+//! over these bytes without interpreting semantic meaning of kind codes.
+//! Domain-level interpretation (mapping kind codes to typed values)
+//! happens in `iggy_common`.
+
+use std::borrow::Cow;
+
+use bytes::{BufMut, Bytes, BytesMut};
+
+use crate::WireError;
+use crate::codec::{WireEncode, read_u8, read_u32_le};
+
+/// Maximum byte length for a single header key or value on the wire.
+const MAX_FIELD_LENGTH: u32 = 255;
+
+/// Opaque header kind tag as transmitted on the wire.
+///
+/// This is a `u8` newtype - NOT an exhaustive enum. New kind codes can be
+/// added without breaking structural validation, which is critical for
+/// VSR rolling upgrades where older nodes must forward messages containing
+/// kind codes they do not yet understand.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct WireHeaderKind(pub u8);
+
+impl WireHeaderKind {
+    /// Highest currently defined kind code (Float64 = 15).
+    pub const KNOWN_MAX: u8 = 15;
+
+    /// Returns `true` if the kind code is within the currently defined range.
+    #[must_use]
+    pub fn is_known(self) -> bool {
+        (1..=Self::KNOWN_MAX).contains(&self.0)
+    }
+}
+
+/// A single key-value header entry borrowing the underlying buffer.
+#[derive(Debug)]
+pub struct WireUserHeaderEntry<'a> {
+    pub key_kind: WireHeaderKind,
+    pub key: &'a [u8],
+    pub value_kind: WireHeaderKind,
+    pub value: &'a [u8],
+}
+
+/// Validate the structural integrity of a user headers byte buffer.
+///
+/// Walks TLV pairs and checks:
+/// - Every kind byte is non-zero
+/// - Every length is in `1..=255`
+/// - No buffer overrun
+/// - Total consumed bytes equals `buf.len()` (no trailing garbage)
+/// - Even number of TLV entries (each pair = key + value)
+///
+/// Returns the number of complete key-value pairs on success.
+///
+/// # Errors
+///
+/// Returns `WireError::UnexpectedEof` if the buffer is truncated mid-entry,
+/// or `WireError::Validation` if structural constraints are violated.
+pub fn validate_user_headers(buf: &[u8]) -> Result<u32, WireError> {
+    if buf.is_empty() {
+        return Ok(0);
+    }
+
+    let mut pos = 0;
+    let mut tlv_count: u32 = 0;
+
+    while pos < buf.len() {
+        let kind = read_u8(buf, pos)?;
+        if kind == 0 {
+            return Err(WireError::Validation(Cow::Owned(format!(
+                "header kind is 0 (reserved) at offset {pos}"
+            ))));
+        }
+        pos += 1;
+
+        let length = read_u32_le(buf, pos)?;
+        pos += 4;
+
+        if length == 0 || length > MAX_FIELD_LENGTH {
+            return Err(WireError::Validation(Cow::Owned(format!(
+                "header field length {length} out of range 1..={MAX_FIELD_LENGTH} at offset {}",
+                pos - 4
+            ))));
+        }
+
+        let data_end = pos
+            .checked_add(length as usize)
+            .ok_or(WireError::Validation(Cow::Borrowed(
+                "header field length overflow",
+            )))?;
+        if data_end > buf.len() {
+            return Err(WireError::UnexpectedEof {
+                offset: pos,
+                need: length as usize,
+                have: buf.len() - pos,
+            });
+        }
+        pos = data_end;
+
+        tlv_count = tlv_count
+            .checked_add(1)
+            .ok_or(WireError::Validation(Cow::Borrowed(
+                "header entry count overflow",
+            )))?;
+    }
+
+    if !tlv_count.is_multiple_of(2) {
+        return Err(WireError::Validation(Cow::Owned(format!(
+            "odd number of TLV entries ({tlv_count}), expected key-value pairs"
+        ))));
+    }
+
+    Ok(tlv_count / 2)
+}
+
+/// Zero-copy iterator over pre-validated user header bytes.
+///
+/// Yields one [`WireUserHeaderEntry`] per key-value pair. The buffer
+/// **must** have been validated by [`validate_user_headers`] first;
+/// iterating an invalid buffer may yield garbage or panic.
+pub struct WireUserHeaderIterator<'a> {
+    buf: &'a [u8],
+    pos: usize,
+}
+
+impl<'a> WireUserHeaderIterator<'a> {
+    /// Create an iterator over a pre-validated user headers buffer.
+    #[must_use]
+    pub const fn new(buf: &'a [u8]) -> Self {
+        Self { buf, pos: 0 }
+    }
+}
+
+impl<'a> Iterator for WireUserHeaderIterator<'a> {
+    type Item = WireUserHeaderEntry<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.pos >= self.buf.len() {
+            return None;
+        }
+
+        // Key TLV
+        let key_kind = self.buf[self.pos];
+        self.pos += 1;
+
+        let key_len = u32::from_le_bytes(
+            self.buf[self.pos..self.pos + 4]
+                .try_into()
+                .expect("pre-validated buffer"),
+        ) as usize;
+        self.pos += 4;
+
+        let key = &self.buf[self.pos..self.pos + key_len];
+        self.pos += key_len;
+
+        // Value TLV
+        let value_kind = self.buf[self.pos];
+        self.pos += 1;
+
+        let value_len = u32::from_le_bytes(
+            self.buf[self.pos..self.pos + 4]
+                .try_into()
+                .expect("pre-validated buffer"),
+        ) as usize;
+        self.pos += 4;
+
+        let value = &self.buf[self.pos..self.pos + value_len];
+        self.pos += value_len;
+
+        Some(WireUserHeaderEntry {
+            key_kind: WireHeaderKind(key_kind),
+            key,
+            value_kind: WireHeaderKind(value_kind),
+            value,
+        })
+    }
+}
+
+/// Calculate the exact encoded size for a set of header entries.
+///
+/// Each entry tuple is `(key_kind, key_data, value_kind, value_data)`.
+#[must_use]
+pub fn user_headers_encoded_size(entries: &[(u8, &[u8], u8, &[u8])]) -> usize {
+    entries
+        .iter()
+        .map(|(_, k, _, v)| 1 + 4 + k.len() + 1 + 4 + v.len())
+        .sum()
+}
+
+/// Encode header entries into a caller-owned buffer.
+///
+/// Each entry tuple is `(key_kind, key_data, value_kind, value_data)`.
+/// The caller should pre-allocate `buf` with [`user_headers_encoded_size`].
+pub fn encode_user_headers(entries: &[(u8, &[u8], u8, &[u8])], buf: &mut BytesMut) {
+    for &(key_kind, key_data, value_kind, value_data) in entries {
+        buf.put_u8(key_kind);
+        #[allow(clippy::cast_possible_truncation)]
+        buf.put_u32_le(key_data.len() as u32);
+        buf.put_slice(key_data);
+        buf.put_u8(value_kind);
+        #[allow(clippy::cast_possible_truncation)]
+        buf.put_u32_le(value_data.len() as u32);
+        buf.put_slice(value_data);
+    }
+}
+
+/// Pre-validated user headers as a contiguous TLV byte buffer.
+///
+/// Construction validates structural integrity (no partial TLVs, no zero kinds,
+/// no oversized fields, even entry count). The inner bytes are immutable.
+///
+/// This type is intentionally opaque at the wire layer. Domain-level
+/// interpretation of kind codes happens in `iggy_common` via the conversion
+/// bridge in `wire_conversions.rs`.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WireUserHeaders(Bytes);
+
+impl WireUserHeaders {
+    /// Validate and wrap a borrowed byte slice (copies into new `Bytes`).
+    ///
+    /// # Errors
+    /// Returns `WireError` if the buffer is not structurally valid TLV.
+    pub fn from_slice(buf: &[u8]) -> Result<Self, WireError> {
+        validate_user_headers(buf)?;
+        Ok(Self(Bytes::copy_from_slice(buf)))
+    }
+
+    /// Validate and wrap an owned `Bytes` buffer (zero-copy).
+    ///
+    /// # Errors
+    /// Returns `WireError` if the buffer is not structurally valid TLV.
+    pub fn from_bytes(buf: Bytes) -> Result<Self, WireError> {
+        validate_user_headers(&buf)?;
+        Ok(Self(buf))
+    }
+
+    /// Wrap pre-validated bytes without re-checking.
+    ///
+    /// # Safety contract (not `unsafe`, but caller must ensure):
+    /// The bytes must be structurally valid TLV as defined by
+    /// [`validate_user_headers`]. Iterating invalid bytes will panic.
+    pub const fn from_validated(buf: Bytes) -> Self {
+        Self(buf)
+    }
+
+    /// Empty headers (zero-length buffer).
+    #[must_use]
+    pub const fn empty() -> Self {
+        Self(Bytes::new())
+    }
+
+    #[must_use]
+    pub const fn is_empty(&self) -> bool {
+        self.0.is_empty()
+    }
+
+    /// The raw validated bytes, suitable for wire transmission.
+    #[must_use]
+    pub fn as_bytes(&self) -> &[u8] {
+        &self.0
+    }
+
+    /// Consume into the inner `Bytes` handle (zero-copy).
+    #[must_use]
+    pub fn into_bytes(self) -> Bytes {
+        self.0
+    }
+
+    /// Zero-copy iteration over the pre-validated entries.
+    #[must_use]
+    pub fn iter(&self) -> WireUserHeaderIterator<'_> {
+        WireUserHeaderIterator::new(&self.0)
+    }
+}
+
+impl<'a> IntoIterator for &'a WireUserHeaders {
+    type Item = WireUserHeaderEntry<'a>;
+    type IntoIter = WireUserHeaderIterator<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl WireEncode for WireUserHeaders {
+    fn encoded_size(&self) -> usize {
+        self.0.len()
+    }
+
+    fn encode(&self, buf: &mut BytesMut) {
+        buf.put_slice(&self.0);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn encode_test_entries(entries: &[(u8, &[u8], u8, &[u8])]) -> Vec<u8> {
+        let size = user_headers_encoded_size(entries);
+        let mut buf = BytesMut::with_capacity(size);
+        encode_user_headers(entries, &mut buf);
+        buf.to_vec()
+    }
+
+    #[test]
+    fn empty_buffer_is_valid() {
+        assert_eq!(validate_user_headers(&[]).unwrap(), 0);
+    }
+
+    #[test]
+    fn single_pair_round_trip() {
+        let entries = [(6u8, b"test" as &[u8], 2u8, b"val" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+
+        assert_eq!(validate_user_headers(&encoded).unwrap(), 1);
+
+        let mut iter = WireUserHeaderIterator::new(&encoded);
+        let entry = iter.next().unwrap();
+        assert_eq!(entry.key_kind, WireHeaderKind(6));
+        assert_eq!(entry.key, b"test");
+        assert_eq!(entry.value_kind, WireHeaderKind(2));
+        assert_eq!(entry.value, b"val");
+        assert!(iter.next().is_none());
+    }
+
+    #[test]
+    fn multiple_pairs() {
+        let entries = [
+            (1u8, b"k1" as &[u8], 2u8, b"v1" as &[u8]),
+            (
+                6u8,
+                &42i32.to_le_bytes() as &[u8],
+                12u8,
+                &99u64.to_le_bytes() as &[u8],
+            ),
+            (3u8, &[1u8] as &[u8], 9u8, &[255u8] as &[u8]),
+        ];
+        let encoded = encode_test_entries(&entries);
+
+        assert_eq!(validate_user_headers(&encoded).unwrap(), 3);
+
+        let items: Vec<_> = WireUserHeaderIterator::new(&encoded).collect();
+        assert_eq!(items.len(), 3);
+        assert_eq!(items[0].key, b"k1");
+        assert_eq!(items[1].key, &42i32.to_le_bytes());
+        assert_eq!(items[2].value, &[255u8]);
+    }
+
+    #[test]
+    fn unknown_kind_codes_pass_validation() {
+        let entries = [(16u8, b"a" as &[u8], 255u8, b"b" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+        assert_eq!(validate_user_headers(&encoded).unwrap(), 1);
+
+        let entry = WireUserHeaderIterator::new(&encoded).next().unwrap();
+        assert_eq!(entry.key_kind, WireHeaderKind(16));
+        assert!(!entry.key_kind.is_known());
+        assert_eq!(entry.value_kind, WireHeaderKind(255));
+        assert!(!entry.value_kind.is_known());
+    }
+
+    #[test]
+    fn known_kind_codes() {
+        for code in 1..=15u8 {
+            assert!(WireHeaderKind(code).is_known());
+        }
+        assert!(!WireHeaderKind(0).is_known());
+        assert!(!WireHeaderKind(16).is_known());
+    }
+
+    #[test]
+    fn truncated_at_kind_byte() {
+        let entries = [(1u8, b"k" as &[u8], 2u8, b"v" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+        // Truncate to just the first kind byte and length, missing data
+        let result = validate_user_headers(&encoded[..3]);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn truncated_at_length_field() {
+        let entries = [(1u8, b"k" as &[u8], 2u8, b"v" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+        // Only kind byte, partial length
+        let result = validate_user_headers(&encoded[..2]);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn truncated_at_data() {
+        let entries = [(1u8, b"longkey" as &[u8], 2u8, b"v" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+        // Kind + length present, but data truncated
+        let result = validate_user_headers(&encoded[..7]);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn zero_length_rejected() {
+        // kind=1, length=0
+        let buf = [1u8, 0, 0, 0, 0];
+        let result = validate_user_headers(&buf);
+        assert!(matches!(result, Err(WireError::Validation(_))));
+    }
+
+    #[test]
+    fn length_exceeds_max_rejected() {
+        // kind=1, length=256
+        let mut buf = vec![1u8];
+        buf.extend_from_slice(&256u32.to_le_bytes());
+        buf.extend_from_slice(&[0u8; 256]);
+        let result = validate_user_headers(&buf);
+        assert!(matches!(result, Err(WireError::Validation(_))));
+    }
+
+    #[test]
+    fn kind_zero_rejected() {
+        // kind=0 is reserved
+        let mut buf = vec![0u8];
+        buf.extend_from_slice(&1u32.to_le_bytes());
+        buf.push(42);
+        let result = validate_user_headers(&buf);
+        assert!(matches!(result, Err(WireError::Validation(_))));
+    }
+
+    #[test]
+    fn trailing_bytes_rejected() {
+        let entries = [(1u8, b"k" as &[u8], 2u8, b"v" as &[u8])];
+        let mut encoded = encode_test_entries(&entries);
+        encoded.push(0xFF); // trailing garbage
+        let result = validate_user_headers(&encoded);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn odd_tlv_count_rejected() {
+        // One TLV entry (key without value) - odd count
+        let mut buf = vec![1u8];
+        buf.extend_from_slice(&2u32.to_le_bytes());
+        buf.extend_from_slice(b"ab");
+        let result = validate_user_headers(&buf);
+        assert!(matches!(result, Err(WireError::Validation(_))));
+    }
+
+    #[test]
+    fn encoded_size_matches_actual() {
+        let entries = [
+            (1u8, b"key1" as &[u8], 2u8, b"value1" as &[u8]),
+            (
+                6u8,
+                &100i32.to_le_bytes() as &[u8],
+                14u8,
+                &std::f32::consts::PI.to_le_bytes() as &[u8],
+            ),
+        ];
+        let expected_size = user_headers_encoded_size(&entries);
+        let encoded = encode_test_entries(&entries);
+        assert_eq!(expected_size, encoded.len());
+    }
+
+    #[test]
+    fn max_length_field_accepted() {
+        let data = [0xAA; 255];
+        let entries = [(1u8, &data[..], 1u8, &data[..])];
+        let encoded = encode_test_entries(&entries);
+        assert_eq!(validate_user_headers(&encoded).unwrap(), 1);
+    }
+
+    #[test]
+    fn wire_user_headers_empty() {
+        let wire = WireUserHeaders::empty();
+        assert!(wire.is_empty());
+        assert_eq!(wire.as_bytes(), &[]);
+        assert_eq!(wire.encoded_size(), 0);
+    }
+
+    #[test]
+    fn wire_user_headers_from_slice_round_trip() {
+        let entries = [(6u8, b"test" as &[u8], 2u8, b"val" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+
+        let wire = WireUserHeaders::from_slice(&encoded).unwrap();
+        assert!(!wire.is_empty());
+        assert_eq!(wire.as_bytes(), &encoded);
+        assert_eq!(wire.encoded_size(), encoded.len());
+
+        let entry = wire.iter().next().unwrap();
+        assert_eq!(entry.key_kind, WireHeaderKind(6));
+        assert_eq!(entry.key, b"test");
+    }
+
+    #[test]
+    fn wire_user_headers_from_bytes_zero_copy() {
+        let entries = [(1u8, b"k" as &[u8], 2u8, b"v" as &[u8])];
+        let encoded = Bytes::from(encode_test_entries(&entries));
+
+        let wire = WireUserHeaders::from_bytes(encoded.clone()).unwrap();
+        assert_eq!(wire.as_bytes(), &encoded[..]);
+        assert_eq!(wire.into_bytes(), encoded);
+    }
+
+    #[test]
+    fn wire_user_headers_rejects_invalid() {
+        let buf = [0u8, 1, 0, 0, 0, 42]; // kind=0 is invalid
+        assert!(WireUserHeaders::from_slice(&buf).is_err());
+    }
+
+    #[test]
+    fn wire_user_headers_encode() {
+        let entries = [(1u8, b"k" as &[u8], 2u8, b"v" as &[u8])];
+        let encoded = encode_test_entries(&entries);
+        let wire = WireUserHeaders::from_slice(&encoded).unwrap();
+
+        let mut buf = BytesMut::with_capacity(wire.encoded_size());
+        wire.encode(&mut buf);
+        assert_eq!(&buf[..], &encoded);
+    }
+}
diff --git a/core/cli/Cargo.toml b/core/cli/Cargo.toml
index 85694e31d..5fabc500d 100644
--- a/core/cli/Cargo.toml
+++ b/core/cli/Cargo.toml
@@ -44,7 +44,6 @@ default = ["login-session"]
 login-session = ["dep:keyring"]
 
 [dependencies]
-ahash = { workspace = true }
 anyhow = { workspace = true }
 async-trait = { workspace = true }
 bytes = { workspace = true }
@@ -55,6 +54,7 @@ comfy-table = { workspace = true }
 dirs = { workspace = true }
 figlet-rs = { workspace = true }
 iggy = { workspace = true }
+iggy_binary_protocol = { workspace = true }
 iggy_common = { workspace = true }
 keyring = { workspace = true, optional = true }
 passterm = { workspace = true }
diff --git a/core/cli/src/args/permissions/mod.rs b/core/cli/src/args/permissions/mod.rs
index 51fa073c5..8f692fdfa 100644
--- a/core/cli/src/args/permissions/mod.rs
+++ b/core/cli/src/args/permissions/mod.rs
@@ -17,9 +17,9 @@
  */
 
 use self::{global::GlobalPermissionsArg, stream::StreamPermissionsArg};
-use ahash::AHashMap;
 use clap::ValueEnum;
 use iggy::prelude::{Permissions, StreamPermissions, UserStatus};
+use std::collections::BTreeMap;
 
 pub(crate) mod constants;
 pub(crate) mod global;
@@ -49,7 +49,7 @@ impl From<PermissionsArgs> for Option<Permissions> {
             .stream
             .into_iter()
             .map(|s| (s.stream_id, s.into()))
-            .collect::<AHashMap<usize, StreamPermissions>>();
+            .collect::<BTreeMap<usize, StreamPermissions>>();
 
         match (value.global, stream_permissions.is_empty()) {
             (Some(global), true) => Some(Permissions {
@@ -114,7 +114,7 @@ mod tests {
             Option::from(PermissionsArgs::new(None, Some(vec![stream])));
 
         let permissions = Permissions {
-            streams: Some(AHashMap::from([(1, StreamPermissions::default())])),
+            streams: Some(BTreeMap::from([(1, StreamPermissions::default())])),
             ..Default::default()
         };
         assert_eq!(permissions_args, Some(permissions));
@@ -128,7 +128,7 @@ mod tests {
             Option::from(PermissionsArgs::new(Some(global), Some(vec![stream])));
 
         let mut permissions = Permissions {
-            streams: Some(AHashMap::from([(1, StreamPermissions::default())])),
+            streams: Some(BTreeMap::from([(1, StreamPermissions::default())])),
             ..Default::default()
         };
         permissions.global.manage_topics = true;
diff --git a/core/cli/src/args/permissions/stream.rs b/core/cli/src/args/permissions/stream.rs
index b371d8720..6769499f8 100644
--- a/core/cli/src/args/permissions/stream.rs
+++ b/core/cli/src/args/permissions/stream.rs
@@ -22,8 +22,8 @@ use super::constants::{
     READ_TOPICS_SHORT, SEND_MESSAGES_LONG, SEND_MESSAGES_SHORT,
 };
 use crate::args::permissions::topic::TopicPermissionsArg;
-use ahash::AHashMap;
 use iggy::prelude::StreamPermissions;
+use std::collections::BTreeMap;
 use std::str::FromStr;
 
 #[derive(Debug, PartialEq)]
@@ -84,7 +84,7 @@ impl StreamPermissionsArg {
         }
 
         if !topic_permissions.is_empty() {
-            let mut permissions = AHashMap::new();
+            let mut permissions = BTreeMap::new();
             for permission in topic_permissions {
                 permissions.insert(permission.topic_id, permission.permissions);
             }
@@ -382,7 +382,7 @@ mod tests {
                     read_topics: false,
                     poll_messages: false,
                     send_messages: false,
-                    topics: Some(AHashMap::from([
+                    topics: Some(BTreeMap::from([
                         (
                             2,
                             TopicPermissions {
@@ -417,7 +417,7 @@ mod tests {
                     read_topics: false,
                     poll_messages: false,
                     send_messages: false,
-                    topics: Some(AHashMap::from([
+                    topics: Some(BTreeMap::from([
                         (
                             2,
                             TopicPermissions {
@@ -515,7 +515,7 @@ mod tests {
                     read_topics: false,
                     poll_messages: false,
                     send_messages: false,
-                    topics: Some(AHashMap::from([
+                    topics: Some(BTreeMap::from([
                         (
                             2,
                             TopicPermissions {
diff --git a/core/cli/src/commands/binary_client/get_client.rs b/core/cli/src/commands/binary_client/get_client.rs
index 33667098e..0150638f6 100644
--- a/core/cli/src/commands/binary_client/get_client.rs
+++ b/core/cli/src/commands/binary_client/get_client.rs
@@ -21,40 +21,32 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::{Table, presets::ASCII_NO_BORDERS};
 use iggy_common::Client;
-use iggy_common::get_client::GetClient;
 use tracing::{Level, event};
 
 pub struct GetClientCmd {
-    get_client: GetClient,
+    client_id: u32,
 }
 
 impl GetClientCmd {
     pub fn new(client_id: u32) -> Self {
-        Self {
-            get_client: GetClient { client_id },
-        }
+        Self { client_id }
     }
 }
 
 #[async_trait]
 impl CliCommand for GetClientCmd {
     fn explain(&self) -> String {
-        format!("get client with ID: {}", self.get_client.client_id)
+        format!("get client with ID: {}", self.client_id)
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let client_details = client
-            .get_client(self.get_client.client_id)
+            .get_client(self.client_id)
             .await
-            .with_context(|| {
-                format!(
-                    "Problem getting client with ID: {}",
-                    self.get_client.client_id
-                )
-            })?;
+            .with_context(|| format!("Problem getting client with ID: {}", self.client_id))?;
 
         if client_details.is_none() {
-            event!(target: PRINT_TARGET, Level::INFO, "Client with ID: {} was not found", self.get_client.client_id);
+            event!(target: PRINT_TARGET, Level::INFO, "Client with ID: {} was not found", self.client_id);
             return Ok(());
         }
 
diff --git a/core/cli/src/commands/binary_client/get_clients.rs b/core/cli/src/commands/binary_client/get_clients.rs
index 02708e404..907c5c73f 100644
--- a/core/cli/src/commands/binary_client/get_clients.rs
+++ b/core/cli/src/commands/binary_client/get_clients.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_clients::GetClients;
 use tracing::{Level, event};
 
 pub enum GetClientsOutput {
@@ -30,23 +29,18 @@ pub enum GetClientsOutput {
 }
 
 pub struct GetClientsCmd {
-    _get_clients: GetClients,
     output: GetClientsOutput,
 }
 
 impl GetClientsCmd {
     pub fn new(output: GetClientsOutput) -> Self {
-        GetClientsCmd {
-            _get_clients: GetClients {},
-            output,
-        }
+        GetClientsCmd { output }
     }
 }
 
 impl Default for GetClientsCmd {
     fn default() -> Self {
         GetClientsCmd {
-            _get_clients: GetClients {},
             output: GetClientsOutput::Table,
         }
     }
diff --git a/core/cli/src/commands/binary_consumer_groups/get_consumer_group.rs b/core/cli/src/commands/binary_consumer_groups/get_consumer_group.rs
index 59f053e01..705dd1ee8 100644
--- a/core/cli/src/commands/binary_consumer_groups/get_consumer_group.rs
+++ b/core/cli/src/commands/binary_consumer_groups/get_consumer_group.rs
@@ -22,21 +22,20 @@ use async_trait::async_trait;
 use comfy_table::{Table, presets::ASCII_NO_BORDERS};
 use iggy_common::Client;
 use iggy_common::Identifier;
-use iggy_common::get_consumer_group::GetConsumerGroup;
 use tracing::{Level, event};
 
 pub struct GetConsumerGroupCmd {
-    get_consumer_group: GetConsumerGroup,
+    stream_id: Identifier,
+    topic_id: Identifier,
+    group_id: Identifier,
 }
 
 impl GetConsumerGroupCmd {
     pub fn new(stream_id: Identifier, topic_id: Identifier, consumer_group_id: Identifier) -> Self {
         Self {
-            get_consumer_group: GetConsumerGroup {
-                stream_id,
-                topic_id,
-                group_id: consumer_group_id,
-            },
+            stream_id,
+            topic_id,
+            group_id: consumer_group_id,
         }
     }
 }
@@ -46,25 +45,23 @@ impl CliCommand for GetConsumerGroupCmd {
     fn explain(&self) -> String {
         format!(
             "get consumer group with ID: {} for topic with ID: {} and stream with ID: {}",
-            self.get_consumer_group.group_id,
-            self.get_consumer_group.topic_id,
-            self.get_consumer_group.stream_id,
+            self.group_id, self.topic_id, self.stream_id,
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let consumer_group = client
-            .get_consumer_group(&self.get_consumer_group.stream_id, &self.get_consumer_group.topic_id, &self.get_consumer_group.group_id)
+            .get_consumer_group(&self.stream_id, &self.topic_id, &self.group_id)
             .await
             .with_context(|| {
                 format!(
                     "Problem getting consumer group with ID: {} for topic with ID: {} and stream with ID: {}",
-                    self.get_consumer_group.group_id, self.get_consumer_group.topic_id, self.get_consumer_group.stream_id
+                    self.group_id, self.topic_id, self.stream_id
                 )
             })?;
 
         if consumer_group.is_none() {
-            event!(target: PRINT_TARGET, Level::INFO, "Consumer group with ID: {} was not found", self.get_consumer_group.group_id);
+            event!(target: PRINT_TARGET, Level::INFO, "Consumer group with ID: {} was not found", self.group_id);
             return Ok(());
         }
 
diff --git a/core/cli/src/commands/binary_consumer_groups/get_consumer_groups.rs b/core/cli/src/commands/binary_consumer_groups/get_consumer_groups.rs
index 79362b88c..146a38cdb 100644
--- a/core/cli/src/commands/binary_consumer_groups/get_consumer_groups.rs
+++ b/core/cli/src/commands/binary_consumer_groups/get_consumer_groups.rs
@@ -22,7 +22,6 @@ use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
 use iggy_common::Identifier;
-use iggy_common::get_consumer_groups::GetConsumerGroups;
 use std::fmt::{self, Display, Formatter};
 use tracing::{Level, event};
 
@@ -43,7 +42,8 @@ impl Display for GetConsumerGroupsOutput {
 }
 
 pub struct GetConsumerGroupsCmd {
-    get_consumer_groups: GetConsumerGroups,
+    stream_id: Identifier,
+    topic_id: Identifier,
     output: GetConsumerGroupsOutput,
 }
 
@@ -54,10 +54,8 @@ impl GetConsumerGroupsCmd {
         output: GetConsumerGroupsOutput,
     ) -> Self {
         Self {
-            get_consumer_groups: GetConsumerGroups {
-                stream_id,
-                topic_id,
-            },
+            stream_id,
+            topic_id,
             output,
         }
     }
@@ -68,21 +66,18 @@ impl CliCommand for GetConsumerGroupsCmd {
     fn explain(&self) -> String {
         format!(
             "list consumer groups for stream with ID: {} and topic with ID: {} in {} mode",
-            self.get_consumer_groups.stream_id, self.get_consumer_groups.topic_id, self.output
+            self.stream_id, self.topic_id, self.output
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let consumer_groups = client
-            .get_consumer_groups(
-                &self.get_consumer_groups.stream_id,
-                &self.get_consumer_groups.topic_id,
-            )
+            .get_consumer_groups(&self.stream_id, &self.topic_id)
             .await
             .with_context(|| {
                 format!(
                     "Problem getting consumer groups for stream with ID: {} and topic with ID: {}",
-                    self.get_consumer_groups.stream_id, self.get_consumer_groups.topic_id
+                    self.stream_id, self.topic_id
                 )
             })?;
 
diff --git a/core/cli/src/commands/binary_consumer_offsets/get_consumer_offset.rs b/core/cli/src/commands/binary_consumer_offsets/get_consumer_offset.rs
index 7fb7066af..fb69ffb26 100644
--- a/core/cli/src/commands/binary_consumer_offsets/get_consumer_offset.rs
+++ b/core/cli/src/commands/binary_consumer_offsets/get_consumer_offset.rs
@@ -21,12 +21,14 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_consumer_offset::GetConsumerOffset;
 use iggy_common::{Consumer, ConsumerKind, Identifier};
 use tracing::{Level, event};
 
 pub struct GetConsumerOffsetCmd {
-    get_consumer_offset: GetConsumerOffset,
+    consumer: Consumer,
+    stream_id: Identifier,
+    topic_id: Identifier,
+    partition_id: u32,
 }
 
 impl GetConsumerOffsetCmd {
@@ -38,27 +40,22 @@ impl GetConsumerOffsetCmd {
         kind: ConsumerKind,
     ) -> Self {
         Self {
-            get_consumer_offset: GetConsumerOffset {
-                consumer: Consumer {
-                    kind,
-                    id: consumer_id,
-                },
-                stream_id,
-                topic_id,
-                partition_id: Some(partition_id),
+            consumer: Consumer {
+                kind,
+                id: consumer_id,
             },
+            stream_id,
+            topic_id,
+            partition_id,
         }
     }
 
     pub fn get_consumer_info(&self) -> String {
-        match self.get_consumer_offset.consumer.kind {
+        match self.consumer.kind {
             ConsumerKind::Consumer => {
-                format!("consumer with ID: {}", self.get_consumer_offset.consumer.id)
+                format!("consumer with ID: {}", self.consumer.id)
             }
-            ConsumerKind::ConsumerGroup => format!(
-                "consumer group with ID: {}",
-                self.get_consumer_offset.consumer.id
-            ),
+            ConsumerKind::ConsumerGroup => format!("consumer group with ID: {}", self.consumer.id),
         }
     }
 }
@@ -69,22 +66,22 @@ impl CliCommand for GetConsumerOffsetCmd {
         format!(
             "get consumer offset for {} for stream with ID: {} and topic with ID: {} and partition with ID: {}",
             self.get_consumer_info(),
-            self.get_consumer_offset.stream_id,
-            self.get_consumer_offset.topic_id,
-            self.get_consumer_offset.partition_id.unwrap(),
+            self.stream_id,
+            self.topic_id,
+            self.partition_id,
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
-        let consumer_offset = client.get_consumer_offset(&self.get_consumer_offset.consumer, &self.get_consumer_offset.stream_id, &self.get_consumer_offset.topic_id, self.get_consumer_offset.partition_id).await.with_context(|| {
+        let consumer_offset = client.get_consumer_offset(&self.consumer, &self.stream_id, &self.topic_id, Some(self.partition_id)).await.with_context(|| {
             format!(
                 "Problem getting consumer offset for {} for stream with ID: {} and topic with ID: {} and partition with ID: {}",
-                self.get_consumer_info(), self.get_consumer_offset.stream_id, self.get_consumer_offset.topic_id, self.get_consumer_offset.partition_id.unwrap()
+                self.get_consumer_info(), self.stream_id, self.topic_id, self.partition_id
             )
         })?;
 
         if consumer_offset.is_none() {
-            event!(target: PRINT_TARGET, Level::INFO, "Consumer offset for {} for stream with ID: {} and topic with ID: {} and partition with ID: {} was not found", self.get_consumer_info(), self.get_consumer_offset.stream_id, self.get_consumer_offset.topic_id, self.get_consumer_offset.partition_id.unwrap());
+            event!(target: PRINT_TARGET, Level::INFO, "Consumer offset for {} for stream with ID: {} and topic with ID: {} and partition with ID: {} was not found", self.get_consumer_info(), self.stream_id, self.topic_id, self.partition_id);
             return Ok(());
         }
 
@@ -94,16 +91,10 @@ impl CliCommand for GetConsumerOffsetCmd {
         table.set_header(vec!["Property", "Value"]);
         table.add_row(vec![
             "Consumer ID",
-            format!("{}", self.get_consumer_offset.consumer.id).as_str(),
-        ]);
-        table.add_row(vec![
-            "Stream ID",
-            format!("{}", self.get_consumer_offset.stream_id).as_str(),
-        ]);
-        table.add_row(vec![
-            "Topic ID",
-            format!("{}", self.get_consumer_offset.topic_id).as_str(),
+            format!("{}", self.consumer.id).as_str(),
         ]);
+        table.add_row(vec!["Stream ID", format!("{}", self.stream_id).as_str()]);
+        table.add_row(vec!["Topic ID", format!("{}", self.topic_id).as_str()]);
         table.add_row(vec![
             "Partition ID",
             format!("{}", consumer_offset.partition_id).as_str(),
diff --git a/core/cli/src/commands/binary_consumer_offsets/set_consumer_offset.rs b/core/cli/src/commands/binary_consumer_offsets/set_consumer_offset.rs
index e61eaaf96..21c18c6f6 100644
--- a/core/cli/src/commands/binary_consumer_offsets/set_consumer_offset.rs
+++ b/core/cli/src/commands/binary_consumer_offsets/set_consumer_offset.rs
@@ -21,12 +21,15 @@ use anyhow::Context;
 use async_trait::async_trait;
 use iggy_common::Client;
 use iggy_common::Identifier;
-use iggy_common::store_consumer_offset::StoreConsumerOffset;
 use iggy_common::{Consumer, ConsumerKind};
 use tracing::{Level, event};
 
 pub struct SetConsumerOffsetCmd {
-    set_consumer_offset: StoreConsumerOffset,
+    consumer: Consumer,
+    stream_id: Identifier,
+    topic_id: Identifier,
+    partition_id: u32,
+    offset: u64,
 }
 
 impl SetConsumerOffsetCmd {
@@ -39,16 +42,14 @@ impl SetConsumerOffsetCmd {
         kind: ConsumerKind,
     ) -> Self {
         Self {
-            set_consumer_offset: StoreConsumerOffset {
-                consumer: Consumer {
-                    kind,
-                    id: consumer_id,
-                },
-                stream_id,
-                topic_id,
-                partition_id: Some(partition_id),
-                offset,
+            consumer: Consumer {
+                kind,
+                id: consumer_id,
             },
+            stream_id,
+            topic_id,
+            partition_id,
+            offset,
         }
     }
 }
@@ -58,32 +59,28 @@ impl CliCommand for SetConsumerOffsetCmd {
     fn explain(&self) -> String {
         format!(
             "set consumer offset for consumer with ID: {} for stream with ID: {} and topic with ID: {} and partition with ID: {} to {}",
-            self.set_consumer_offset.consumer.id,
-            self.set_consumer_offset.stream_id,
-            self.set_consumer_offset.topic_id,
-            self.set_consumer_offset.partition_id.unwrap(),
-            self.set_consumer_offset.offset,
+            self.consumer.id, self.stream_id, self.topic_id, self.partition_id, self.offset,
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         client
-            .store_consumer_offset(&self.set_consumer_offset.consumer, &self.set_consumer_offset.stream_id, &self.set_consumer_offset.topic_id, self.set_consumer_offset.partition_id, self.set_consumer_offset.offset)
+            .store_consumer_offset(&self.consumer, &self.stream_id, &self.topic_id, Some(self.partition_id), self.offset)
             .await
             .with_context(|| {
                 format!(
                     "Problem setting consumer offset for consumer with ID: {} for stream with ID: {} and topic with ID: {} and partition with ID: {}",
-                    self.set_consumer_offset.consumer.id, self.set_consumer_offset.stream_id, self.set_consumer_offset.topic_id, self.set_consumer_offset.partition_id.unwrap()
+                    self.consumer.id, self.stream_id, self.topic_id, self.partition_id
                 )
             })?;
 
         event!(target: PRINT_TARGET, Level::INFO,
             "Consumer offset for consumer with ID: {} for stream with ID: {} and topic with ID: {} and partition with ID: {} set to {}",
-            self.set_consumer_offset.consumer.id,
-            self.set_consumer_offset.stream_id,
-            self.set_consumer_offset.topic_id,
-            self.set_consumer_offset.partition_id.unwrap(),
-            self.set_consumer_offset.offset,
+            self.consumer.id,
+            self.stream_id,
+            self.topic_id,
+            self.partition_id,
+            self.offset,
         );
 
         Ok(())
diff --git a/core/cli/src/commands/binary_message/poll_messages.rs b/core/cli/src/commands/binary_message/poll_messages.rs
index 3cb4c4abe..3274bb943 100644
--- a/core/cli/src/commands/binary_message/poll_messages.rs
+++ b/core/cli/src/commands/binary_message/poll_messages.rs
@@ -20,12 +20,14 @@ use crate::commands::cli_command::{CliCommand, PRINT_TARGET};
 use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::{Cell, CellAlignment, Row, Table};
+use iggy_binary_protocol::WireUserHeaders;
 use iggy_common::Client;
 use iggy_common::{
-    BytesSerializable, Consumer, HeaderKey, HeaderKind, HeaderValue, Identifier, IggyByteSize,
-    IggyDuration, IggyMessage, IggyTimestamp, PollMessages, PollingStrategy, Sizeable,
+    Consumer, HeaderKey, HeaderKind, Identifier, IggyByteSize, IggyDuration, IggyMessage,
+    IggyTimestamp, PollMessages, PollingStrategy, Sizeable,
+    wire_conversions::user_headers_from_wire,
 };
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 use tokio::io::AsyncWriteExt;
 use tracing::{Level, event};
 
@@ -85,7 +87,10 @@ impl PollMessagesCmd {
             .iter()
             .flat_map(|m| {
                 if let Some(user_headers) = &m.user_headers {
-                    match HashMap::<HeaderKey, HeaderValue>::from_bytes(user_headers.clone()) {
+                    match WireUserHeaders::from_bytes(user_headers.clone())
+                        .map_err(|_| iggy_common::IggyError::InvalidHeaderKey)
+                        .and_then(|w| user_headers_from_wire(&w))
+                    {
                         Ok(headers) => headers
                             .iter()
                             .map(|(k, v)| (k.clone(), v.kind()))
diff --git a/core/cli/src/commands/binary_message/send_messages.rs b/core/cli/src/commands/binary_message/send_messages.rs
index 9dbdcbe22..99d40ebda 100644
--- a/core/cli/src/commands/binary_message/send_messages.rs
+++ b/core/cli/src/commands/binary_message/send_messages.rs
@@ -21,10 +21,8 @@ use anyhow::Context;
 use async_trait::async_trait;
 use bytes::Bytes;
 use iggy_common::Client;
-use iggy_common::{
-    BytesSerializable, HeaderKey, HeaderValue, Identifier, IggyMessage, Partitioning, Sizeable,
-};
-use std::collections::HashMap;
+use iggy_common::{HeaderKey, HeaderValue, Identifier, IggyMessage, Partitioning, Sizeable};
+use std::collections::BTreeMap;
 use std::io::{self, Read};
 use tokio::io::AsyncReadExt;
 use tracing::{Level, event};
@@ -75,7 +73,7 @@ impl SendMessagesCmd {
         Ok(buffer)
     }
 
-    fn get_headers(&self) -> Option<HashMap<HeaderKey, HeaderValue>> {
+    fn get_headers(&self) -> Option<BTreeMap<HeaderKey, HeaderValue>> {
         match self.headers.len() {
             0 => None,
             _ => Some(self.headers.iter().cloned().collect()),
diff --git a/core/cli/src/commands/binary_personal_access_tokens/get_personal_access_tokens.rs b/core/cli/src/commands/binary_personal_access_tokens/get_personal_access_tokens.rs
index 53a5dc9fc..da058a6f6 100644
--- a/core/cli/src/commands/binary_personal_access_tokens/get_personal_access_tokens.rs
+++ b/core/cli/src/commands/binary_personal_access_tokens/get_personal_access_tokens.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_personal_access_tokens::GetPersonalAccessTokens;
 use tracing::{Level, event};
 
 pub enum GetPersonalAccessTokensOutput {
@@ -30,16 +29,12 @@ pub enum GetPersonalAccessTokensOutput {
 }
 
 pub struct GetPersonalAccessTokensCmd {
-    _get_tokens: GetPersonalAccessTokens,
     output: GetPersonalAccessTokensOutput,
 }
 
 impl GetPersonalAccessTokensCmd {
     pub fn new(output: GetPersonalAccessTokensOutput) -> Self {
-        Self {
-            _get_tokens: GetPersonalAccessTokens {},
-            output,
-        }
+        Self { output }
     }
 }
 
diff --git a/core/cli/src/commands/binary_streams/get_stream.rs b/core/cli/src/commands/binary_streams/get_stream.rs
index 6e22dd2da..18df52611 100644
--- a/core/cli/src/commands/binary_streams/get_stream.rs
+++ b/core/cli/src/commands/binary_streams/get_stream.rs
@@ -22,40 +22,32 @@ use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
 use iggy_common::Identifier;
-use iggy_common::get_stream::GetStream;
 use tracing::{Level, event};
 
 pub struct GetStreamCmd {
-    get_stream: GetStream,
+    stream_id: Identifier,
 }
 
 impl GetStreamCmd {
     pub fn new(stream_id: Identifier) -> Self {
-        Self {
-            get_stream: GetStream { stream_id },
-        }
+        Self { stream_id }
     }
 }
 
 #[async_trait]
 impl CliCommand for GetStreamCmd {
     fn explain(&self) -> String {
-        format!("get stream with ID: {}", self.get_stream.stream_id)
+        format!("get stream with ID: {}", self.stream_id)
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let stream = client
-            .get_stream(&self.get_stream.stream_id)
+            .get_stream(&self.stream_id)
             .await
-            .with_context(|| {
-                format!(
-                    "Problem getting stream with ID: {}",
-                    self.get_stream.stream_id
-                )
-            })?;
+            .with_context(|| format!("Problem getting stream with ID: {}", self.stream_id))?;
 
         if stream.is_none() {
-            event!(target: PRINT_TARGET, Level::INFO, "Stream with ID: {} was not found", self.get_stream.stream_id);
+            event!(target: PRINT_TARGET, Level::INFO, "Stream with ID: {} was not found", self.stream_id);
             return Ok(());
         }
 
diff --git a/core/cli/src/commands/binary_streams/get_streams.rs b/core/cli/src/commands/binary_streams/get_streams.rs
index 55c785ed1..4ff5c7ad8 100644
--- a/core/cli/src/commands/binary_streams/get_streams.rs
+++ b/core/cli/src/commands/binary_streams/get_streams.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_streams::GetStreams;
 use tracing::{Level, event};
 
 pub enum GetStreamsOutput {
@@ -30,23 +29,18 @@ pub enum GetStreamsOutput {
 }
 
 pub struct GetStreamsCmd {
-    _get_streams: GetStreams,
     output: GetStreamsOutput,
 }
 
 impl GetStreamsCmd {
     pub fn new(output: GetStreamsOutput) -> Self {
-        GetStreamsCmd {
-            _get_streams: GetStreams {},
-            output,
-        }
+        GetStreamsCmd { output }
     }
 }
 
 impl Default for GetStreamsCmd {
     fn default() -> Self {
         GetStreamsCmd {
-            _get_streams: GetStreams {},
             output: GetStreamsOutput::Table,
         }
     }
diff --git a/core/cli/src/commands/binary_system/me.rs b/core/cli/src/commands/binary_system/me.rs
index ad3834277..522a6c468 100644
--- a/core/cli/src/commands/binary_system/me.rs
+++ b/core/cli/src/commands/binary_system/me.rs
@@ -21,22 +21,14 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_me::GetMe;
 use tracing::{Level, event};
 
-pub struct GetMeCmd {
-    _get_me: GetMe,
-}
+#[derive(Default)]
+pub struct GetMeCmd;
 
 impl GetMeCmd {
     pub fn new() -> Self {
-        Self::default()
-    }
-}
-
-impl Default for GetMeCmd {
-    fn default() -> Self {
-        Self { _get_me: GetMe {} }
+        Self
     }
 }
 
diff --git a/core/cli/src/commands/binary_system/ping.rs b/core/cli/src/commands/binary_system/ping.rs
index 990d72ad6..0552994f4 100644
--- a/core/cli/src/commands/binary_system/ping.rs
+++ b/core/cli/src/commands/binary_system/ping.rs
@@ -20,23 +20,18 @@ use crate::commands::cli_command::{CliCommand, PRINT_TARGET};
 use anyhow::Context;
 use async_trait::async_trait;
 use iggy_common::Client;
-use iggy_common::ping::Ping;
 use std::fmt::{Display, Formatter, Result};
 use std::time::Duration;
 use tokio::time::{Instant, sleep};
 use tracing::{Level, event};
 
 pub struct PingCmd {
-    _ping: Ping,
     count: u32,
 }
 
 impl PingCmd {
     pub fn new(count: u32) -> Self {
-        Self {
-            _ping: Ping {},
-            count,
-        }
+        Self { count }
     }
 }
 
diff --git a/core/cli/src/commands/binary_system/snapshot.rs b/core/cli/src/commands/binary_system/snapshot.rs
index b0178f111..4c4ef999a 100644
--- a/core/cli/src/commands/binary_system/snapshot.rs
+++ b/core/cli/src/commands/binary_system/snapshot.rs
@@ -23,13 +23,13 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_snapshot::GetSnapshot;
 use iggy_common::{SnapshotCompression, SystemSnapshotType};
 use tokio::io::AsyncWriteExt;
 use tracing::{Level, event};
 
 pub struct GetSnapshotCmd {
-    _get_snapshot: GetSnapshot,
+    compression: SnapshotCompression,
+    snapshot_types: Vec<SystemSnapshotType>,
     out_dir: String,
 }
 
@@ -39,28 +39,23 @@ impl GetSnapshotCmd {
         snapshot_types: Option<Vec<SystemSnapshotType>>,
         out_dir: Option<String>,
     ) -> Self {
-        let mut cmd = GetSnapshotCmd::default();
-
-        if let Some(compress) = compression {
-            cmd._get_snapshot.compression = compress;
-        }
-        if let Some(types) = snapshot_types {
-            cmd._get_snapshot.snapshot_types = types
-        }
-        if let Some(out) = out_dir {
-            cmd.out_dir = out
+        let default_types = vec![
+            SystemSnapshotType::FilesystemOverview,
+            SystemSnapshotType::ProcessList,
+            SystemSnapshotType::ResourceUsage,
+            SystemSnapshotType::ServerLogs,
+        ];
+        Self {
+            compression: compression.unwrap_or(SnapshotCompression::Deflated),
+            snapshot_types: snapshot_types.unwrap_or(default_types),
+            out_dir: out_dir.unwrap_or_else(|| ".".to_string()),
         }
-
-        cmd
     }
 }
 
 impl Default for GetSnapshotCmd {
     fn default() -> Self {
-        Self {
-            _get_snapshot: GetSnapshot::default(),
-            out_dir: ".".to_string(),
-        }
+        Self::new(None, None, None)
     }
 }
 
@@ -72,10 +67,7 @@ impl CliCommand for GetSnapshotCmd {
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let snapshot_data = client
-            .snapshot(
-                self._get_snapshot.compression,
-                self._get_snapshot.snapshot_types.to_owned(),
-            )
+            .snapshot(self.compression, self.snapshot_types.to_owned())
             .await
             .with_context(|| "Problem sending snapshot command".to_owned())?;
         let file_path = Path::new(&self.out_dir).join(format!(
diff --git a/core/cli/src/commands/binary_system/stats.rs b/core/cli/src/commands/binary_system/stats.rs
index c63e6ef48..e73959808 100644
--- a/core/cli/src/commands/binary_system/stats.rs
+++ b/core/cli/src/commands/binary_system/stats.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_stats::GetStats;
 use std::fmt::Display;
 use std::time::SystemTime;
 use tracing::{Level, event};
@@ -48,16 +47,11 @@ impl Display for GetStatsOutput {
 pub struct GetStatsCmd {
     quiet_mode: bool,
     output: GetStatsOutput,
-    _get_stats: GetStats,
 }
 
 impl GetStatsCmd {
     pub fn new(quiet_mode: bool, output: GetStatsOutput) -> Self {
-        Self {
-            quiet_mode,
-            output,
-            _get_stats: GetStats {},
-        }
+        Self { quiet_mode, output }
     }
 }
 
diff --git a/core/cli/src/commands/binary_topics/get_topic.rs b/core/cli/src/commands/binary_topics/get_topic.rs
index 570fcc72b..6443b2d0d 100644
--- a/core/cli/src/commands/binary_topics/get_topic.rs
+++ b/core/cli/src/commands/binary_topics/get_topic.rs
@@ -23,20 +23,18 @@ use comfy_table::Table;
 use iggy_common::Client;
 use iggy_common::Identifier;
 use iggy_common::IggyExpiry;
-use iggy_common::get_topic::GetTopic;
 use tracing::{Level, event};
 
 pub struct GetTopicCmd {
-    get_topic: GetTopic,
+    stream_id: Identifier,
+    topic_id: Identifier,
 }
 
 impl GetTopicCmd {
     pub fn new(stream_id: Identifier, topic_id: Identifier) -> Self {
         Self {
-            get_topic: GetTopic {
-                stream_id,
-                topic_id,
-            },
+            stream_id,
+            topic_id,
         }
     }
 }
@@ -46,23 +44,23 @@ impl CliCommand for GetTopicCmd {
     fn explain(&self) -> String {
         format!(
             "get topic with ID: {} from stream with ID: {}",
-            self.get_topic.topic_id, self.get_topic.stream_id
+            self.topic_id, self.stream_id
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let topic = client
-            .get_topic(&self.get_topic.stream_id, &self.get_topic.topic_id)
+            .get_topic(&self.stream_id, &self.topic_id)
             .await
             .with_context(|| {
                 format!(
                     "Problem getting topic with ID: {} in stream {}",
-                    self.get_topic.topic_id, self.get_topic.stream_id
+                    self.topic_id, self.stream_id
                 )
             })?;
 
         if topic.is_none() {
-            event!(target: PRINT_TARGET, Level::INFO, "Topic with ID: {} in stream {} was not found", self.get_topic.topic_id, self.get_topic.stream_id);
+            event!(target: PRINT_TARGET, Level::INFO, "Topic with ID: {} in stream {} was not found", self.topic_id, self.stream_id);
             return Ok(());
         }
 
diff --git a/core/cli/src/commands/binary_topics/get_topics.rs b/core/cli/src/commands/binary_topics/get_topics.rs
index a4812f376..ff8d4c4e3 100644
--- a/core/cli/src/commands/binary_topics/get_topics.rs
+++ b/core/cli/src/commands/binary_topics/get_topics.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_topics::GetTopics;
 use iggy_common::{Identifier, IggyExpiry};
 use std::fmt::{self, Display, Formatter};
 use tracing::{Level, event};
@@ -43,16 +42,13 @@ impl Display for GetTopicsOutput {
 }
 
 pub struct GetTopicsCmd {
-    get_topics: GetTopics,
+    stream_id: Identifier,
     output: GetTopicsOutput,
 }
 
 impl GetTopicsCmd {
     pub fn new(stream_id: Identifier, output: GetTopicsOutput) -> Self {
-        Self {
-            get_topics: GetTopics { stream_id },
-            output,
-        }
+        Self { stream_id, output }
     }
 }
 
@@ -61,20 +57,15 @@ impl CliCommand for GetTopicsCmd {
     fn explain(&self) -> String {
         format!(
             "list topics from stream with ID: {} in {} mode",
-            self.get_topics.stream_id, self.output
+            self.stream_id, self.output
         )
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let topics = client
-            .get_topics(&self.get_topics.stream_id)
+            .get_topics(&self.stream_id)
             .await
-            .with_context(|| {
-                format!(
-                    "Problem getting topics from stream {}",
-                    self.get_topics.stream_id
-                )
-            })?;
+            .with_context(|| format!("Problem getting topics from stream {}", self.stream_id))?;
 
         match self.output {
             GetTopicsOutput::Table => {
diff --git a/core/cli/src/commands/binary_users/get_user.rs b/core/cli/src/commands/binary_users/get_user.rs
index b68e2a9ec..91c37ca7d 100644
--- a/core/cli/src/commands/binary_users/get_user.rs
+++ b/core/cli/src/commands/binary_users/get_user.rs
@@ -22,39 +22,36 @@ use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
 use iggy_common::Identifier;
-use iggy_common::get_user::GetUser;
 use tracing::{Level, event};
 
 pub struct GetUserCmd {
-    get_user: GetUser,
+    user_id: Identifier,
 }
 
 impl GetUserCmd {
     pub fn new(user_id: Identifier) -> Self {
-        Self {
-            get_user: GetUser { user_id },
-        }
+        Self { user_id }
     }
 }
 
 #[async_trait]
 impl CliCommand for GetUserCmd {
     fn explain(&self) -> String {
-        format!("get user with ID: {}", self.get_user.user_id)
+        format!("get user with ID: {}", self.user_id)
     }
 
     async fn execute_cmd(&mut self, client: &dyn Client) -> anyhow::Result<(), anyhow::Error> {
         let user = client
-            .get_user(&self.get_user.user_id)
+            .get_user(&self.user_id)
             .await
-            .with_context(|| format!("Problem getting user with ID: {}", self.get_user.user_id))?;
+            .with_context(|| format!("Problem getting user with ID: {}", self.user_id))?;
 
         if user.is_none() {
             event!(
                 target: PRINT_TARGET,
                 Level::INFO,
                 "User with ID: {} was not found",
-                self.get_user.user_id
+                self.user_id
             );
             return Ok(());
         }
diff --git a/core/cli/src/commands/binary_users/get_users.rs b/core/cli/src/commands/binary_users/get_users.rs
index cfb55407f..d60b23596 100644
--- a/core/cli/src/commands/binary_users/get_users.rs
+++ b/core/cli/src/commands/binary_users/get_users.rs
@@ -21,7 +21,6 @@ use anyhow::Context;
 use async_trait::async_trait;
 use comfy_table::Table;
 use iggy_common::Client;
-use iggy_common::get_users::GetUsers;
 use tracing::{Level, event};
 
 pub enum GetUsersOutput {
@@ -30,23 +29,18 @@ pub enum GetUsersOutput {
 }
 
 pub struct GetUsersCmd {
-    _get_users: GetUsers,
     output: GetUsersOutput,
 }
 
 impl GetUsersCmd {
     pub fn new(output: GetUsersOutput) -> Self {
-        GetUsersCmd {
-            _get_users: GetUsers {},
-            output,
-        }
+        GetUsersCmd { output }
     }
 }
 
 impl Default for GetUsersCmd {
     fn default() -> Self {
         GetUsersCmd {
-            _get_users: GetUsers {},
             output: GetUsersOutput::Table,
         }
     }
diff --git a/core/common/Cargo.toml b/core/common/Cargo.toml
index 3a89f6cd3..e600fc4b0 100644
--- a/core/common/Cargo.toml
+++ b/core/common/Cargo.toml
@@ -29,7 +29,6 @@ readme = "../../README.md"
 
 [dependencies]
 aes-gcm = { workspace = true }
-ahash = { workspace = true }
 aligned-vec = { workspace = true }
 async-broadcast = { workspace = true }
 async-trait = { workspace = true }
diff --git a/core/common/src/commands/consumer_groups/create_consumer_group.rs b/core/common/src/commands/consumer_groups/create_consumer_group.rs
deleted file mode 100644
index f697bc9c7..000000000
--- a/core/common/src/commands/consumer_groups/create_consumer_group.rs
+++ /dev/null
@@ -1,189 +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 super::MAX_NAME_LENGTH;
-use crate::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{CREATE_CONSUMER_GROUP_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `CreateConsumerGroup` command creates a new consumer group for the topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `name` - unique consumer group name, max length is 255 characters.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct CreateConsumerGroup {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique consumer group name, max length is 255 characters.
-    pub name: String,
-}
-
-impl Command for CreateConsumerGroup {
-    fn code(&self) -> u32 {
-        CREATE_CONSUMER_GROUP_CODE
-    }
-}
-
-impl Default for CreateConsumerGroup {
-    fn default() -> Self {
-        CreateConsumerGroup {
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            name: "consumer_group_1".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreateConsumerGroup {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
-            return Err(IggyError::InvalidConsumerGroupName);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreateConsumerGroup {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            1 + stream_id_bytes.len() + topic_id_bytes.len() + self.name.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<CreateConsumerGroup, IggyError> {
-        if bytes.len() < 6 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let name_length = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        let name = from_utf8(
-            bytes
-                .get(position + 1..position + 1 + name_length as usize)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-        let command = CreateConsumerGroup {
-            stream_id,
-            topic_id,
-            name,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for CreateConsumerGroup {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.name)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreateConsumerGroup {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            name: "test".to_string(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-
-        let name_length = bytes[position];
-        let name = from_utf8(&bytes[position + 1..position + 1 + name_length as usize]).unwrap();
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(CreateConsumerGroup::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = CreateConsumerGroup::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                CreateConsumerGroup::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let name = "test".to_string();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes =
-            BytesMut::with_capacity(1 + stream_id_bytes.len() + topic_id_bytes.len() + name.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-        let command = CreateConsumerGroup::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.name, name);
-    }
-}
diff --git a/core/common/src/commands/consumer_groups/delete_consumer_group.rs b/core/common/src/commands/consumer_groups/delete_consumer_group.rs
deleted file mode 100644
index 859ded6f9..000000000
--- a/core/common/src/commands/consumer_groups/delete_consumer_group.rs
+++ /dev/null
@@ -1,147 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_CONSUMER_GROUP_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteConsumerGroup` command deletes an existing consumer group from the topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `group_id` - unique consumer group ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct DeleteConsumerGroup {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique consumer group ID (numeric or name).
-    #[serde(skip)]
-    pub group_id: Identifier,
-}
-
-impl Command for DeleteConsumerGroup {
-    fn code(&self) -> u32 {
-        DELETE_CONSUMER_GROUP_CODE
-    }
-}
-
-impl Validatable<IggyError> for DeleteConsumerGroup {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteConsumerGroup {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let group_id_bytes = self.group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<DeleteConsumerGroup, IggyError> {
-        if bytes.len() < 9 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = DeleteConsumerGroup {
-            stream_id,
-            topic_id,
-            group_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteConsumerGroup {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.group_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteConsumerGroup {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            group_id: Identifier::numeric(3).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(group_id, command.group_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let group_id = Identifier::numeric(3).unwrap();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let group_id_bytes = group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        let command = DeleteConsumerGroup::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.group_id, group_id);
-    }
-}
diff --git a/core/common/src/commands/consumer_groups/get_consumer_group.rs b/core/common/src/commands/consumer_groups/get_consumer_group.rs
deleted file mode 100644
index 6febd5393..000000000
--- a/core/common/src/commands/consumer_groups/get_consumer_group.rs
+++ /dev/null
@@ -1,147 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_CONSUMER_GROUP_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetConsumerGroup` command retrieves the consumer group from the topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `group_id` - unique consumer group ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetConsumerGroup {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique consumer group ID (numeric or name).
-    #[serde(skip)]
-    pub group_id: Identifier,
-}
-
-impl Command for GetConsumerGroup {
-    fn code(&self) -> u32 {
-        GET_CONSUMER_GROUP_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetConsumerGroup {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetConsumerGroup {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let group_id_bytes = self.group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetConsumerGroup, IggyError> {
-        if bytes.len() < 9 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = GetConsumerGroup {
-            stream_id,
-            topic_id,
-            group_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for GetConsumerGroup {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.group_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetConsumerGroup {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            group_id: Identifier::numeric(3).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(group_id, command.group_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let group_id = Identifier::numeric(3).unwrap();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let group_id_bytes = group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        let command = GetConsumerGroup::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.group_id, group_id);
-    }
-}
diff --git a/core/common/src/commands/consumer_groups/get_consumer_groups.rs b/core/common/src/commands/consumer_groups/get_consumer_groups.rs
deleted file mode 100644
index d2f64293b..000000000
--- a/core/common/src/commands/consumer_groups/get_consumer_groups.rs
+++ /dev/null
@@ -1,126 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_CONSUMER_GROUPS_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetConsumerGroups` command retrieves the consumer groups from the topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetConsumerGroups {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-}
-
-impl Command for GetConsumerGroups {
-    fn code(&self) -> u32 {
-        GET_CONSUMER_GROUPS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetConsumerGroups {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetConsumerGroups {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetConsumerGroups, IggyError> {
-        if bytes.len() < 6 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = GetConsumerGroups {
-            stream_id,
-            topic_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for GetConsumerGroups {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.stream_id, self.topic_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use bytes::BufMut;
-
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetConsumerGroups {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let mut bytes = BytesMut::new();
-        bytes.put(stream_id.to_bytes());
-        bytes.put(topic_id.to_bytes());
-        let command = GetConsumerGroups::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-    }
-}
diff --git a/core/common/src/commands/consumer_groups/join_consumer_group.rs b/core/common/src/commands/consumer_groups/join_consumer_group.rs
deleted file mode 100644
index 5385373e6..000000000
--- a/core/common/src/commands/consumer_groups/join_consumer_group.rs
+++ /dev/null
@@ -1,147 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, JOIN_CONSUMER_GROUP_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `JoinConsumerGroup` command joins the consumer group by currently authenticated user.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `group_id` - unique consumer group ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct JoinConsumerGroup {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique consumer group ID (numeric or name).
-    #[serde(skip)]
-    pub group_id: Identifier,
-}
-
-impl Command for JoinConsumerGroup {
-    fn code(&self) -> u32 {
-        JOIN_CONSUMER_GROUP_CODE
-    }
-}
-
-impl Validatable<IggyError> for JoinConsumerGroup {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for JoinConsumerGroup {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let group_id_bytes = self.group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<JoinConsumerGroup, IggyError> {
-        if bytes.len() < 9 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = JoinConsumerGroup {
-            stream_id,
-            topic_id,
-            group_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for JoinConsumerGroup {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.group_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = JoinConsumerGroup {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            group_id: Identifier::numeric(3).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(group_id, command.group_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let group_id = Identifier::numeric(3).unwrap();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let group_id_bytes = group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        let command = JoinConsumerGroup::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.group_id, group_id);
-    }
-}
diff --git a/core/common/src/commands/consumer_groups/leave_consumer_group.rs b/core/common/src/commands/consumer_groups/leave_consumer_group.rs
deleted file mode 100644
index ff22829dc..000000000
--- a/core/common/src/commands/consumer_groups/leave_consumer_group.rs
+++ /dev/null
@@ -1,147 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, LEAVE_CONSUMER_GROUP_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `LeaveConsumerGroup` command leaves the consumer group by currently authenticated user.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `group_id` - unique consumer group ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct LeaveConsumerGroup {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique consumer group ID (numeric or name).
-    #[serde(skip)]
-    pub group_id: Identifier,
-}
-
-impl Command for LeaveConsumerGroup {
-    fn code(&self) -> u32 {
-        LEAVE_CONSUMER_GROUP_CODE
-    }
-}
-
-impl Validatable<IggyError> for LeaveConsumerGroup {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for LeaveConsumerGroup {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let group_id_bytes = self.group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<LeaveConsumerGroup, IggyError> {
-        if bytes.len() < 9 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = LeaveConsumerGroup {
-            stream_id,
-            topic_id,
-            group_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for LeaveConsumerGroup {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}|{}", self.stream_id, self.topic_id, self.group_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = LeaveConsumerGroup {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            group_id: Identifier::numeric(3).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let group_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(group_id, command.group_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let group_id = Identifier::numeric(3).unwrap();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let group_id_bytes = group_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            stream_id_bytes.len() + topic_id_bytes.len() + group_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_slice(&group_id_bytes);
-        let command = LeaveConsumerGroup::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.group_id, group_id);
-    }
-}
diff --git a/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs b/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs
deleted file mode 100644
index b0fc12aa2..000000000
--- a/core/common/src/commands/consumer_offsets/delete_consumer_offset.rs
+++ /dev/null
@@ -1,220 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_CONSUMER_OFFSET_CODE};
-use crate::{Consumer, ConsumerKind};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteConsumerOffset` command deletes the offset of a consumer from a given partition on the server.
-/// It has additional payload:
-/// - `consumer` - the consumer that is deleting the offset, either the regular consumer or the consumer group.
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partition_id` - partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct DeleteConsumerOffset {
-    /// The consumer that is storing the offset, either the regular consumer or the consumer group.
-    #[serde(skip)]
-    pub consumer: Consumer,
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-    pub partition_id: Option<u32>,
-}
-
-impl Default for DeleteConsumerOffset {
-    fn default() -> Self {
-        DeleteConsumerOffset {
-            consumer: Consumer::default(),
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partition_id: Some(0),
-        }
-    }
-}
-
-impl Command for DeleteConsumerOffset {
-    fn code(&self) -> u32 {
-        DELETE_CONSUMER_OFFSET_CODE
-    }
-}
-
-impl Validatable<IggyError> for DeleteConsumerOffset {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteConsumerOffset {
-    fn to_bytes(&self) -> Bytes {
-        let consumer_bytes = self.consumer.to_bytes();
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        // Encode partition_id with a flag byte: 1 = Some, 0 = None
-        if let Some(partition_id) = self.partition_id {
-            bytes.put_u8(1);
-            bytes.put_u32_le(partition_id);
-        } else {
-            bytes.put_u8(0);
-            bytes.put_u32_le(0); // Padding to keep structure consistent
-        }
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<DeleteConsumerOffset, IggyError> {
-        if bytes.len() < 16 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let consumer_kind =
-            ConsumerKind::from_code(*bytes.first().ok_or(IggyError::InvalidCommand)?)?;
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..))?;
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        let partition_id_value = u32::from_le_bytes(
-            bytes
-                .get(position + 1..position + 5)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id_value)
-        } else {
-            None
-        };
-        let command = DeleteConsumerOffset {
-            consumer,
-            stream_id,
-            topic_id,
-            partition_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteConsumerOffset {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}",
-            self.consumer,
-            self.stream_id,
-            self.topic_id,
-            self.partition_id.unwrap_or(0),
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteConsumerOffset {
-            consumer: Consumer::new(Identifier::numeric(1).unwrap()),
-            stream_id: Identifier::numeric(2).unwrap(),
-            topic_id: Identifier::numeric(3).unwrap(),
-            partition_id: Some(4),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let consumer_kind = ConsumerKind::from_code(bytes[0]).unwrap();
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..)).unwrap();
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = bytes[position];
-        let partition_id =
-            u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id)
-        } else {
-            None
-        };
-
-        assert!(!bytes.is_empty());
-        assert_eq!(consumer, command.consumer);
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let consumer = Consumer::new(Identifier::numeric(1).unwrap());
-        let stream_id = Identifier::numeric(2).unwrap();
-        let topic_id = Identifier::numeric(3).unwrap();
-        let partition_id = 4u32;
-
-        let consumer_bytes = consumer.to_bytes();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u8(1); // Flag: partition_id is Some
-        bytes.put_u32_le(partition_id);
-
-        let command = DeleteConsumerOffset::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.consumer, consumer);
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partition_id, Some(partition_id));
-    }
-}
diff --git a/core/common/src/commands/consumer_offsets/get_consumer_offset.rs b/core/common/src/commands/consumer_offsets/get_consumer_offset.rs
deleted file mode 100644
index f27624948..000000000
--- a/core/common/src/commands/consumer_offsets/get_consumer_offset.rs
+++ /dev/null
@@ -1,225 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_CONSUMER_OFFSET_CODE};
-use crate::{Consumer, ConsumerKind};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetConsumerOffset` command that retrieves the offset of a consumer for a given partition from the server.
-/// It has additional payload:
-/// - `consumer` - the consumer that is storing the offset, either the regular consumer or the consumer group.
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partition_id` - partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct GetConsumerOffset {
-    /// The consumer that is storing the offset, either the regular consumer or the consumer group.
-    #[serde(flatten)]
-    pub consumer: Consumer,
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-    #[serde(default = "default_partition_id")]
-    pub partition_id: Option<u32>,
-}
-
-impl Default for GetConsumerOffset {
-    fn default() -> Self {
-        GetConsumerOffset {
-            consumer: Consumer::default(),
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partition_id: default_partition_id(),
-        }
-    }
-}
-
-impl Command for GetConsumerOffset {
-    fn code(&self) -> u32 {
-        GET_CONSUMER_OFFSET_CODE
-    }
-}
-
-fn default_partition_id() -> Option<u32> {
-    Some(0)
-}
-
-impl Validatable<IggyError> for GetConsumerOffset {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetConsumerOffset {
-    fn to_bytes(&self) -> Bytes {
-        let consumer_bytes = self.consumer.to_bytes();
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        // Encode partition_id with a flag byte: 1 = Some, 0 = None
-        if let Some(partition_id) = self.partition_id {
-            bytes.put_u8(1);
-            bytes.put_u32_le(partition_id);
-        } else {
-            bytes.put_u8(0);
-            bytes.put_u32_le(0); // Padding to keep structure consistent
-        }
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetConsumerOffset, IggyError> {
-        if bytes.len() < 16 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let consumer_kind =
-            ConsumerKind::from_code(*bytes.first().ok_or(IggyError::InvalidCommand)?)?;
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..))?;
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        let partition_id_value = u32::from_le_bytes(
-            bytes
-                .get(position + 1..position + 5)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id_value)
-        } else {
-            None
-        };
-        let command = GetConsumerOffset {
-            consumer,
-            stream_id,
-            topic_id,
-            partition_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for GetConsumerOffset {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}",
-            self.consumer,
-            self.stream_id,
-            self.topic_id,
-            self.partition_id.unwrap_or(0)
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetConsumerOffset {
-            consumer: Consumer::new(Identifier::numeric(1).unwrap()),
-            stream_id: Identifier::numeric(2).unwrap(),
-            topic_id: Identifier::numeric(3).unwrap(),
-            partition_id: Some(4),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let consumer_kind = ConsumerKind::from_code(bytes[0]).unwrap();
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..)).unwrap();
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = bytes[position];
-        let partition_id =
-            u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id)
-        } else {
-            None
-        };
-
-        assert!(!bytes.is_empty());
-        assert_eq!(consumer, command.consumer);
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let consumer = Consumer::new(Identifier::numeric(1).unwrap());
-        let stream_id = Identifier::numeric(2).unwrap();
-        let topic_id = Identifier::numeric(3).unwrap();
-        let partition_id = 4u32;
-
-        let consumer_bytes = consumer.to_bytes();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            5 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u8(1); // Flag: partition_id is Some
-        bytes.put_u32_le(partition_id);
-
-        let command = GetConsumerOffset::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(consumer, command.consumer);
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partition_id, Some(partition_id));
-    }
-}
diff --git a/core/common/src/commands/consumer_offsets/store_consumer_offset.rs b/core/common/src/commands/consumer_offsets/store_consumer_offset.rs
deleted file mode 100644
index d8512df75..000000000
--- a/core/common/src/commands/consumer_offsets/store_consumer_offset.rs
+++ /dev/null
@@ -1,240 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, STORE_CONSUMER_OFFSET_CODE};
-use crate::{Consumer, ConsumerKind};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `StoreConsumerOffset` command stores the offset of a consumer for a given partition on the server.
-/// It has additional payload:
-/// - `consumer` - the consumer that is storing the offset, either the regular consumer or the consumer group.
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partition_id` - partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-/// - `offset` - offset to store.
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct StoreConsumerOffset {
-    /// The consumer that is storing the offset, either the regular consumer or the consumer group.
-    #[serde(flatten)]
-    pub consumer: Consumer,
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Partition ID on which the offset is stored. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-    pub partition_id: Option<u32>,
-    /// Offset to store.
-    pub offset: u64,
-}
-
-impl Default for StoreConsumerOffset {
-    fn default() -> Self {
-        StoreConsumerOffset {
-            consumer: Consumer::default(),
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partition_id: Some(0),
-            offset: 0,
-        }
-    }
-}
-
-impl Command for StoreConsumerOffset {
-    fn code(&self) -> u32 {
-        STORE_CONSUMER_OFFSET_CODE
-    }
-}
-
-impl Validatable<IggyError> for StoreConsumerOffset {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for StoreConsumerOffset {
-    fn to_bytes(&self) -> Bytes {
-        let consumer_bytes = self.consumer.to_bytes();
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            13 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        // Encode partition_id with a flag byte: 1 = Some, 0 = None
-        if let Some(partition_id) = self.partition_id {
-            bytes.put_u8(1);
-            bytes.put_u32_le(partition_id);
-        } else {
-            bytes.put_u8(0);
-            bytes.put_u32_le(0); // Padding to keep structure consistent
-        }
-        bytes.put_u64_le(self.offset);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<StoreConsumerOffset, IggyError> {
-        if bytes.len() < 24 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let consumer_kind =
-            ConsumerKind::from_code(*bytes.first().ok_or(IggyError::InvalidCommand)?)?;
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..))?;
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        let partition_id_value = u32::from_le_bytes(
-            bytes
-                .get(position + 1..position + 5)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id_value)
-        } else {
-            None
-        };
-        let offset = u64::from_le_bytes(
-            bytes
-                .get(position + 5..position + 13)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let command = StoreConsumerOffset {
-            consumer,
-            stream_id,
-            topic_id,
-            partition_id,
-            offset,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for StoreConsumerOffset {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}|{}",
-            self.consumer,
-            self.stream_id,
-            self.topic_id,
-            self.partition_id.unwrap_or(0),
-            self.offset
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = StoreConsumerOffset {
-            consumer: Consumer::new(Identifier::numeric(1).unwrap()),
-            stream_id: Identifier::numeric(2).unwrap(),
-            topic_id: Identifier::numeric(3).unwrap(),
-            partition_id: Some(4),
-            offset: 5,
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let consumer_kind = ConsumerKind::from_code(bytes[0]).unwrap();
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..)).unwrap();
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = bytes[position];
-        let partition_id =
-            u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id)
-        } else {
-            None
-        };
-        let offset = u64::from_le_bytes(bytes[position + 5..position + 13].try_into().unwrap());
-
-        assert!(!bytes.is_empty());
-        assert_eq!(consumer, command.consumer);
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-        assert_eq!(offset, command.offset);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let consumer = Consumer::new(Identifier::numeric(1).unwrap());
-        let stream_id = Identifier::numeric(2).unwrap();
-        let topic_id = Identifier::numeric(3).unwrap();
-        let partition_id = 4u32;
-        let offset = 5u64;
-
-        let consumer_bytes = consumer.to_bytes();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            13 + consumer_bytes.len() + stream_id_bytes.len() + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u8(1); // Flag: partition_id is Some
-        bytes.put_u32_le(partition_id);
-        bytes.put_u64_le(offset);
-
-        let command = StoreConsumerOffset::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.consumer, consumer);
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partition_id, Some(partition_id));
-        assert_eq!(command.offset, offset);
-    }
-}
diff --git a/core/common/src/commands/messages/flush_unsaved_buffer.rs b/core/common/src/commands/messages/flush_unsaved_buffer.rs
deleted file mode 100644
index 2235a911b..000000000
--- a/core/common/src/commands/messages/flush_unsaved_buffer.rs
+++ /dev/null
@@ -1,132 +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::{
-    BytesSerializable, Identifier, Validatable,
-    error::IggyError,
-    {Command, FLUSH_UNSAVED_BUFFER_CODE},
-};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `FlushUnsavedBuffer` command is used to force a flush of `unsaved_buffer` to disk for specific stream -> topic -> partition.
-/// - `stream_id` - stream identifier
-/// - `topic_id` - topic identifier
-/// - `partition_id` - partition identifier
-/// - `fsync` - if `true` then the data is flushed to disk and fsynced, if `false` then the data is only flushed to disk.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct FlushUnsavedBuffer {
-    pub stream_id: Identifier,
-    pub topic_id: Identifier,
-    pub partition_id: u32,
-    pub fsync: bool,
-}
-
-impl FlushUnsavedBuffer {
-    fn fsync_stringified(&self) -> &'static str {
-        if self.fsync { "f" } else { "n" }
-    }
-}
-
-impl Display for FlushUnsavedBuffer {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}",
-            self.stream_id,
-            self.topic_id,
-            self.partition_id,
-            self.fsync_stringified()
-        )
-    }
-}
-
-impl Command for FlushUnsavedBuffer {
-    fn code(&self) -> u32 {
-        FLUSH_UNSAVED_BUFFER_CODE
-    }
-}
-
-impl BytesSerializable for FlushUnsavedBuffer {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes =
-            BytesMut::with_capacity(stream_id_bytes.len() + topic_id_bytes.len() + 4 + 1);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(self.partition_id);
-        bytes.put_u8(if self.fsync { 1 } else { 0 });
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<Self, IggyError>
-    where
-        Self: Sized,
-    {
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.to_bytes().len();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.to_bytes().len();
-        let partition_id = u32::from_le_bytes(
-            bytes
-                .get(position..position + 4)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        position += 4;
-        let fsync = *bytes.get(position).ok_or(IggyError::InvalidCommand)? == 1;
-        Ok(FlushUnsavedBuffer {
-            stream_id,
-            topic_id,
-            partition_id,
-            fsync,
-        })
-    }
-}
-
-impl Validatable<IggyError> for FlushUnsavedBuffer {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use super::FlushUnsavedBuffer;
-    use crate::BytesSerializable;
-    use crate::Identifier;
-
-    #[test]
-    fn test_flush_unsaved_buffer_serialization() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(1).unwrap();
-        let flush_unsaved_buffer = super::FlushUnsavedBuffer {
-            stream_id,
-            topic_id,
-            partition_id: 1,
-            fsync: false,
-        };
-        let bytes = flush_unsaved_buffer.to_bytes();
-        let deserialized_flush_unsaved_buffer = FlushUnsavedBuffer::from_bytes(bytes).unwrap();
-        assert_eq!(flush_unsaved_buffer, deserialized_flush_unsaved_buffer);
-    }
-}
diff --git a/core/common/src/commands/messages/poll_messages.rs b/core/common/src/commands/messages/poll_messages.rs
deleted file mode 100644
index ac266ca4c..000000000
--- a/core/common/src/commands/messages/poll_messages.rs
+++ /dev/null
@@ -1,341 +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::error::IggyError;
-use crate::{BytesSerializable, Identifier, PollingKind, PollingStrategy, Sizeable, Validatable};
-use crate::{Command, POLL_MESSAGES_CODE};
-use crate::{Consumer, ConsumerKind};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-pub const DEFAULT_PARTITION_ID: u32 = 0;
-pub const DEFAULT_NUMBER_OF_MESSAGES_TO_POLL: u32 = 10;
-
-/// `PollMessages` command is used to poll messages from a topic in a stream.
-/// It has additional payload:
-/// - `consumer` - consumer which will poll messages. Either regular consumer or consumer group.
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partition_id` - partition ID from which messages will be polled. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-/// - `strategy` - polling strategy which specifies from where to start polling messages.
-/// - `count` - number of messages to poll.
-/// - `auto_commit` - whether to commit offset on the server automatically after polling the messages.
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct PollMessages {
-    /// Consumer which will poll messages. Either regular consumer or consumer group.
-    #[serde(flatten)]
-    pub consumer: Consumer,
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Partition ID from which messages will be polled. Has to be specified for the regular consumer. For consumer group it is ignored (use `None`).
-    #[serde(default = "PollMessages::default_partition_id")]
-    pub partition_id: Option<u32>,
-    /// Polling strategy which specifies from where to start polling messages.
-    #[serde(default = "PollingStrategy::default", flatten)]
-    pub strategy: PollingStrategy,
-    /// Number of messages to poll.
-    #[serde(default = "PollMessages::default_number_of_messages_to_poll")]
-    pub count: u32,
-    /// Whether to commit offset on the server automatically after polling the messages.
-    #[serde(default)]
-    pub auto_commit: bool,
-}
-
-impl PollMessages {
-    pub fn bytes(
-        stream_id: &Identifier,
-        topic_id: &Identifier,
-        partition_id: Option<u32>,
-        consumer: &Consumer,
-        strategy: &PollingStrategy,
-        count: u32,
-        auto_commit: bool,
-    ) -> Bytes {
-        let consumer_bytes = consumer.to_bytes();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let strategy_bytes = strategy.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            10 + consumer_bytes.len()
-                + stream_id_bytes.len()
-                + topic_id_bytes.len()
-                + strategy_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        // Encode partition_id with a flag byte: 1 = Some, 0 = None
-        if let Some(partition_id) = partition_id {
-            bytes.put_u8(1);
-            bytes.put_u32_le(partition_id);
-        } else {
-            bytes.put_u8(0);
-            bytes.put_u32_le(0); // Padding to keep structure consistent
-        }
-        bytes.put_slice(&strategy_bytes);
-        bytes.put_u32_le(count);
-        if auto_commit {
-            bytes.put_u8(1);
-        } else {
-            bytes.put_u8(0);
-        }
-
-        bytes.freeze()
-    }
-
-    pub fn default_number_of_messages_to_poll() -> u32 {
-        DEFAULT_NUMBER_OF_MESSAGES_TO_POLL
-    }
-
-    pub fn default_partition_id() -> Option<u32> {
-        Some(DEFAULT_PARTITION_ID)
-    }
-}
-
-impl Default for PollMessages {
-    fn default() -> Self {
-        Self {
-            consumer: Consumer::default(),
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(1).unwrap(),
-            partition_id: PollMessages::default_partition_id(),
-            strategy: PollingStrategy::default(),
-            count: PollMessages::default_number_of_messages_to_poll(),
-            auto_commit: false,
-        }
-    }
-}
-
-impl Command for PollMessages {
-    fn code(&self) -> u32 {
-        POLL_MESSAGES_CODE
-    }
-}
-
-impl Validatable<IggyError> for PollMessages {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for PollMessages {
-    fn to_bytes(&self) -> Bytes {
-        PollMessages::bytes(
-            &self.stream_id,
-            &self.topic_id,
-            self.partition_id,
-            &self.consumer,
-            &self.strategy,
-            self.count,
-            self.auto_commit,
-        )
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<Self, IggyError> {
-        if bytes.len() < 30 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let consumer_kind =
-            ConsumerKind::from_code(*bytes.first().ok_or(IggyError::InvalidCommand)?)?;
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..))?;
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        let partition_id_value = u32::from_le_bytes(
-            bytes
-                .get(position + 1..position + 5)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id_value)
-        } else {
-            None
-        };
-        let polling_kind =
-            PollingKind::from_code(*bytes.get(position + 5).ok_or(IggyError::InvalidCommand)?)?;
-        position += 6;
-        let value = u64::from_le_bytes(
-            bytes
-                .get(position..position + 8)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let strategy = PollingStrategy {
-            kind: polling_kind,
-            value,
-        };
-        let count = u32::from_le_bytes(
-            bytes
-                .get(position + 8..position + 12)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let auto_commit = *bytes.get(position + 12).ok_or(IggyError::InvalidCommand)? == 1;
-        let command = PollMessages {
-            consumer,
-            stream_id,
-            topic_id,
-            partition_id,
-            strategy,
-            count,
-            auto_commit,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for PollMessages {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}|{}|{}|{}",
-            self.consumer,
-            self.stream_id,
-            self.topic_id,
-            self.partition_id.unwrap_or(0),
-            self.strategy,
-            self.count,
-            auto_commit_to_string(self.auto_commit)
-        )
-    }
-}
-
-fn auto_commit_to_string(auto_commit: bool) -> &'static str {
-    if auto_commit { "a" } else { "n" }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = PollMessages {
-            consumer: Consumer::new(Identifier::numeric(1).unwrap()),
-            stream_id: Identifier::numeric(2).unwrap(),
-            topic_id: Identifier::numeric(3).unwrap(),
-            partition_id: Some(4),
-            strategy: PollingStrategy::offset(2),
-            count: 3,
-            auto_commit: true,
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let consumer_kind = ConsumerKind::from_code(bytes[0]).unwrap();
-        let consumer_id = Identifier::from_bytes(bytes.slice(1..)).unwrap();
-        position += 1 + consumer_id.get_size_bytes().as_bytes_usize();
-        let consumer = Consumer {
-            kind: consumer_kind,
-            id: consumer_id,
-        };
-        let stream_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let has_partition_id = bytes[position];
-        let partition_id =
-            u32::from_le_bytes(bytes[position + 1..position + 5].try_into().unwrap());
-        let partition_id = if has_partition_id == 1 {
-            Some(partition_id)
-        } else {
-            None
-        };
-        let polling_kind = PollingKind::from_code(bytes[position + 5]).unwrap();
-        position += 6;
-        let value = u64::from_le_bytes(bytes[position..position + 8].try_into().unwrap());
-        let strategy = PollingStrategy {
-            kind: polling_kind,
-            value,
-        };
-        let count = u32::from_le_bytes(bytes[position + 8..position + 12].try_into().unwrap());
-        let auto_commit = bytes[position + 12];
-        let auto_commit = matches!(auto_commit, 1);
-
-        assert!(!bytes.is_empty());
-        assert_eq!(consumer, command.consumer);
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-        assert_eq!(strategy, command.strategy);
-        assert_eq!(count, command.count);
-        assert_eq!(auto_commit, command.auto_commit);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let consumer = Consumer::new(Identifier::numeric(1).unwrap());
-        let stream_id = Identifier::numeric(2).unwrap();
-        let topic_id = Identifier::numeric(3).unwrap();
-        let partition_id = 4u32;
-        let strategy = PollingStrategy::offset(2);
-        let count = 3u32;
-        let auto_commit = 1u8;
-
-        let consumer_bytes = consumer.to_bytes();
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let strategy_bytes = strategy.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            10 + consumer_bytes.len()
-                + stream_id_bytes.len()
-                + topic_id_bytes.len()
-                + strategy_bytes.len(),
-        );
-        bytes.put_slice(&consumer_bytes);
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u8(1); // Flag: partition_id is Some
-        bytes.put_u32_le(partition_id);
-        bytes.put_slice(&strategy_bytes);
-        bytes.put_u32_le(count);
-        bytes.put_u8(auto_commit);
-
-        let command = PollMessages::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let auto_commit = matches!(auto_commit, 1);
-
-        let command = command.unwrap();
-        assert_eq!(command.consumer, consumer);
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partition_id, Some(partition_id));
-        assert_eq!(command.strategy, strategy);
-        assert_eq!(command.count, count);
-        assert_eq!(command.auto_commit, auto_commit);
-    }
-}
diff --git a/core/common/src/commands/partitions/create_partitions.rs b/core/common/src/commands/partitions/create_partitions.rs
deleted file mode 100644
index 2bd9ee454..000000000
--- a/core/common/src/commands/partitions/create_partitions.rs
+++ /dev/null
@@ -1,167 +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 super::MAX_PARTITIONS_COUNT;
-use crate::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{CREATE_PARTITIONS_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `CreatePartitions` command is used to create new partitions for a topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partitions_count` - number of partitions in the topic to create, max value is 1000.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct CreatePartitions {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Number of partitions in the topic to create, max value is 1000.
-    pub partitions_count: u32,
-}
-
-impl Command for CreatePartitions {
-    fn code(&self) -> u32 {
-        CREATE_PARTITIONS_CODE
-    }
-}
-
-impl Default for CreatePartitions {
-    fn default() -> Self {
-        CreatePartitions {
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partitions_count: 1,
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreatePartitions {
-    fn validate(&self) -> Result<(), IggyError> {
-        if !(1..=MAX_PARTITIONS_COUNT).contains(&self.partitions_count) {
-            return Err(IggyError::TooManyPartitions);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreatePartitions {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(4 + stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(self.partitions_count);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<CreatePartitions, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partitions_count = u32::from_le_bytes(
-            bytes
-                .get(position..position + 4)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let command = CreatePartitions {
-            stream_id,
-            topic_id,
-            partitions_count,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for CreatePartitions {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}",
-            self.stream_id, self.topic_id, self.partitions_count
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use bytes::BufMut;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreatePartitions {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            partitions_count: 3,
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partitions_count =
-            u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partitions_count, command.partitions_count);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let partitions_count = 3u32;
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(4 + stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(partitions_count);
-        let command = CreatePartitions::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partitions_count, partitions_count);
-    }
-}
diff --git a/core/common/src/commands/partitions/delete_partitions.rs b/core/common/src/commands/partitions/delete_partitions.rs
deleted file mode 100644
index 9e0cf1ddc..000000000
--- a/core/common/src/commands/partitions/delete_partitions.rs
+++ /dev/null
@@ -1,167 +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 super::MAX_PARTITIONS_COUNT;
-use crate::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_PARTITIONS_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeletePartitions` command is used to delete partitions from a topic.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partitions_count` - number of partitions in the topic to delete, max value is 1000.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct DeletePartitions {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Number of partitions in the topic to delete, max value is 1000.
-    pub partitions_count: u32,
-}
-
-impl Command for DeletePartitions {
-    fn code(&self) -> u32 {
-        DELETE_PARTITIONS_CODE
-    }
-}
-
-impl Default for DeletePartitions {
-    fn default() -> Self {
-        DeletePartitions {
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partitions_count: 1,
-        }
-    }
-}
-
-impl Validatable<IggyError> for DeletePartitions {
-    fn validate(&self) -> Result<(), IggyError> {
-        if !(1..=MAX_PARTITIONS_COUNT).contains(&self.partitions_count) {
-            return Err(IggyError::TooManyPartitions);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeletePartitions {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(4 + stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(self.partitions_count);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<DeletePartitions, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partitions_count = u32::from_le_bytes(
-            bytes
-                .get(position..position + 4)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let command = DeletePartitions {
-            stream_id,
-            topic_id,
-            partitions_count,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for DeletePartitions {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}",
-            self.stream_id, self.topic_id, self.partitions_count
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use bytes::BufMut;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeletePartitions {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            partitions_count: 3,
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partitions_count =
-            u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partitions_count, command.partitions_count);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let partitions_count = 3u32;
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(4 + stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(partitions_count);
-        let command = DeletePartitions::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.partitions_count, partitions_count);
-    }
-}
diff --git a/core/common/src/commands/personal_access_tokens/create_personal_access_token.rs b/core/common/src/commands/personal_access_tokens/create_personal_access_token.rs
deleted file mode 100644
index 395822854..000000000
--- a/core/common/src/commands/personal_access_tokens/create_personal_access_token.rs
+++ /dev/null
@@ -1,157 +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::BytesSerializable;
-use crate::Validatable;
-use crate::defaults::*;
-use crate::error::IggyError;
-use crate::utils::expiry::IggyExpiry;
-use crate::{CREATE_PERSONAL_ACCESS_TOKEN_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::{Display, Formatter};
-use std::str::from_utf8;
-
-/// `CreatePersonalAccessToken` command is used to create a new personal access token for the authenticated user.
-/// It has additional payload:
-/// - `name` - unique name of the token, must be between 3 and 30 characters long.
-/// - `expiry` - expiry of the token.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct CreatePersonalAccessToken {
-    /// Unique name of the token, must be between 3 and 30 characters long.
-    pub name: String,
-    /// Expiry of the token.
-    pub expiry: IggyExpiry,
-}
-
-impl Command for CreatePersonalAccessToken {
-    fn code(&self) -> u32 {
-        CREATE_PERSONAL_ACCESS_TOKEN_CODE
-    }
-}
-
-impl Default for CreatePersonalAccessToken {
-    fn default() -> Self {
-        CreatePersonalAccessToken {
-            name: "token".to_string(),
-            expiry: IggyExpiry::NeverExpire,
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreatePersonalAccessToken {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty()
-            || self.name.len() > MAX_PERSONAL_ACCESS_TOKEN_NAME_LENGTH
-            || self.name.len() < MIN_PERSONAL_ACCESS_TOKEN_NAME_LENGTH
-        {
-            return Err(IggyError::InvalidPersonalAccessTokenName);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreatePersonalAccessToken {
-    fn to_bytes(&self) -> Bytes {
-        let mut bytes = BytesMut::with_capacity(5 + self.name.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.put_u64_le(self.expiry.into());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<CreatePersonalAccessToken, IggyError> {
-        if bytes.len() < 12 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let name_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(1..1 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let position = 1 + name_length;
-        let expiry = u64::from_le_bytes(
-            bytes
-                .get(position..position + 8)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let expiry: IggyExpiry = expiry.into();
-
-        let command = CreatePersonalAccessToken { name, expiry };
-        Ok(command)
-    }
-}
-
-impl Display for CreatePersonalAccessToken {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.name, self.expiry)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreatePersonalAccessToken {
-            name: "test".to_string(),
-            expiry: IggyExpiry::NeverExpire,
-        };
-
-        let bytes = command.to_bytes();
-        let name_length = bytes[0];
-        let name = from_utf8(&bytes[1..1 + name_length as usize]).unwrap();
-        let expiry = u64::from_le_bytes(
-            bytes[1 + name_length as usize..9 + name_length as usize]
-                .try_into()
-                .unwrap(),
-        );
-        let expiry: IggyExpiry = expiry.into();
-        assert!(!bytes.is_empty());
-        assert_eq!(name, command.name);
-        assert_eq!(expiry, command.expiry);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let name = "test";
-        let expiry = IggyExpiry::NeverExpire;
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-        bytes.put_u64_le(expiry.into());
-
-        let command = CreatePersonalAccessToken::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.name, name);
-        assert_eq!(command.expiry, expiry);
-    }
-}
diff --git a/core/common/src/commands/personal_access_tokens/delete_personal_access_token.rs b/core/common/src/commands/personal_access_tokens/delete_personal_access_token.rs
deleted file mode 100644
index 0d19b68c9..000000000
--- a/core/common/src/commands/personal_access_tokens/delete_personal_access_token.rs
+++ /dev/null
@@ -1,156 +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::Command;
-use crate::Validatable;
-use crate::defaults::*;
-use crate::error::IggyError;
-use crate::{BytesSerializable, DELETE_PERSONAL_ACCESS_TOKEN_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::{Display, Formatter};
-use std::str::from_utf8;
-
-/// `DeletePersonalAccessToken` command is used to delete a personal access token for the authenticated user.
-/// It has additional payload:
-/// - `name` - unique name of the token, must be between 3 and 30 characters long.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct DeletePersonalAccessToken {
-    /// Unique name of the token, must be between 3 and 30 characters long.
-    pub name: String,
-}
-
-impl Command for DeletePersonalAccessToken {
-    fn code(&self) -> u32 {
-        DELETE_PERSONAL_ACCESS_TOKEN_CODE
-    }
-}
-
-impl Default for DeletePersonalAccessToken {
-    fn default() -> Self {
-        DeletePersonalAccessToken {
-            name: "token".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for DeletePersonalAccessToken {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty()
-            || self.name.len() > MAX_PERSONAL_ACCESS_TOKEN_NAME_LENGTH
-            || self.name.len() < MIN_PERSONAL_ACCESS_TOKEN_NAME_LENGTH
-        {
-            return Err(IggyError::InvalidPersonalAccessTokenName);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeletePersonalAccessToken {
-    fn to_bytes(&self) -> Bytes {
-        let mut bytes = BytesMut::with_capacity(5 + self.name.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<DeletePersonalAccessToken, IggyError> {
-        if bytes.len() < 4 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let name_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(1..1 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let command = DeletePersonalAccessToken { name };
-        Ok(command)
-    }
-}
-
-impl Display for DeletePersonalAccessToken {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.name)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeletePersonalAccessToken {
-            name: "test".to_string(),
-        };
-
-        let bytes = command.to_bytes();
-        let name_length = bytes[0];
-        let name = from_utf8(&bytes[1..1 + name_length as usize]).unwrap();
-        assert!(!bytes.is_empty());
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(DeletePersonalAccessToken::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = DeletePersonalAccessToken::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                DeletePersonalAccessToken::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_corrupted_name_length() {
-        let mut buf = BytesMut::new();
-        buf.put_u8(255);
-        buf.put_slice(b"short");
-        assert!(DeletePersonalAccessToken::from_bytes(buf.freeze()).is_err());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let name = "test";
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-
-        let command = DeletePersonalAccessToken::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.name, name);
-    }
-}
diff --git a/core/common/src/commands/personal_access_tokens/get_personal_access_tokens.rs b/core/common/src/commands/personal_access_tokens/get_personal_access_tokens.rs
deleted file mode 100644
index c07551a61..000000000
--- a/core/common/src/commands/personal_access_tokens/get_personal_access_tokens.rs
+++ /dev/null
@@ -1,86 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_PERSONAL_ACCESS_TOKENS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetPersonalAccessTokens` command is used to get all personal access tokens for the authenticated user.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetPersonalAccessTokens {}
-
-impl Command for GetPersonalAccessTokens {
-    fn code(&self) -> u32 {
-        GET_PERSONAL_ACCESS_TOKENS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetPersonalAccessTokens {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetPersonalAccessTokens {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetPersonalAccessTokens, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetPersonalAccessTokens {})
-    }
-}
-
-impl Display for GetPersonalAccessTokens {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetPersonalAccessTokens {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetPersonalAccessTokens::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetPersonalAccessTokens::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/personal_access_tokens/login_with_personal_access_token.rs b/core/common/src/commands/personal_access_tokens/login_with_personal_access_token.rs
deleted file mode 100644
index 73dedb34b..000000000
--- a/core/common/src/commands/personal_access_tokens/login_with_personal_access_token.rs
+++ /dev/null
@@ -1,173 +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::BytesSerializable;
-use crate::Validatable;
-use crate::defaults::*;
-use crate::error::IggyError;
-use crate::{Command, LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use secrecy::{ExposeSecret, SecretString};
-use serde::{Deserialize, Serialize};
-use std::fmt::{Display, Formatter};
-use std::str::from_utf8;
-
-/// `LoginWithPersonalAccessToken` command is used to login the user with a personal access token, instead of the username and password.
-/// It has additional payload:
-/// - `token` - personal access token
-#[derive(Debug, Serialize, Deserialize)]
-pub struct LoginWithPersonalAccessToken {
-    /// Personal access token
-    #[serde(serialize_with = "crate::utils::serde_secret::serialize_secret")]
-    pub token: SecretString,
-}
-
-impl Command for LoginWithPersonalAccessToken {
-    fn code(&self) -> u32 {
-        LOGIN_WITH_PERSONAL_ACCESS_TOKEN_CODE
-    }
-}
-
-impl Default for LoginWithPersonalAccessToken {
-    fn default() -> Self {
-        LoginWithPersonalAccessToken {
-            token: SecretString::from("token"),
-        }
-    }
-}
-
-impl Validatable<IggyError> for LoginWithPersonalAccessToken {
-    fn validate(&self) -> Result<(), IggyError> {
-        let token = self.token.expose_secret();
-        if token.is_empty() || token.len() > MAX_PAT_LENGTH {
-            return Err(IggyError::InvalidPersonalAccessToken);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for LoginWithPersonalAccessToken {
-    fn to_bytes(&self) -> Bytes {
-        let token = self.token.expose_secret();
-        let mut bytes = BytesMut::with_capacity(5 + token.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(token.len() as u8);
-        bytes.put_slice(token.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<LoginWithPersonalAccessToken, IggyError> {
-        if bytes.len() < 4 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let token_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let token = from_utf8(
-            bytes
-                .get(1..1 + token_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let command = LoginWithPersonalAccessToken {
-            token: SecretString::from(token),
-        };
-        Ok(command)
-    }
-}
-
-impl Display for LoginWithPersonalAccessToken {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "******")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = LoginWithPersonalAccessToken {
-            token: SecretString::from("test"),
-        };
-
-        let bytes = command.to_bytes();
-        let token_length = bytes[0];
-        let token = from_utf8(&bytes[1..1 + token_length as usize]).unwrap();
-        assert!(!bytes.is_empty());
-        assert_eq!(token, command.token.expose_secret());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(LoginWithPersonalAccessToken::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = LoginWithPersonalAccessToken::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                LoginWithPersonalAccessToken::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_corrupted_token_length() {
-        let mut buf = BytesMut::new();
-        buf.put_u8(255);
-        buf.put_slice(b"short");
-        assert!(LoginWithPersonalAccessToken::from_bytes(buf.freeze()).is_err());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let token = "test";
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(token.len() as u8);
-        bytes.put_slice(token.as_bytes());
-
-        let command = LoginWithPersonalAccessToken::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.token.expose_secret(), token);
-    }
-
-    #[test]
-    fn should_fail_validation_for_empty_token() {
-        let command = LoginWithPersonalAccessToken {
-            token: SecretString::from(""),
-        };
-        assert!(command.validate().is_err());
-    }
-
-    #[test]
-    fn should_pass_validation_for_valid_token() {
-        let command = LoginWithPersonalAccessToken::default();
-        assert!(command.validate().is_ok());
-    }
-}
diff --git a/core/common/src/commands/segments/delete_segments.rs b/core/common/src/commands/segments/delete_segments.rs
deleted file mode 100644
index c57833f4c..000000000
--- a/core/common/src/commands/segments/delete_segments.rs
+++ /dev/null
@@ -1,202 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_SEGMENTS_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteSegments` command is used to delete segments from a partition.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `partition_id` - unique partition ID (numeric or name).
-/// - `segments_count` - number of segments in the partition to delete.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct DeleteSegments {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Unique partition ID (numeric or name).
-    #[serde(skip)]
-    pub partition_id: u32,
-    /// Number of segments in the partition to delete, max value is 1000.
-    pub segments_count: u32,
-}
-
-impl Command for DeleteSegments {
-    fn code(&self) -> u32 {
-        DELETE_SEGMENTS_CODE
-    }
-}
-
-impl Default for DeleteSegments {
-    fn default() -> Self {
-        DeleteSegments {
-            segments_count: 1,
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            partition_id: u32::default(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for DeleteSegments {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteSegments {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            std::mem::size_of::<u32>()
-                + std::mem::size_of::<u32>()
-                + stream_id_bytes.len()
-                + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(self.partition_id);
-        bytes.put_u32_le(self.segments_count);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<DeleteSegments, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partition_id = u32::from_le_bytes(
-            bytes
-                .get(position..position + std::mem::size_of::<u32>())
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        position += std::mem::size_of::<u32>();
-        let segments_count = u32::from_le_bytes(
-            bytes
-                .get(position..position + std::mem::size_of::<u32>())
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let command = DeleteSegments {
-            stream_id,
-            topic_id,
-            partition_id,
-            segments_count,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteSegments {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}",
-            self.stream_id, self.topic_id, self.partition_id, self.segments_count
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use bytes::BufMut;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteSegments {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            partition_id: 3,
-            segments_count: 3,
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let partition_id = u32::from_le_bytes(
-            bytes[position..position + std::mem::size_of::<u32>()]
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)
-                .unwrap(),
-        );
-        position += std::mem::size_of::<u32>();
-        let segments_count = u32::from_le_bytes(
-            bytes[position..position + std::mem::size_of::<u32>()]
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)
-                .unwrap(),
-        );
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-        assert_eq!(segments_count, command.segments_count);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let partition_id = 3;
-        let segments_count = 3u32;
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            std::mem::size_of::<u32>()
-                + std::mem::size_of::<u32>()
-                + stream_id_bytes.len()
-                + topic_id_bytes.len(),
-        );
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u32_le(partition_id);
-        bytes.put_u32_le(segments_count);
-        let command = DeleteSegments::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(partition_id, command.partition_id);
-        assert_eq!(segments_count, command.segments_count);
-    }
-}
diff --git a/core/common/src/commands/streams/create_stream.rs b/core/common/src/commands/streams/create_stream.rs
deleted file mode 100644
index d5964eb5e..000000000
--- a/core/common/src/commands/streams/create_stream.rs
+++ /dev/null
@@ -1,153 +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 super::MAX_NAME_LENGTH;
-use crate::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{CREATE_STREAM_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `CreateStream` command is used to create a new stream.
-/// It has additional payload:
-/// - `name` - unique stream name (string), max length is 255 characters.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct CreateStream {
-    /// Unique stream name (string), max length is 255 characters.
-    pub name: String,
-}
-
-impl Command for CreateStream {
-    fn code(&self) -> u32 {
-        CREATE_STREAM_CODE
-    }
-}
-
-impl Default for CreateStream {
-    fn default() -> Self {
-        CreateStream {
-            name: "stream".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreateStream {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
-            return Err(IggyError::InvalidStreamName);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreateStream {
-    fn to_bytes(&self) -> Bytes {
-        let mut bytes = BytesMut::with_capacity(5 + self.name.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<CreateStream, IggyError> {
-        if bytes.len() < 2 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let name_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(1..1 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let command = CreateStream { name };
-        Ok(command)
-    }
-}
-
-impl Display for CreateStream {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.name)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreateStream {
-            name: "test".to_string(),
-        };
-
-        let bytes = command.to_bytes();
-        let name_length = bytes[0];
-        let name = from_utf8(&bytes[1..1 + name_length as usize]).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(CreateStream::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = CreateStream::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                CreateStream::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_corrupted_name_length() {
-        let mut buf = BytesMut::new();
-        buf.put_u8(255);
-        buf.put_slice(b"short");
-        assert!(CreateStream::from_bytes(buf.freeze()).is_err());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let name = "test".to_string();
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-        let command = CreateStream::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.name, name);
-    }
-}
diff --git a/core/common/src/commands/streams/delete_stream.rs b/core/common/src/commands/streams/delete_stream.rs
deleted file mode 100644
index f44110340..000000000
--- a/core/common/src/commands/streams/delete_stream.rs
+++ /dev/null
@@ -1,99 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_STREAM_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteStream` command is used to delete an existing stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct DeleteStream {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-}
-
-impl Command for DeleteStream {
-    fn code(&self) -> u32 {
-        DELETE_STREAM_CODE
-    }
-}
-
-impl Validatable<IggyError> for DeleteStream {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteStream {
-    fn to_bytes(&self) -> Bytes {
-        self.stream_id.to_bytes()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<DeleteStream, IggyError> {
-        if bytes.len() < 3 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let stream_id = Identifier::from_bytes(bytes)?;
-        let command = DeleteStream { stream_id };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteStream {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.stream_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteStream {
-            stream_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let bytes = stream_id.to_bytes();
-        let command = DeleteStream::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-    }
-}
diff --git a/core/common/src/commands/streams/get_stream.rs b/core/common/src/commands/streams/get_stream.rs
deleted file mode 100644
index 0e3cae734..000000000
--- a/core/common/src/commands/streams/get_stream.rs
+++ /dev/null
@@ -1,99 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_STREAM_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetStream` command is used to retrieve the information about a stream by unique ID.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetStream {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-}
-
-impl Command for GetStream {
-    fn code(&self) -> u32 {
-        GET_STREAM_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetStream {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetStream {
-    fn to_bytes(&self) -> Bytes {
-        self.stream_id.to_bytes()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<GetStream, IggyError> {
-        if bytes.len() < 3 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let stream_id = Identifier::from_bytes(bytes)?;
-        let command = GetStream { stream_id };
-        Ok(command)
-    }
-}
-
-impl Display for GetStream {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.stream_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetStream {
-            stream_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let bytes = stream_id.to_bytes();
-        let command = GetStream::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-    }
-}
diff --git a/core/common/src/commands/streams/get_streams.rs b/core/common/src/commands/streams/get_streams.rs
deleted file mode 100644
index 082894375..000000000
--- a/core/common/src/commands/streams/get_streams.rs
+++ /dev/null
@@ -1,86 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_STREAMS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetStreams` command is used to retrieve the information about all streams.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetStreams {}
-
-impl Command for GetStreams {
-    fn code(&self) -> u32 {
-        GET_STREAMS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetStreams {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetStreams {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<GetStreams, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetStreams {})
-    }
-}
-
-impl Display for GetStreams {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetStreams {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetStreams::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetStreams::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/streams/purge_stream.rs b/core/common/src/commands/streams/purge_stream.rs
deleted file mode 100644
index 036930ff8..000000000
--- a/core/common/src/commands/streams/purge_stream.rs
+++ /dev/null
@@ -1,102 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, PURGE_STREAM_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `PurgeStream` command is used to purge stream data (all the messages from its topics).
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct PurgeStream {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-}
-
-impl Command for PurgeStream {
-    fn code(&self) -> u32 {
-        PURGE_STREAM_CODE
-    }
-}
-
-impl Validatable<IggyError> for PurgeStream {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for PurgeStream {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(stream_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<PurgeStream, IggyError> {
-        if bytes.len() < 5 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let stream_id = Identifier::from_bytes(bytes)?;
-        let command = PurgeStream { stream_id };
-        Ok(command)
-    }
-}
-
-impl Display for PurgeStream {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.stream_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = PurgeStream {
-            stream_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let bytes = stream_id.to_bytes();
-        let command = PurgeStream::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-    }
-}
diff --git a/core/common/src/commands/streams/update_stream.rs b/core/common/src/commands/streams/update_stream.rs
deleted file mode 100644
index a202105bf..000000000
--- a/core/common/src/commands/streams/update_stream.rs
+++ /dev/null
@@ -1,151 +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 super::MAX_NAME_LENGTH;
-use crate::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, UPDATE_STREAM_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `UpdateStream` command is used to update an existing stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `name` - unique stream name (string), max length is 255 characters.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct UpdateStream {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique stream name (string), max length is 255 characters.
-    pub name: String,
-}
-
-impl Command for UpdateStream {
-    fn code(&self) -> u32 {
-        UPDATE_STREAM_CODE
-    }
-}
-
-impl Default for UpdateStream {
-    fn default() -> Self {
-        UpdateStream {
-            stream_id: Identifier::default(),
-            name: "stream".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for UpdateStream {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
-            return Err(IggyError::InvalidStreamName);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for UpdateStream {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(1 + stream_id_bytes.len() + self.name.len());
-        bytes.put_slice(&stream_id_bytes);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<UpdateStream, IggyError> {
-        if bytes.len() < 5 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let name_length = *bytes.get(position).ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(position + 1..position + 1 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let command = UpdateStream { stream_id, name };
-        Ok(command)
-    }
-}
-
-impl Display for UpdateStream {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.stream_id, self.name)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = UpdateStream {
-            stream_id: Identifier::numeric(1).unwrap(),
-            name: "test".to_string(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let name_length = bytes[position];
-        let name = from_utf8(&bytes[position + 1..position + 1 + name_length as usize])
-            .unwrap()
-            .to_string();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let name = "test".to_string();
-
-        let stream_id_bytes = stream_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(1 + stream_id_bytes.len() + name.len());
-        bytes.put_slice(&stream_id_bytes);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-        let command = UpdateStream::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.name, name);
-    }
-}
diff --git a/core/common/src/commands/system/get_client.rs b/core/common/src/commands/system/get_client.rs
deleted file mode 100644
index 6307355b5..000000000
--- a/core/common/src/commands/system/get_client.rs
+++ /dev/null
@@ -1,113 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_CLIENT_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetClient` command is used to get the information about a specific client by unique ID.
-/// It has additional payload:
-/// - `client_id` - unique ID (numeric) of the client.
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct GetClient {
-    /// Unique ID (numeric) of the client.
-    pub client_id: u32,
-}
-
-impl Command for GetClient {
-    fn code(&self) -> u32 {
-        GET_CLIENT_CODE
-    }
-}
-
-impl Default for GetClient {
-    fn default() -> Self {
-        GetClient { client_id: 1 }
-    }
-}
-
-impl Validatable<IggyError> for GetClient {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.client_id == 0 {
-            return Err(IggyError::InvalidClientId);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetClient {
-    fn to_bytes(&self) -> Bytes {
-        let mut bytes = BytesMut::with_capacity(4);
-        bytes.put_u32_le(self.client_id);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetClient, IggyError> {
-        if bytes.len() != 4 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let client_id = u32::from_le_bytes(
-            bytes
-                .as_ref()
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let command = GetClient { client_id };
-        Ok(command)
-    }
-}
-
-impl Display for GetClient {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.client_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetClient { client_id: 1 };
-
-        let bytes = command.to_bytes();
-        let client_id = u32::from_le_bytes(bytes[..4].try_into().unwrap());
-
-        assert!(!bytes.is_empty());
-        assert_eq!(client_id, command.client_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let client_id = 1u32;
-        let mut bytes = BytesMut::with_capacity(4);
-        bytes.put_u32_le(client_id);
-        let command = GetClient::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.client_id, client_id);
-    }
-}
diff --git a/core/common/src/commands/system/get_clients.rs b/core/common/src/commands/system/get_clients.rs
deleted file mode 100644
index 4fdbad67b..000000000
--- a/core/common/src/commands/system/get_clients.rs
+++ /dev/null
@@ -1,86 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_CLIENTS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetClients` command is used to get the information about all connected clients.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetClients {}
-
-impl Command for GetClients {
-    fn code(&self) -> u32 {
-        GET_CLIENTS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetClients {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetClients {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetClients, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetClients {})
-    }
-}
-
-impl Display for GetClients {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetClients {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetClients::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetClients::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/system/get_cluster_metadata.rs b/core/common/src/commands/system/get_cluster_metadata.rs
deleted file mode 100644
index ad9693ab3..000000000
--- a/core/common/src/commands/system/get_cluster_metadata.rs
+++ /dev/null
@@ -1,59 +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::error::IggyError;
-use crate::{BytesSerializable, Command, GET_CLUSTER_METADATA_CODE, Validatable};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetClusterMetadata` command is used to retrieve cluster metadata including
-/// available nodes, their roles, and connection information.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetClusterMetadata {}
-
-impl Command for GetClusterMetadata {
-    fn code(&self) -> u32 {
-        GET_CLUSTER_METADATA_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetClusterMetadata {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetClusterMetadata {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetClusterMetadata, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-        Ok(GetClusterMetadata {})
-    }
-}
-
-impl Display for GetClusterMetadata {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "GetClusterMetadata")
-    }
-}
diff --git a/core/common/src/commands/system/get_me.rs b/core/common/src/commands/system/get_me.rs
deleted file mode 100644
index 5b8d772cf..000000000
--- a/core/common/src/commands/system/get_me.rs
+++ /dev/null
@@ -1,86 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_ME_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetMe` command is used to get the information connected client.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetMe {}
-
-impl Command for GetMe {
-    fn code(&self) -> u32 {
-        GET_ME_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetMe {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetMe {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetMe, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetMe {})
-    }
-}
-
-impl Display for GetMe {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetMe {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetMe::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetMe::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/system/get_snapshot.rs b/core/common/src/commands/system/get_snapshot.rs
deleted file mode 100644
index 83a20809d..000000000
--- a/core/common/src/commands/system/get_snapshot.rs
+++ /dev/null
@@ -1,168 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_SNAPSHOT_FILE_CODE};
-use crate::{SnapshotCompression, SystemSnapshotType};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use tracing::error;
-
-/// `GetSnapshot` command is used to get snapshot information.
-#[derive(Debug, Serialize, Deserialize, PartialEq)]
-pub struct GetSnapshot {
-    pub snapshot_types: Vec<SystemSnapshotType>,
-    pub compression: SnapshotCompression,
-}
-
-impl Default for GetSnapshot {
-    fn default() -> Self {
-        let types = vec![
-            SystemSnapshotType::FilesystemOverview,
-            SystemSnapshotType::ProcessList,
-            SystemSnapshotType::ResourceUsage,
-            SystemSnapshotType::ServerLogs,
-        ];
-        Self {
-            compression: SnapshotCompression::Deflated,
-            snapshot_types: types,
-        }
-    }
-}
-
-impl Command for GetSnapshot {
-    fn code(&self) -> u32 {
-        GET_SNAPSHOT_FILE_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetSnapshot {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.snapshot_types.contains(&SystemSnapshotType::All) && self.snapshot_types.len() > 1 {
-            error!("When using 'All' snapshot type, no other types can be specified");
-            return Err(IggyError::InvalidCommand);
-        }
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetSnapshot {
-    fn to_bytes(&self) -> Bytes {
-        let mut bytes = BytesMut::new();
-        bytes.put_u8(self.compression.as_code());
-
-        bytes.put_u8(self.snapshot_types.len() as u8);
-        for snapshot_type in &self.snapshot_types {
-            bytes.put_u8(snapshot_type.as_code());
-        }
-
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetSnapshot, IggyError> {
-        let mut index = 0;
-
-        let compression =
-            SnapshotCompression::from_code(*bytes.get(index).ok_or(IggyError::InvalidCommand)?)?;
-        index += 1;
-
-        let types_count = *bytes.get(index).ok_or(IggyError::InvalidCommand)? as usize;
-        index += 1;
-
-        let mut snapshot_types = Vec::with_capacity(types_count);
-
-        for _ in 0..types_count {
-            let tool =
-                SystemSnapshotType::from_code(*bytes.get(index).ok_or(IggyError::InvalidCommand)?)?;
-            index += 1;
-
-            snapshot_types.push(tool);
-        }
-
-        Ok(GetSnapshot {
-            compression,
-            snapshot_types,
-        })
-    }
-}
-
-impl Display for GetSnapshot {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "GetSnapshotFile {{\n  snapshot_types: [\n")?;
-        for snapshot_type in &self.snapshot_types {
-            writeln!(f, "    {snapshot_type}")?;
-        }
-        write!(f, "  ],\n  Compression: {}\n}}", self.compression)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let get_snapshot_file_command = GetSnapshot {
-            compression: SnapshotCompression::Deflated,
-            snapshot_types: vec![SystemSnapshotType::FilesystemOverview],
-        };
-
-        let bytes = get_snapshot_file_command.to_bytes();
-
-        let deserialized = GetSnapshot::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-
-        assert_eq!(
-            deserialized.compression,
-            get_snapshot_file_command.compression
-        );
-        assert_eq!(
-            deserialized.snapshot_types,
-            get_snapshot_file_command.snapshot_types
-        );
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let types = vec![SystemSnapshotType::FilesystemOverview];
-
-        let mut bytes = BytesMut::new();
-        bytes.put_u8(SnapshotCompression::Deflated.as_code());
-        bytes.put_u8(1);
-
-        bytes.put_u8(types.len() as u8);
-        for t in &types {
-            bytes.put_u8(t.as_code());
-        }
-
-        let get_snapshot = GetSnapshot::from_bytes(bytes.freeze());
-        assert!(get_snapshot.is_ok());
-
-        let get_snapshot = get_snapshot.unwrap();
-        assert_eq!(get_snapshot.snapshot_types.len(), 1);
-        assert_eq!(get_snapshot.compression, SnapshotCompression::Deflated);
-        assert_eq!(
-            get_snapshot.snapshot_types[0],
-            SystemSnapshotType::FilesystemOverview
-        );
-    }
-}
diff --git a/core/common/src/commands/system/get_stats.rs b/core/common/src/commands/system/get_stats.rs
deleted file mode 100644
index 8d0fea06f..000000000
--- a/core/common/src/commands/system/get_stats.rs
+++ /dev/null
@@ -1,86 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_STATS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetStats` command is used to get the statistics about the system.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetStats {}
-
-impl Command for GetStats {
-    fn code(&self) -> u32 {
-        GET_STATS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetStats {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetStats {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetStats, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetStats {})
-    }
-}
-
-impl Display for GetStats {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetStats {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetStats::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetStats::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/system/ping.rs b/core/common/src/commands/system/ping.rs
deleted file mode 100644
index 2a7e1b130..000000000
--- a/core/common/src/commands/system/ping.rs
+++ /dev/null
@@ -1,87 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, PING_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `Ping` command is used to check if the server is alive.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct Ping {}
-
-impl Command for Ping {
-    fn code(&self) -> u32 {
-        PING_CODE
-    }
-}
-
-impl Validatable<IggyError> for Ping {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for Ping {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<Ping, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let command = Ping {};
-        Ok(command)
-    }
-}
-
-impl Display for Ping {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = Ping {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = Ping::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = Ping::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/topics/create_topic.rs b/core/common/src/commands/topics/create_topic.rs
deleted file mode 100644
index bd4d87f48..000000000
--- a/core/common/src/commands/topics/create_topic.rs
+++ /dev/null
@@ -1,301 +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 super::{MAX_NAME_LENGTH, MAX_PARTITIONS_COUNT};
-use crate::BytesSerializable;
-use crate::CompressionAlgorithm;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::utils::expiry::IggyExpiry;
-use crate::utils::topic_size::MaxTopicSize;
-use crate::{CREATE_TOPIC_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `CreateTopic` command is used to create a new topic in a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `partitions_count` - number of partitions in the topic, max value is 1000.
-/// - `message_expiry` - message expiry, if `NeverExpire` then messages will never expire.
-/// - `max_topic_size` - maximum size of the topic, if `Unlimited` then topic size is unlimited.
-///   Can't be lower than segment size in the config.
-/// - `replication_factor` - replication factor for the topic.
-/// - `name` - unique topic name, max length is 255 characters.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct CreateTopic {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Number of partitions in the topic, max value is 1000.
-    pub partitions_count: u32,
-    /// Compression algorithm for the topic.
-    pub compression_algorithm: CompressionAlgorithm,
-    /// Message expiry, if `NeverExpire` then messages will never expire.
-    pub message_expiry: IggyExpiry,
-    /// Max topic size, if `Unlimited` then topic size is unlimited.
-    /// Can't be lower than segment size in the config.
-    pub max_topic_size: MaxTopicSize,
-    /// Replication factor for the topic.
-    pub replication_factor: Option<u8>,
-    /// Unique topic name, max length is 255 characters.
-    pub name: String,
-}
-
-impl Command for CreateTopic {
-    fn code(&self) -> u32 {
-        CREATE_TOPIC_CODE
-    }
-}
-
-impl Default for CreateTopic {
-    fn default() -> Self {
-        CreateTopic {
-            stream_id: Identifier::default(),
-            partitions_count: 1,
-            compression_algorithm: CompressionAlgorithm::None,
-            message_expiry: IggyExpiry::NeverExpire,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: None,
-            name: "topic".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreateTopic {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
-            return Err(IggyError::InvalidTopicName);
-        }
-
-        if !(0..=MAX_PARTITIONS_COUNT).contains(&self.partitions_count) {
-            return Err(IggyError::TooManyPartitions);
-        }
-
-        if let Some(replication_factor) = self.replication_factor
-            && replication_factor == 0
-        {
-            return Err(IggyError::InvalidReplicationFactor);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreateTopic {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(19 + stream_id_bytes.len() + self.name.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_u32_le(self.partitions_count);
-        bytes.put_u8(self.compression_algorithm.as_code());
-        bytes.put_u64_le(self.message_expiry.into());
-        bytes.put_u64_le(self.max_topic_size.into());
-        match self.replication_factor {
-            Some(replication_factor) => bytes.put_u8(replication_factor),
-            None => bytes.put_u8(0),
-        }
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<CreateTopic, IggyError> {
-        if bytes.len() < 14 {
-            return Err(IggyError::InvalidCommand);
-        }
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let partitions_count = u32::from_le_bytes(
-            bytes
-                .get(position..position + 4)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let compression_algorithm = CompressionAlgorithm::from_code(
-            *bytes.get(position + 4).ok_or(IggyError::InvalidCommand)?,
-        )?;
-        let message_expiry = u64::from_le_bytes(
-            bytes
-                .get(position + 5..position + 13)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let message_expiry: IggyExpiry = message_expiry.into();
-        let max_topic_size = u64::from_le_bytes(
-            bytes
-                .get(position + 13..position + 21)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let max_topic_size: MaxTopicSize = max_topic_size.into();
-        let replication_factor = match *bytes.get(position + 21).ok_or(IggyError::InvalidCommand)? {
-            0 => None,
-            factor => Some(factor),
-        };
-        let name_length = *bytes.get(position + 22).ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(position + 23..position + 23 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-        let command = CreateTopic {
-            stream_id,
-            partitions_count,
-            compression_algorithm,
-            message_expiry,
-            max_topic_size,
-            replication_factor,
-            name,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for CreateTopic {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}|{}|{}",
-            self.stream_id,
-            self.partitions_count,
-            self.message_expiry,
-            self.max_topic_size,
-            self.replication_factor.unwrap_or(0),
-            self.name
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use bytes::BufMut;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreateTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            partitions_count: 3,
-            message_expiry: IggyExpiry::NeverExpire,
-            compression_algorithm: CompressionAlgorithm::None,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: Some(1),
-            name: "test".to_string(),
-        };
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let partitions_count =
-            u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-        let compression_algorithm = CompressionAlgorithm::from_code(bytes[position + 4]).unwrap();
-        let message_expiry =
-            u64::from_le_bytes(bytes[position + 5..position + 13].try_into().unwrap());
-        let message_expiry: IggyExpiry = message_expiry.into();
-        let max_topic_size =
-            u64::from_le_bytes(bytes[position + 13..position + 21].try_into().unwrap());
-        let max_topic_size: MaxTopicSize = max_topic_size.into();
-        let replication_factor = bytes[position + 21];
-        let name_length = bytes[position + 22];
-        let name = from_utf8(&bytes[position + 23..(position + 23 + name_length as usize)])
-            .unwrap()
-            .to_string();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(partitions_count, command.partitions_count);
-        assert_eq!(compression_algorithm, command.compression_algorithm);
-        assert_eq!(message_expiry, command.message_expiry);
-        assert_eq!(max_topic_size, command.max_topic_size);
-        assert_eq!(replication_factor, command.replication_factor.unwrap());
-        assert_eq!(name.len() as u8, command.name.len() as u8);
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(CreateTopic::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = CreateTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            partitions_count: 3,
-            compression_algorithm: CompressionAlgorithm::None,
-            message_expiry: IggyExpiry::NeverExpire,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: Some(1),
-            name: "test".to_string(),
-        };
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                CreateTopic::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let partitions_count = 3u32;
-        let compression_algorithm = CompressionAlgorithm::None;
-        let name = "test".to_string();
-        let message_expiry = IggyExpiry::NeverExpire;
-        let max_topic_size = MaxTopicSize::ServerDefault;
-        let replication_factor = 1;
-        let stream_id_bytes = stream_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(10 + stream_id_bytes.len() + name.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_u32_le(partitions_count);
-        bytes.put_u8(compression_algorithm.as_code());
-        bytes.put_u64_le(message_expiry.into());
-        bytes.put_u64_le(max_topic_size.as_bytes_u64());
-        bytes.put_u8(replication_factor);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-
-        let command = CreateTopic::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.name, name);
-        assert_eq!(command.partitions_count, partitions_count);
-        assert_eq!(command.compression_algorithm, compression_algorithm);
-        assert_eq!(command.message_expiry, message_expiry);
-        assert_eq!(command.max_topic_size, max_topic_size);
-        assert_eq!(command.replication_factor.unwrap(), replication_factor);
-        assert_eq!(command.partitions_count, partitions_count);
-    }
-}
diff --git a/core/common/src/commands/topics/delete_topic.rs b/core/common/src/commands/topics/delete_topic.rs
deleted file mode 100644
index 45680311d..000000000
--- a/core/common/src/commands/topics/delete_topic.rs
+++ /dev/null
@@ -1,126 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_TOPIC_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteTopic` command is used to delete a topic from a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct DeleteTopic {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-}
-
-impl Command for DeleteTopic {
-    fn code(&self) -> u32 {
-        DELETE_TOPIC_CODE
-    }
-}
-
-impl Validatable<IggyError> for DeleteTopic {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteTopic {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<DeleteTopic, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = DeleteTopic {
-            stream_id,
-            topic_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteTopic {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.stream_id, self.topic_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use bytes::BufMut;
-
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let mut bytes = BytesMut::new();
-        bytes.put(stream_id.to_bytes());
-        bytes.put(topic_id.to_bytes());
-        let command = DeleteTopic::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-    }
-}
diff --git a/core/common/src/commands/topics/get_topic.rs b/core/common/src/commands/topics/get_topic.rs
deleted file mode 100644
index d18d07b42..000000000
--- a/core/common/src/commands/topics/get_topic.rs
+++ /dev/null
@@ -1,126 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_TOPIC_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetTopic` command is used to retrieve a topic from a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetTopic {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-}
-
-impl Command for GetTopic {
-    fn code(&self) -> u32 {
-        GET_TOPIC_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetTopic {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetTopic {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<GetTopic, IggyError> {
-        if bytes.len() < 6 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = GetTopic {
-            stream_id,
-            topic_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for GetTopic {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.stream_id, self.topic_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use bytes::BufMut;
-
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let mut bytes = BytesMut::new();
-        bytes.put(stream_id.to_bytes());
-        bytes.put(topic_id.to_bytes());
-        let command = GetTopic::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-    }
-}
diff --git a/core/common/src/commands/topics/get_topics.rs b/core/common/src/commands/topics/get_topics.rs
deleted file mode 100644
index fa49c570c..000000000
--- a/core/common/src/commands/topics/get_topics.rs
+++ /dev/null
@@ -1,99 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_TOPICS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetTopics` command is used to retrieve the collection of topics from a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetTopics {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-}
-
-impl Command for GetTopics {
-    fn code(&self) -> u32 {
-        GET_TOPICS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetTopics {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetTopics {
-    fn to_bytes(&self) -> Bytes {
-        self.stream_id.to_bytes()
-    }
-
-    fn from_bytes(bytes: Bytes) -> std::result::Result<GetTopics, IggyError> {
-        if bytes.len() < 3 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let stream_id = Identifier::from_bytes(bytes)?;
-        let command = GetTopics { stream_id };
-        Ok(command)
-    }
-}
-
-impl Display for GetTopics {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.stream_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetTopics {
-            stream_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let bytes = stream_id.to_bytes();
-        let command = GetTopics::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-    }
-}
diff --git a/core/common/src/commands/topics/purge_topic.rs b/core/common/src/commands/topics/purge_topic.rs
deleted file mode 100644
index 9473b38c7..000000000
--- a/core/common/src/commands/topics/purge_topic.rs
+++ /dev/null
@@ -1,124 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, PURGE_TOPIC_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `PurgeTopic` command is used to purge topic data (its messages in all the partitions) from a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct PurgeTopic {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-}
-
-impl Command for PurgeTopic {
-    fn code(&self) -> u32 {
-        PURGE_TOPIC_CODE
-    }
-}
-
-impl Validatable<IggyError> for PurgeTopic {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for PurgeTopic {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(stream_id_bytes.len() + topic_id_bytes.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<PurgeTopic, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        let command = PurgeTopic {
-            stream_id,
-            topic_id,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for PurgeTopic {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|{}", self.stream_id, self.topic_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = PurgeTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let mut bytes = BytesMut::new();
-        bytes.put_slice(&stream_id.to_bytes());
-        bytes.put_slice(&topic_id.to_bytes());
-        let command = PurgeTopic::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-    }
-}
diff --git a/core/common/src/commands/topics/update_topic.rs b/core/common/src/commands/topics/update_topic.rs
deleted file mode 100644
index 6f7bf5a02..000000000
--- a/core/common/src/commands/topics/update_topic.rs
+++ /dev/null
@@ -1,302 +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 super::MAX_NAME_LENGTH;
-use crate::BytesSerializable;
-use crate::CompressionAlgorithm;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::utils::expiry::IggyExpiry;
-use crate::utils::topic_size::MaxTopicSize;
-use crate::{Command, UPDATE_TOPIC_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `UpdateTopic` command is used to update a topic in a stream.
-/// It has additional payload:
-/// - `stream_id` - unique stream ID (numeric or name).
-/// - `topic_id` - unique topic ID (numeric or name).
-/// - `message_expiry` - message expiry, if `NeverExpire` then messages will never expire.
-/// - `max_topic_size` - maximum size of the topic in bytes, if `Unlimited` then topic size is unlimited.
-///   Can't be lower than segment size in the config.
-/// - `replication_factor` - replication factor for the topic.
-/// - `name` - unique topic name, max length is 255 characters.
-#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
-pub struct UpdateTopic {
-    /// Unique stream ID (numeric or name).
-    #[serde(skip)]
-    pub stream_id: Identifier,
-    /// Unique topic ID (numeric or name).
-    #[serde(skip)]
-    pub topic_id: Identifier,
-    /// Compression algorithm for the topic.
-    pub compression_algorithm: CompressionAlgorithm,
-    /// Message expiry, if `NeverExpire` then messages will never expire.
-    pub message_expiry: IggyExpiry,
-    /// Max topic size, if `Unlimited` then topic size is unlimited.
-    /// Can't be lower than segment size in the config.
-    pub max_topic_size: MaxTopicSize,
-    /// Replication factor for the topic.
-    pub replication_factor: Option<u8>,
-    /// Unique topic name, max length is 255 characters.
-    pub name: String,
-}
-
-impl Command for UpdateTopic {
-    fn code(&self) -> u32 {
-        UPDATE_TOPIC_CODE
-    }
-}
-
-impl Default for UpdateTopic {
-    fn default() -> Self {
-        UpdateTopic {
-            stream_id: Identifier::default(),
-            topic_id: Identifier::default(),
-            compression_algorithm: Default::default(),
-            message_expiry: IggyExpiry::NeverExpire,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: None,
-            name: "topic".to_string(),
-        }
-    }
-}
-
-impl Validatable<IggyError> for UpdateTopic {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.name.is_empty() || self.name.len() > MAX_NAME_LENGTH {
-            return Err(IggyError::InvalidTopicName);
-        }
-
-        if let Some(replication_factor) = self.replication_factor
-            && replication_factor == 0
-        {
-            return Err(IggyError::InvalidReplicationFactor);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for UpdateTopic {
-    fn to_bytes(&self) -> Bytes {
-        let stream_id_bytes = self.stream_id.to_bytes();
-        let topic_id_bytes = self.topic_id.to_bytes();
-        let mut bytes = BytesMut::with_capacity(
-            19 + stream_id_bytes.len() + topic_id_bytes.len() + self.name.len(),
-        );
-        bytes.put_slice(&stream_id_bytes.clone());
-        bytes.put_slice(&topic_id_bytes.clone());
-        bytes.put_u8(self.compression_algorithm.as_code());
-        bytes.put_u64_le(self.message_expiry.into());
-        bytes.put_u64_le(self.max_topic_size.into());
-        match self.replication_factor {
-            Some(replication_factor) => bytes.put_u8(replication_factor),
-            None => bytes.put_u8(0),
-        }
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.name.len() as u8);
-        bytes.put_slice(self.name.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<UpdateTopic, IggyError> {
-        if bytes.len() < 21 {
-            return Err(IggyError::InvalidCommand);
-        }
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone())?;
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..))?;
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let compression_algorithm = CompressionAlgorithm::from_code(
-            *bytes.get(position).ok_or(IggyError::InvalidCommand)?,
-        )?;
-        position += 1;
-        let message_expiry = u64::from_le_bytes(
-            bytes
-                .get(position..position + 8)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let message_expiry: IggyExpiry = message_expiry.into();
-        let max_topic_size = u64::from_le_bytes(
-            bytes
-                .get(position + 8..position + 16)
-                .ok_or(IggyError::InvalidCommand)?
-                .try_into()
-                .map_err(|_| IggyError::InvalidNumberEncoding)?,
-        );
-        let max_topic_size: MaxTopicSize = max_topic_size.into();
-        let replication_factor = match *bytes.get(position + 16).ok_or(IggyError::InvalidCommand)? {
-            0 => None,
-            factor => Some(factor),
-        };
-        let name_length = *bytes.get(position + 17).ok_or(IggyError::InvalidCommand)? as usize;
-        let name = from_utf8(
-            bytes
-                .get(position + 18..position + 18 + name_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-        let command = UpdateTopic {
-            stream_id,
-            topic_id,
-            compression_algorithm,
-            message_expiry,
-            max_topic_size,
-            replication_factor,
-            name,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for UpdateTopic {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "{}|{}|{}|{}|{}|{}",
-            self.stream_id,
-            self.topic_id,
-            self.message_expiry,
-            self.max_topic_size,
-            self.replication_factor.unwrap_or(0),
-            self.name,
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::utils::byte_size::IggyByteSize;
-    use bytes::BufMut;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = UpdateTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            compression_algorithm: CompressionAlgorithm::None,
-            message_expiry: IggyExpiry::NeverExpire,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: Some(1),
-            name: "test".to_string(),
-        };
-
-        let bytes = command.to_bytes();
-        let mut position = 0;
-        let stream_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        position += stream_id.get_size_bytes().as_bytes_usize();
-        let topic_id = Identifier::from_bytes(bytes.slice(position..)).unwrap();
-        position += topic_id.get_size_bytes().as_bytes_usize();
-        let compression_algorithm = CompressionAlgorithm::from_code(bytes[position]).unwrap();
-        position += 1;
-        let message_expiry = u64::from_le_bytes(bytes[position..position + 8].try_into().unwrap());
-        let message_expiry: IggyExpiry = message_expiry.into();
-        let max_topic_size =
-            u64::from_le_bytes(bytes[position + 8..position + 16].try_into().unwrap());
-        let max_topic_size: MaxTopicSize = max_topic_size.into();
-        let replication_factor = bytes[position + 16];
-        let name_length = bytes[position + 17];
-        let name = from_utf8(&bytes[position + 18..position + 18 + name_length as usize])
-            .unwrap()
-            .to_string();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(stream_id, command.stream_id);
-        assert_eq!(topic_id, command.topic_id);
-        assert_eq!(compression_algorithm, command.compression_algorithm);
-        assert_eq!(message_expiry, command.message_expiry);
-        assert_eq!(max_topic_size, command.max_topic_size);
-        assert_eq!(replication_factor, command.replication_factor.unwrap());
-        assert_eq!(name.len() as u8, command.name.len() as u8);
-        assert_eq!(name, command.name);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(UpdateTopic::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = UpdateTopic {
-            stream_id: Identifier::numeric(1).unwrap(),
-            topic_id: Identifier::numeric(2).unwrap(),
-            compression_algorithm: CompressionAlgorithm::None,
-            message_expiry: IggyExpiry::NeverExpire,
-            max_topic_size: MaxTopicSize::ServerDefault,
-            replication_factor: Some(1),
-            name: "test".to_string(),
-        };
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                UpdateTopic::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let stream_id = Identifier::numeric(1).unwrap();
-        let topic_id = Identifier::numeric(2).unwrap();
-        let compression_algorithm = CompressionAlgorithm::None;
-        let name = "test".to_string();
-        let message_expiry = IggyExpiry::NeverExpire;
-        let max_topic_size = MaxTopicSize::Custom(IggyByteSize::from(100));
-        let replication_factor = 1;
-
-        let stream_id_bytes = stream_id.to_bytes();
-        let topic_id_bytes = topic_id.to_bytes();
-        let mut bytes =
-            BytesMut::with_capacity(19 + stream_id_bytes.len() + topic_id_bytes.len() + name.len());
-        bytes.put_slice(&stream_id_bytes);
-        bytes.put_slice(&topic_id_bytes);
-        bytes.put_u8(compression_algorithm.as_code());
-        bytes.put_u64_le(message_expiry.into());
-        bytes.put_u64_le(max_topic_size.as_bytes_u64());
-        bytes.put_u8(replication_factor);
-
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(name.len() as u8);
-        bytes.put_slice(name.as_bytes());
-
-        let command = UpdateTopic::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.stream_id, stream_id);
-        assert_eq!(command.topic_id, topic_id);
-        assert_eq!(command.compression_algorithm, compression_algorithm);
-        assert_eq!(command.message_expiry, message_expiry);
-        assert_eq!(command.max_topic_size, max_topic_size);
-        assert_eq!(command.replication_factor, Some(replication_factor));
-        assert_eq!(command.name, name);
-    }
-}
diff --git a/core/common/src/commands/users/change_password.rs b/core/common/src/commands/users/change_password.rs
deleted file mode 100644
index 9d5272433..000000000
--- a/core/common/src/commands/users/change_password.rs
+++ /dev/null
@@ -1,246 +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 super::defaults::*;
-use crate::BytesSerializable;
-use crate::Identifier;
-use crate::Sizeable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{CHANGE_PASSWORD_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use secrecy::{ExposeSecret, SecretString};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `ChangePassword` command is used to change a user's password.
-/// It has additional payload:
-/// - `user_id` - unique user ID (numeric or name).
-/// - `current_password` - current password, must be between 3 and 100 characters long.
-/// - `new_password` - new password, must be between 3 and 100 characters long.
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct ChangePassword {
-    /// Unique user ID (numeric or name).
-    #[serde(skip)]
-    pub user_id: Identifier,
-    /// Current password, must be between 3 and 100 characters long.
-    #[serde(serialize_with = "crate::utils::serde_secret::serialize_secret")]
-    pub current_password: SecretString,
-    /// New password, must be between 3 and 100 characters long.
-    #[serde(serialize_with = "crate::utils::serde_secret::serialize_secret")]
-    pub new_password: SecretString,
-}
-
-impl Command for ChangePassword {
-    fn code(&self) -> u32 {
-        CHANGE_PASSWORD_CODE
-    }
-}
-
-impl Default for ChangePassword {
-    fn default() -> Self {
-        ChangePassword {
-            user_id: Identifier::default(),
-            current_password: SecretString::from("secret"),
-            new_password: SecretString::from("topsecret"),
-        }
-    }
-}
-
-impl Validatable<IggyError> for ChangePassword {
-    fn validate(&self) -> Result<(), IggyError> {
-        let current_password = self.current_password.expose_secret();
-        if current_password.is_empty()
-            || current_password.len() > MAX_PASSWORD_LENGTH
-            || current_password.len() < MIN_PASSWORD_LENGTH
-        {
-            return Err(IggyError::InvalidPassword);
-        }
-
-        let new_password = self.new_password.expose_secret();
-        if new_password.is_empty()
-            || new_password.len() > MAX_PASSWORD_LENGTH
-            || new_password.len() < MIN_PASSWORD_LENGTH
-        {
-            return Err(IggyError::InvalidPassword);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for ChangePassword {
-    fn to_bytes(&self) -> Bytes {
-        let user_id_bytes = self.user_id.to_bytes();
-        let current_password = self.current_password.expose_secret();
-        let new_password = self.new_password.expose_secret();
-        let mut bytes = BytesMut::new();
-        bytes.put_slice(&user_id_bytes);
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(current_password.len() as u8);
-        bytes.put_slice(current_password.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(new_password.len() as u8);
-        bytes.put_slice(new_password.as_bytes());
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<ChangePassword, IggyError> {
-        if bytes.len() < 9 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let user_id = Identifier::from_bytes(bytes.clone())?;
-        let mut position = user_id.get_size_bytes().as_bytes_usize();
-        let current_password_length = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        position += 1;
-        let current_password = from_utf8(
-            bytes
-                .get(position..position + current_password_length as usize)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-        position += current_password_length as usize;
-        let new_password_length = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        position += 1;
-        let new_password = from_utf8(
-            bytes
-                .get(position..position + new_password_length as usize)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let command = ChangePassword {
-            user_id,
-            current_password: SecretString::from(current_password),
-            new_password: SecretString::from(new_password),
-        };
-        Ok(command)
-    }
-}
-
-impl Display for ChangePassword {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|******|******", self.user_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = ChangePassword {
-            user_id: Identifier::numeric(1).unwrap(),
-            current_password: SecretString::from("user"),
-            new_password: SecretString::from("secret"),
-        };
-
-        let bytes = command.to_bytes();
-        let user_id = Identifier::from_bytes(bytes.clone()).unwrap();
-        let mut position = user_id.get_size_bytes().as_bytes_usize();
-        let current_password_length = bytes[position];
-        position += 1;
-        let current_password =
-            from_utf8(&bytes[position..position + current_password_length as usize]).unwrap();
-        position += current_password_length as usize;
-        let new_password_length = bytes[position];
-        position += 1;
-        let new_password =
-            from_utf8(&bytes[position..position + new_password_length as usize]).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(user_id, command.user_id);
-        assert_eq!(current_password, command.current_password.expose_secret());
-        assert_eq!(new_password, command.new_password.expose_secret());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(ChangePassword::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = ChangePassword::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                ChangePassword::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let user_id = Identifier::numeric(1).unwrap();
-        let current_password = "secret";
-        let new_password = "topsecret";
-        let mut bytes = BytesMut::new();
-        bytes.put_slice(&user_id.to_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(current_password.len() as u8);
-        bytes.put_slice(current_password.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(new_password.len() as u8);
-        bytes.put_slice(new_password.as_bytes());
-
-        let command = ChangePassword::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.user_id, user_id);
-        assert_eq!(command.current_password.expose_secret(), current_password);
-        assert_eq!(command.new_password.expose_secret(), new_password);
-    }
-
-    #[test]
-    fn should_fail_validation_for_invalid_current_password() {
-        for password in ["", "ab"] {
-            let command = ChangePassword {
-                current_password: SecretString::from(password),
-                ..ChangePassword::default()
-            };
-            assert!(
-                command.validate().is_err(),
-                "expected validation error for current_password: {password:?}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_fail_validation_for_empty_new_password() {
-        let command = ChangePassword {
-            new_password: SecretString::from(""),
-            ..ChangePassword::default()
-        };
-        assert!(command.validate().is_err());
-    }
-
-    #[test]
-    fn should_pass_validation_for_valid_command() {
-        let command = ChangePassword::default();
-        assert!(command.validate().is_ok());
-    }
-}
diff --git a/core/common/src/commands/users/create_user.rs b/core/common/src/commands/users/create_user.rs
deleted file mode 100644
index 3405bd486..000000000
--- a/core/common/src/commands/users/create_user.rs
+++ /dev/null
@@ -1,347 +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 super::defaults::*;
-use crate::BytesSerializable;
-use crate::Permissions;
-use crate::UserStatus;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{CREATE_USER_CODE, Command};
-use bytes::{BufMut, Bytes, BytesMut};
-use secrecy::{ExposeSecret, SecretString};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `CreateUser` command is used to create a new user.
-/// It has additional payload:
-/// - `username` - unique name of the user, must be between 3 and 50 characters long.
-/// - `password` - password of the user, must be between 3 and 100 characters long.
-/// - `status` - status of the user, can be either `active` or `inactive`.
-/// - `permissions` - optional permissions of the user. If not provided, user will have no permissions.
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct CreateUser {
-    /// Unique name of the user, must be between 3 and 50 characters long.
-    pub username: String,
-    /// Password of the user, must be between 3 and 100 characters long.
-    #[serde(serialize_with = "crate::utils::serde_secret::serialize_secret")]
-    pub password: SecretString,
-    /// Status of the user, can be either `active` or `inactive`.
-    pub status: UserStatus,
-    /// Optional permissions of the user. If not provided, user will have no permissions.
-    pub permissions: Option<Permissions>,
-}
-
-impl Command for CreateUser {
-    fn code(&self) -> u32 {
-        CREATE_USER_CODE
-    }
-}
-
-impl Default for CreateUser {
-    fn default() -> Self {
-        CreateUser {
-            username: "user".to_string(),
-            password: SecretString::from("secret"),
-            status: UserStatus::Active,
-            permissions: None,
-        }
-    }
-}
-
-impl Validatable<IggyError> for CreateUser {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.username.is_empty()
-            || self.username.len() > MAX_USERNAME_LENGTH
-            || self.username.len() < MIN_USERNAME_LENGTH
-        {
-            return Err(IggyError::InvalidUsername);
-        }
-
-        let password = self.password.expose_secret();
-        if password.is_empty()
-            || password.len() > MAX_PASSWORD_LENGTH
-            || password.len() < MIN_PASSWORD_LENGTH
-        {
-            return Err(IggyError::InvalidPassword);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for CreateUser {
-    fn to_bytes(&self) -> Bytes {
-        let password = self.password.expose_secret();
-        let mut bytes = BytesMut::with_capacity(2 + self.username.len() + password.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.username.len() as u8);
-        bytes.put_slice(self.username.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(password.len() as u8);
-        bytes.put_slice(password.as_bytes());
-        bytes.put_u8(self.status.as_code());
-        if let Some(permissions) = &self.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_u8(0);
-        }
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<CreateUser, IggyError> {
-        if bytes.len() < 10 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let username_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let username = from_utf8(
-            bytes
-                .get(1..1 + username_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let mut position = 1 + username_length;
-        let password_length = *bytes.get(position).ok_or(IggyError::InvalidCommand)? as usize;
-        position += 1;
-        let password = from_utf8(
-            bytes
-                .get(position..position + password_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        position += password_length;
-        let status = UserStatus::from_code(*bytes.get(position).ok_or(IggyError::InvalidCommand)?)?;
-        position += 1;
-        let has_permissions = *bytes.get(position).ok_or(IggyError::InvalidCommand)?;
-        if has_permissions > 1 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        position += 1;
-        let permissions = if has_permissions == 1 {
-            let permissions_length = u32::from_le_bytes(
-                bytes
-                    .get(position..position + 4)
-                    .ok_or(IggyError::InvalidCommand)?
-                    .try_into()
-                    .map_err(|_| IggyError::InvalidNumberEncoding)?,
-            );
-            position += 4;
-            let end = position
-                .checked_add(permissions_length as usize)
-                .ok_or(IggyError::InvalidCommand)?;
-            if end > bytes.len() {
-                return Err(IggyError::InvalidCommand);
-            }
-            Some(Permissions::from_bytes(bytes.slice(position..end))?)
-        } else {
-            None
-        };
-
-        let command = CreateUser {
-            username,
-            password: SecretString::from(password),
-            status,
-            permissions,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for CreateUser {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let permissions = if let Some(permissions) = &self.permissions {
-            permissions.to_string()
-        } else {
-            "no_permissions".to_string()
-        };
-        write!(
-            f,
-            "{}|******|{}|{}",
-            self.username, self.status, permissions
-        )
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::GlobalPermissions;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = CreateUser {
-            username: "user".to_string(),
-            password: SecretString::from("secret"),
-            status: UserStatus::Active,
-            permissions: Some(Permissions {
-                global: GlobalPermissions {
-                    manage_servers: false,
-                    read_servers: true,
-                    manage_users: false,
-                    read_users: true,
-                    manage_streams: false,
-                    read_streams: true,
-                    manage_topics: false,
-                    read_topics: true,
-                    poll_messages: true,
-                    send_messages: true,
-                },
-                streams: None,
-            }),
-        };
-
-        let bytes = command.to_bytes();
-        let username_length = bytes[0];
-        let username = from_utf8(&bytes[1..1 + username_length as usize]).unwrap();
-        let mut position = 1 + username_length as usize;
-        let password_length = bytes[position];
-        position += 1;
-        let password = from_utf8(&bytes[position..position + password_length as usize]).unwrap();
-        position += password_length as usize;
-        let status = UserStatus::from_code(bytes[position]).unwrap();
-        position += 1;
-        let has_permissions = bytes[3 + username_length as usize + password_length as usize];
-        position += 1;
-
-        let permissions_length =
-            u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-        position += 4;
-        let permissions =
-            Permissions::from_bytes(bytes.slice(position..position + permissions_length as usize))
-                .unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(username, command.username);
-        assert_eq!(password, command.password.expose_secret());
-        assert_eq!(status, command.status);
-        assert_eq!(has_permissions, 1);
-        assert_eq!(permissions, command.permissions.unwrap());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(CreateUser::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = CreateUser::default();
-        let bytes = command.to_bytes();
-        for i in 0..bytes.len() - 1 {
-            let truncated = bytes.slice(..i);
-            assert!(
-                CreateUser::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_corrupted_username_length() {
-        let mut buf = BytesMut::new();
-        buf.put_u8(255);
-        buf.put_slice(b"short");
-        assert!(CreateUser::from_bytes(buf.freeze()).is_err());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let username = "user";
-        let password = "secret";
-        let status = UserStatus::Active;
-        let has_permissions = 1u8;
-        let permissions = Permissions {
-            global: GlobalPermissions {
-                manage_servers: false,
-                read_servers: true,
-                manage_users: false,
-                read_users: true,
-                manage_streams: false,
-                read_streams: true,
-                manage_topics: false,
-                read_topics: true,
-                poll_messages: true,
-                send_messages: true,
-            },
-            streams: None,
-        };
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(username.len() as u8);
-        bytes.put_slice(username.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(password.len() as u8);
-        bytes.put_slice(password.as_bytes());
-        bytes.put_u8(status.as_code());
-        bytes.put_u8(has_permissions);
-        let permissions_bytes = permissions.to_bytes();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u32_le(permissions_bytes.len() as u32);
-        bytes.put_slice(&permissions_bytes);
-
-        let command = CreateUser::from_bytes(bytes.freeze());
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.username, username);
-        assert_eq!(command.password.expose_secret(), password);
-        assert_eq!(command.status, status);
-        assert!(command.permissions.is_some());
-        assert_eq!(command.permissions.unwrap(), permissions);
-    }
-
-    #[test]
-    fn should_fail_validation_for_empty_username() {
-        let command = CreateUser {
-            username: "".to_string(),
-            ..CreateUser::default()
-        };
-        assert!(command.validate().is_err());
-    }
-
-    #[test]
-    fn should_fail_validation_for_invalid_password() {
-        for password in ["", "ab"] {
-            let command = CreateUser {
-                password: SecretString::from(password),
-                ..CreateUser::default()
-            };
-            assert!(
-                command.validate().is_err(),
-                "expected validation error for password: {password:?}"
-            );
-        }
-    }
-
-    #[test]
-    fn should_pass_validation_for_valid_command() {
-        let command = CreateUser::default();
-        assert!(command.validate().is_ok());
-    }
-}
diff --git a/core/common/src/commands/users/delete_user.rs b/core/common/src/commands/users/delete_user.rs
deleted file mode 100644
index 3cc5682a6..000000000
--- a/core/common/src/commands/users/delete_user.rs
+++ /dev/null
@@ -1,99 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, DELETE_USER_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `DeleteUser` command is used to delete a user by unique ID.
-/// It has additional payload:
-/// - `user_id` - unique user ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
-pub struct DeleteUser {
-    /// Unique user ID (numeric or name).
-    #[serde(skip)]
-    pub user_id: Identifier,
-}
-
-impl Command for DeleteUser {
-    fn code(&self) -> u32 {
-        DELETE_USER_CODE
-    }
-}
-
-impl Validatable<IggyError> for DeleteUser {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for DeleteUser {
-    fn to_bytes(&self) -> Bytes {
-        self.user_id.to_bytes()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<DeleteUser, IggyError> {
-        if bytes.len() < 3 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let user_id = Identifier::from_bytes(bytes)?;
-        let command = DeleteUser { user_id };
-        Ok(command)
-    }
-}
-
-impl Display for DeleteUser {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.user_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = DeleteUser {
-            user_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let user_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(user_id, command.user_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let user_id = Identifier::numeric(1).unwrap();
-        let bytes = user_id.to_bytes();
-        let command = DeleteUser::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.user_id, user_id);
-    }
-}
diff --git a/core/common/src/commands/users/get_user.rs b/core/common/src/commands/users/get_user.rs
deleted file mode 100644
index b0082f3b5..000000000
--- a/core/common/src/commands/users/get_user.rs
+++ /dev/null
@@ -1,99 +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::BytesSerializable;
-use crate::Identifier;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_USER_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetUser` command is used to retrieve the information about a user by unique ID.
-/// It has additional payload:
-/// - `user_id` - unique user ID (numeric or name).
-#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
-pub struct GetUser {
-    #[serde(skip)]
-    /// Unique user ID (numeric or name).
-    pub user_id: Identifier,
-}
-
-impl Command for GetUser {
-    fn code(&self) -> u32 {
-        GET_USER_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetUser {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetUser {
-    fn to_bytes(&self) -> Bytes {
-        self.user_id.to_bytes()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetUser, IggyError> {
-        if bytes.len() < 3 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let user_id = Identifier::from_bytes(bytes)?;
-        let command = GetUser { user_id };
-        Ok(command)
-    }
-}
-
-impl Display for GetUser {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.user_id)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = GetUser {
-            user_id: Identifier::numeric(1).unwrap(),
-        };
-
-        let bytes = command.to_bytes();
-        let user_id = Identifier::from_bytes(bytes.clone()).unwrap();
-
-        assert!(!bytes.is_empty());
-        assert_eq!(user_id, command.user_id);
-    }
-
-    #[test]
-    fn should_be_deserialized_from_bytes() {
-        let user_id = Identifier::numeric(1).unwrap();
-        let bytes = user_id.to_bytes();
-        let command = GetUser::from_bytes(bytes);
-        assert!(command.is_ok());
-
-        let command = command.unwrap();
-        assert_eq!(command.user_id, user_id);
-    }
-}
diff --git a/core/common/src/commands/users/get_users.rs b/core/common/src/commands/users/get_users.rs
deleted file mode 100644
index dc40bd1cb..000000000
--- a/core/common/src/commands/users/get_users.rs
+++ /dev/null
@@ -1,88 +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::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, GET_USERS_CODE};
-use bytes::Bytes;
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-
-/// `GetUsers` command is used to retrieve the information about all users.
-/// It has no additional payload.
-#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
-pub struct GetUsers {}
-
-impl Command for GetUsers {
-    fn code(&self) -> u32 {
-        GET_USERS_CODE
-    }
-}
-
-impl Validatable<IggyError> for GetUsers {
-    fn validate(&self) -> Result<(), IggyError> {
-        Ok(())
-    }
-}
-
-impl BytesSerializable for GetUsers {
-    fn to_bytes(&self) -> Bytes {
-        Bytes::new()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<GetUsers, IggyError> {
-        if !bytes.is_empty() {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        Ok(GetUsers {})
-    }
-}
-
-impl Display for GetUsers {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "")
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use bytes::Bytes;
-
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_empty_bytes() {
-        let command = GetUsers {};
-        let bytes = command.to_bytes();
-        assert!(bytes.is_empty());
-    }
-
-    #[test]
-    fn should_be_deserialized_from_empty_bytes() {
-        let command = GetUsers::from_bytes(Bytes::new());
-        assert!(command.is_ok());
-    }
-
-    #[test]
-    fn should_not_be_deserialized_from_empty_bytes() {
-        let command = GetUsers::from_bytes(Bytes::from_static(&[0]));
-        assert!(command.is_err());
-    }
-}
diff --git a/core/common/src/commands/users/login_user.rs b/core/common/src/commands/users/login_user.rs
deleted file mode 100644
index ce729d1c0..000000000
--- a/core/common/src/commands/users/login_user.rs
+++ /dev/null
@@ -1,399 +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 super::defaults::*;
-use crate::BytesSerializable;
-use crate::Validatable;
-use crate::error::IggyError;
-use crate::{Command, LOGIN_USER_CODE};
-use bytes::{BufMut, Bytes, BytesMut};
-use secrecy::{ExposeSecret, SecretString};
-use serde::{Deserialize, Serialize};
-use std::fmt::Display;
-use std::str::from_utf8;
-
-/// `LoginUser` command is used to login a user by username and password.
-/// It has additional payload:
-/// - `username` - username, must be between 3 and 50 characters long.
-/// - `password` - password, must be between 3 and 100 characters long.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct LoginUser {
-    /// Username, must be between 3 and 50 characters long.
-    pub username: String,
-    /// Password, must be between 3 and 100 characters long.
-    #[serde(serialize_with = "crate::utils::serde_secret::serialize_secret")]
-    pub password: SecretString,
-    // Version metadata added by SDK.
-    pub version: Option<String>,
-    // Context metadata added by SDK.
-    pub context: Option<String>,
-}
-
-impl Command for LoginUser {
-    fn code(&self) -> u32 {
-        LOGIN_USER_CODE
-    }
-}
-
-impl Default for LoginUser {
-    fn default() -> Self {
-        LoginUser {
-            username: "user".to_string(),
-            password: SecretString::from("secret"),
-            version: None,
-            context: None,
-        }
-    }
-}
-
-impl Validatable<IggyError> for LoginUser {
-    fn validate(&self) -> Result<(), IggyError> {
-        if self.username.is_empty()
-            || self.username.len() > MAX_USERNAME_LENGTH
-            || self.username.len() < MIN_USERNAME_LENGTH
-        {
-            return Err(IggyError::InvalidUsername);
-        }
-
-        let password = self.password.expose_secret();
-        if password.is_empty()
-            || password.len() > MAX_PASSWORD_LENGTH
-            || password.len() < MIN_PASSWORD_LENGTH
-        {
-            return Err(IggyError::InvalidPassword);
-        }
-
-        Ok(())
-    }
-}
-
-impl BytesSerializable for LoginUser {
-    fn to_bytes(&self) -> Bytes {
-        let password = self.password.expose_secret();
-        let mut bytes = BytesMut::with_capacity(2 + self.username.len() + password.len());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(self.username.len() as u8);
-        bytes.put_slice(self.username.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(password.len() as u8);
-        bytes.put_slice(password.as_bytes());
-        match &self.version {
-            Some(version) => {
-                bytes.put_u32_le(version.len() as u32);
-                bytes.put_slice(version.as_bytes());
-            }
-            None => {
-                bytes.put_u32_le(0);
-            }
-        }
-        match &self.context {
-            Some(context) => {
-                bytes.put_u32_le(context.len() as u32);
-                bytes.put_slice(context.as_bytes());
-            }
-            None => {
-                bytes.put_u32_le(0);
-            }
-        }
-        bytes.freeze()
-    }
-
-    fn from_bytes(bytes: Bytes) -> Result<LoginUser, IggyError> {
-        if bytes.len() < 4 {
-            return Err(IggyError::InvalidCommand);
-        }
-
-        let username_length = *bytes.first().ok_or(IggyError::InvalidCommand)? as usize;
-        let username = from_utf8(
-            bytes
-                .get(1..1 + username_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let pos = 1 + username_length;
-        let password_length = *bytes.get(pos).ok_or(IggyError::InvalidCommand)? as usize;
-        let password = from_utf8(
-            bytes
-                .get(pos + 1..pos + 1 + password_length)
-                .ok_or(IggyError::InvalidCommand)?,
-        )
-        .map_err(|_| IggyError::InvalidUtf8)?
-        .to_string();
-
-        let mut position = pos + 1 + password_length;
-
-        // Version and context fields are optional for backward compatibility
-        // with older SDKs (e.g. v0.8.0) that don't send them.
-        // However, 1-3 trailing bytes (incomplete u32 length prefix) are rejected
-        // as they indicate a corrupt payload rather than a valid old-SDK format.
-        let remaining = bytes.len() - position;
-        let version = if remaining == 0 {
-            None
-        } else if remaining < 4 {
-            return Err(IggyError::InvalidCommand);
-        } else {
-            let version_length = u32::from_le_bytes(
-                bytes
-                    .get(position..position + 4)
-                    .ok_or(IggyError::InvalidCommand)?
-                    .try_into()
-                    .map_err(|_| IggyError::InvalidNumberEncoding)?,
-            );
-            position += 4;
-            match version_length {
-                0 => None,
-                _ => {
-                    let version = from_utf8(
-                        bytes
-                            .get(position..position + version_length as usize)
-                            .ok_or(IggyError::InvalidCommand)?,
-                    )
-                    .map_err(|_| IggyError::InvalidUtf8)?
-                    .to_string();
-                    position += version_length as usize;
-                    Some(version)
-                }
-            }
-        };
-
-        let remaining = bytes.len() - position;
-        let context = if remaining == 0 {
-            None
-        } else if remaining < 4 {
-            return Err(IggyError::InvalidCommand);
-        } else {
-            let context_length = u32::from_le_bytes(
-                bytes
-                    .get(position..position + 4)
-                    .ok_or(IggyError::InvalidCommand)?
-                    .try_into()
-                    .map_err(|_| IggyError::InvalidNumberEncoding)?,
-            );
-            position += 4;
-            match context_length {
-                0 => None,
-                _ => {
-                    let context = from_utf8(
-                        bytes
-                            .get(position..position + context_length as usize)
-                            .ok_or(IggyError::InvalidCommand)?,
-                    )
-                    .map_err(|_| IggyError::InvalidUtf8)?
-                    .to_string();
-                    Some(context)
-                }
-            }
-        };
-
-        let command = LoginUser {
-            username,
-            password: SecretString::from(password),
-            version,
-            context,
-        };
-        Ok(command)
-    }
-}
-
-impl Display for LoginUser {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}|******", self.username)
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn should_be_serialized_as_bytes() {
-        let command = LoginUser {
-            username: "user".to_string(),
-            password: SecretString::from("secret"),
-            version: Some("1.0.0".to_string()),
-            context: Some("test".to_string()),
-        };
-
-        let bytes = command.to_bytes();
-        let username_length = bytes[0];
-        let username = from_utf8(&bytes[1..=(username_length as usize)]).unwrap();
-        let password_length = bytes[1 + username_length as usize];
-        let password = from_utf8(
-            &bytes[2 + username_length as usize
-                ..2 + username_length as usize + password_length as usize],
-        )
-        .unwrap();
-        let position = 2 + username_length as usize + password_length as usize;
-        let version_length = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-        let version = Some(
-            from_utf8(&bytes[position + 4..position + 4 + version_length as usize])
-                .unwrap()
-                .to_string(),
-        );
-        let position = position + 4 + version_length as usize;
-        let context_length = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
-        let context = Some(
-            from_utf8(&bytes[position + 4..position + 4 + context_length as usize])
-                .unwrap()
-                .to_string(),
-        );
-
-        assert!(!bytes.is_empty());
-        assert_eq!(username, command.username);
-        assert_eq!(password, command.password.expose_secret());
-        assert_eq!(version, command.version);
-        assert_eq!(context, command.context);
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_empty_input() {
-        assert!(LoginUser::from_bytes(Bytes::new()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_truncated_input() {
-        let command = LoginUser {
-            username: "user".to_string(),
-            password: SecretString::from("secret"),
-            version: Some("1.0.0".to_string()),
-            context: Some("test".to_string()),
-        };
-        let bytes = command.to_bytes();
-        // Truncate at every position up to (but not including) the version field.
-        // Positions within username/password must error; positions at or past the
-        // version boundary are valid old-SDK payloads.
-        let version_offset = 2 + command.username.len() + command.password.expose_secret().len();
-        for i in 0..version_offset {
-            let truncated = bytes.slice(..i);
-            assert!(
-                LoginUser::from_bytes(truncated).is_err(),
-                "expected error for truncation at byte {i}"
-            );
-        }
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_corrupted_username_length() {
-        let mut buf = BytesMut::new();
-        buf.put_u8(255); // username_length = 255
-        buf.put_slice(b"short");
-        assert!(LoginUser::from_bytes(buf.freeze()).is_err());
-    }
-
-    #[test]
-    fn from_bytes_should_fail_on_trailing_bytes() {
-        let username = "user";
-        let password = "secret";
-        let mut bytes = BytesMut::new();
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(username.len() as u8);
-        bytes.put_slice(username.as_bytes());
-        #[allow(clippy::cast_possible_truncation)]
-        bytes.put_u8(password.len() as u8);
-        bytes.put_slice(password.as_bytes());
-
-        // 1-3 trailing bytes (incomplete u32 length prefix) must be rejected
-        for extra in 1..=3u8 {
-            let mut buf = bytes.clone();
-            for i in 0..extra {
-                buf.put_u8(i);
-            }
-            assert!(
-                LoginUser::from_bytes(buf.freeze()).is_err(),
-                "expected error for {extra} trailing byte(s)"
-            );
... 14381 lines suppressed ...