You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iggy.apache.org by hg...@apache.org on 2026/07/02 22:08:46 UTC
(iggy) 39/43: fix(server-ng): frame raw PAT reply with a result section
This is an automated email from the ASF dual-hosted git repository.
hubcio pushed a commit to branch http-listener-rebased
in repository https://gitbox.apache.org/repos/asf/iggy.git
commit 4cadd9e9b88719bde5a8ac4c4054294862579439
Author: Hubert Gruszecki <h....@gmail.com>
AuthorDate: Thu Jul 2 22:19:32 2026 +0200
fix(server-ng): frame raw PAT reply with a result section
The spliced CreatePersonalAccessToken reply carried a bare
RawPersonalAccessTokenResponse body, but the SDK strips a leading
result section from every metadata reply (split_metadata_result), so a
successful create over the binary transports decoded the token bytes
as a garbage result code and failed. Prefix the success section like
any committed metadata reply and strip it in the HTTP token decode.
Pre-existing; surfaced by the authentication scenario once the
duplicate-name path stopped masking replies.
---
core/server-ng/src/http.rs | 13 ++++++-------
core/server-ng/src/responses.rs | 22 ++++++++++++++++------
2 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/core/server-ng/src/http.rs b/core/server-ng/src/http.rs
index 1f5579505..0fd2b846b 100644
--- a/core/server-ng/src/http.rs
+++ b/core/server-ng/src/http.rs
@@ -3000,14 +3000,13 @@ fn decode_consumer_group_details(payload: &[u8]) -> Result<ConsumerGroupDetails,
Ok(ConsumerGroupDetails::from(response))
}
-/// Extract the raw one-time token from a [`build_raw_pat_reply`] output. That
-/// reply's body is a bare `RawPersonalAccessTokenResponse` (no leading result
-/// section, unlike a committed metadata reply), so decode it straight past the
-/// header.
+/// Extract the raw one-time token from a [`build_raw_pat_reply`] output. The
+/// spliced reply is framed like any committed metadata reply (a success result
+/// section, then the `RawPersonalAccessTokenResponse`) so the SDK's
+/// `split_metadata_result` can decode it; strip the section the same way here.
fn decode_raw_pat_token(reply: &Message<GenericHeader>) -> Result<String, WriteError> {
- let size = reply.header().size as usize;
- let body = reply.as_slice().get(HEADER_SIZE..size).unwrap_or_default();
- let response = RawPersonalAccessTokenResponse::decode_from(body)
+ let payload = committed_payload(reply)?;
+ let response = RawPersonalAccessTokenResponse::decode_from(payload)
.map_err(|_| WriteError::Rejected(IggyError::InvalidCommand))?;
Ok(response.token.as_str().to_string())
}
diff --git a/core/server-ng/src/responses.rs b/core/server-ng/src/responses.rs
index e41659bd5..26946adb1 100644
--- a/core/server-ng/src/responses.rs
+++ b/core/server-ng/src/responses.rs
@@ -26,14 +26,14 @@
use crate::bootstrap::ServerNgShard;
use crate::session_manager::SessionManager;
use crate::wire::{transport_kind_to_wire, usize_to_u32};
-use bytes::Bytes;
+use bytes::{BufMut, Bytes, BytesMut};
use consensus::{MetadataHandle, VsrConsensus};
use iggy_binary_protocol::codes::{
GET_CLUSTER_METADATA_CODE, GET_CONSUMER_GROUP_CODE, GET_CONSUMER_GROUPS_CODE, GET_STATS_CODE,
GET_STREAM_CODE, GET_STREAMS_CODE, GET_TOPIC_CODE, GET_TOPICS_CODE, GET_USER_CODE,
GET_USERS_CODE,
};
-use iggy_binary_protocol::consensus::result_code;
+use iggy_binary_protocol::consensus::{RESULT_COUNT_LEN, result_code};
use iggy_binary_protocol::primitives::consumer::WireConsumer;
use iggy_binary_protocol::requests::consumer_groups::{
GetConsumerGroupRequest, GetConsumerGroupsRequest,
@@ -917,13 +917,20 @@ pub(crate) fn build_raw_pat_reply(
return Ok(committed);
}
let token = WireName::new(raw.as_str()).map_err(|_| IggyError::InvalidFormat)?;
- let body = RawPersonalAccessTokenResponse { token }.to_bytes();
+ let response = RawPersonalAccessTokenResponse { token };
+ // The SDK strips a leading result section from every metadata reply
+ // (`split_metadata_result`), so the spliced body must carry the success
+ // section like any committed metadata reply; a bare token body would be
+ // read as a garbage result code.
+ let mut body = BytesMut::with_capacity(RESULT_COUNT_LEN + response.encoded_size());
+ body.put_u32_le(0);
+ response.encode(&mut body);
let reply = build_reply_from_bytes(
request_header,
request_header.client,
request_header.session,
commit,
- &body,
+ &body.freeze(),
);
Ok(reply.into_generic())
}
@@ -1134,8 +1141,11 @@ mod tests {
.expect("splice succeeds");
let header_len = std::mem::size_of::<ReplyHeader>();
let body = &reply.as_slice()[header_len..reply.header().size as usize];
- let response =
- RawPersonalAccessTokenResponse::decode_from(body).expect("token body decodes");
+ // Framed like every committed metadata reply: the SDK reads the result
+ // section first, then decodes the token payload past it.
+ assert_eq!(result_code(body), Some(0));
+ let response = RawPersonalAccessTokenResponse::decode_from(&body[RESULT_COUNT_LEN..])
+ .expect("token body decodes");
assert_eq!(response.token.as_str(), "raw-token");
}