use bytes::{Buf, Bytes, BytesMut};
use kafka_protocol::messages::RequestHeader;
use kafka_protocol::messages::ResponseHeader;
use kafka_protocol::protocol::{Decodable, Encodable, StrBytes};
use tokio_util::codec::LengthDelimitedCodec;
use crate::api_key::ApiKey;
use crate::error::{Error, Result};
use crate::rpc::Rpc;
pub const DEFAULT_MAX_FRAME_BYTES: usize = 100 * 1024 * 1024;
pub(crate) fn frame_codec(max_frame_bytes: usize) -> LengthDelimitedCodec {
LengthDelimitedCodec::builder()
.big_endian()
.length_field_offset(0)
.length_field_length(4)
.length_adjustment(0)
.num_skip(4)
.max_frame_length(max_frame_bytes)
.new_codec()
}
pub(crate) fn encode_request<R: Rpc>(
api_key: ApiKey,
version: i16,
correlation_id: i32,
client_id: Option<&str>,
request: &R,
) -> Result<BytesMut> {
let upstream = protocol_key(api_key)?;
let header = RequestHeader::default()
.with_request_api_key(api_key.code())
.with_request_api_version(version)
.with_correlation_id(correlation_id)
.with_client_id(client_id.map(|id| StrBytes::from_string(id.to_owned())));
let header_version = upstream.request_header_version(version);
let mut buf = BytesMut::new();
header
.encode(&mut buf, header_version)
.map_err(|e| Error::decode("encoding request header", e))?;
request
.encode(&mut buf, version)
.map_err(|e| Error::decode("encoding request body", e))?;
Ok(buf)
}
pub(crate) fn peek_correlation_id(frame: &[u8]) -> Result<i32> {
if frame.len() < 4 {
return Err(Error::decode(
"reading response correlation id",
std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("response frame is {} bytes, need at least 4", frame.len()),
),
));
}
let mut head = frame.get(..4).unwrap_or_default();
Ok(head.get_i32())
}
pub(crate) fn decode_response<R: Rpc>(
api_key: ApiKey,
version: i16,
frame: Bytes,
) -> Result<R::Response> {
let mut buf = frame;
let upstream = protocol_key(api_key)?;
let header_version = upstream.response_header_version(version);
ResponseHeader::decode(&mut buf, header_version)
.map_err(|e| Error::decode("decoding response header", e))?;
R::Response::decode(&mut buf, version).map_err(|e| Error::decode("decoding response body", e))
}
pub(crate) fn split_response_body(api_key: ApiKey, version: i16, frame: Bytes) -> Result<Bytes> {
let mut buf = frame;
let upstream = protocol_key(api_key)?;
let header_version = upstream.response_header_version(version);
ResponseHeader::decode(&mut buf, header_version)
.map_err(|e| Error::decode("decoding response header", e))?;
Ok(buf)
}
fn protocol_key(api_key: ApiKey) -> Result<kafka_protocol::messages::ApiKey> {
kafka_protocol::messages::ApiKey::try_from(api_key.code()).map_err(|_| Error::UnsupportedApi {
api_key,
broker: None,
ours: None,
})
}
#[cfg(test)]
mod tests {
use kafka_protocol::messages::{ApiVersionsRequest, MetadataRequest};
use super::*;
#[test]
fn request_frames_start_with_key_version_and_correlation() {
let req = MetadataRequest::default();
let buf =
encode_request(ApiKey::Metadata, 12, 0x0102_0304, Some("kaas"), &req).expect("encodes");
assert_eq!(&buf[..2], &ApiKey::Metadata.code().to_be_bytes());
assert_eq!(&buf[2..4], &12i16.to_be_bytes());
assert_eq!(&buf[4..8], &0x0102_0304i32.to_be_bytes());
}
#[test]
fn correlation_id_is_readable_without_knowing_the_version() {
for header_version in [0i16, 1] {
let mut buf = BytesMut::new();
ResponseHeader::default()
.with_correlation_id(4242)
.encode(&mut buf, header_version)
.expect("encodes");
assert_eq!(peek_correlation_id(&buf).ok(), Some(4242));
}
}
#[test]
fn a_short_frame_is_a_decode_error_not_a_panic() {
assert!(peek_correlation_id(&[0, 1]).is_err());
}
#[test]
fn api_versions_responses_use_header_v0_at_every_api_version() {
let upstream = kafka_protocol::messages::ApiKey::ApiVersions;
for version in 0..=4 {
assert_eq!(
upstream.response_header_version(version),
0,
"api version {version}"
);
}
assert_eq!(upstream.request_header_version(2), 1);
assert_eq!(upstream.request_header_version(3), 2);
}
#[test]
fn response_header_version_is_not_the_api_version() {
let upstream = kafka_protocol::messages::ApiKey::Metadata;
assert_eq!(upstream.response_header_version(8), 0);
assert_eq!(upstream.response_header_version(9), 1);
}
#[test]
fn header_and_body_round_trip_through_the_decoder() {
let req = ApiVersionsRequest::default()
.with_client_software_name(StrBytes::from_static_str("kaas-lib"))
.with_client_software_version(StrBytes::from_static_str("0.1.0"));
let frame = encode_request(ApiKey::ApiVersions, 3, 7, Some("kaas"), &req).expect("encodes");
assert_eq!(peek_correlation_id(&frame[4..8]).ok(), Some(7));
let mut response = BytesMut::new();
ResponseHeader::default()
.with_correlation_id(7)
.encode(&mut response, 0) .expect("encodes");
response.extend_from_slice(&0i16.to_be_bytes()); response.extend_from_slice(&[2]); response.extend_from_slice(&ApiKey::Metadata.code().to_be_bytes());
response.extend_from_slice(&0i16.to_be_bytes()); response.extend_from_slice(&13i16.to_be_bytes()); response.extend_from_slice(&[0]); response.extend_from_slice(&0i32.to_be_bytes()); response.extend_from_slice(&[0]);
let decoded =
decode_response::<ApiVersionsRequest>(ApiKey::ApiVersions, 3, response.freeze())
.expect("decodes");
assert_eq!(decoded.error_code, 0);
assert_eq!(decoded.api_keys.len(), 1);
assert_eq!(decoded.api_keys[0].max_version, 13);
}
#[test]
fn the_frame_codec_is_big_endian_with_a_four_byte_prefix() {
use tokio_util::codec::Decoder;
let mut codec = frame_codec(DEFAULT_MAX_FRAME_BYTES);
let mut buf = BytesMut::from(&[0u8, 0, 0, 3, 0xAA, 0xBB, 0xCC][..]);
let frame = codec.decode(&mut buf).expect("decodes").expect("a frame");
assert_eq!(&frame[..], &[0xAA, 0xBB, 0xCC]);
}
}