use copybook_core::{Error, ErrorCode, Result};
#[inline]
#[must_use = "Handle the Result or propagate the error"]
pub fn decode_binary_int(data: &[u8], bits: u16, signed: bool) -> Result<i64> {
let expected_bytes = usize::from(bits / 8);
if data.len() != expected_bytes {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE, format!(
"Binary data length {} doesn't match expected {} bytes for {} bits",
data.len(),
expected_bytes,
bits
),
));
}
match bits {
16 => {
if data.len() != 2 {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
"16-bit binary field requires exactly 2 bytes",
));
}
let value = u16::from_be_bytes([data[0], data[1]]);
if signed {
Ok(i64::from(i16::from_be_bytes([data[0], data[1]])))
} else {
Ok(i64::from(value))
}
}
32 => {
if data.len() != 4 {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
"32-bit binary field requires exactly 4 bytes",
));
}
let value = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
if signed {
Ok(i64::from(i32::from_be_bytes([
data[0], data[1], data[2], data[3],
])))
} else {
Ok(i64::from(value))
}
}
64 => {
if data.len() != 8 {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
"64-bit binary field requires exactly 8 bytes",
));
}
let bytes: [u8; 8] = data.try_into().map_err(|_| {
Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
"Failed to convert data to 8-byte array",
)
})?;
if signed {
Ok(i64::from_be_bytes(bytes))
} else {
let value = u64::from_be_bytes(bytes);
let max_i64 = u64::try_from(i64::MAX).unwrap_or(u64::MAX);
if value > max_i64 {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
format!("Unsigned 64-bit value {value} exceeds i64::MAX"),
));
}
i64::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
format!("Unsigned 64-bit value {value} exceeds i64::MAX"),
)
})
}
}
_ => Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
format!("Unsupported binary field width: {bits} bits"),
)),
}
}
#[inline]
#[must_use = "Handle the Result or propagate the error"]
pub fn encode_binary_int(value: i64, bits: u16, signed: bool) -> Result<Vec<u8>> {
match bits {
16 => {
if signed {
let int_value = i16::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Value {value} out of range for signed 16-bit integer"),
)
})?;
Ok(int_value.to_be_bytes().to_vec())
} else {
let int_value = u16::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Value {value} out of range for unsigned 16-bit integer"),
)
})?;
Ok(int_value.to_be_bytes().to_vec())
}
}
32 => {
if signed {
let int_value = i32::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Value {value} out of range for signed 32-bit integer"),
)
})?;
Ok(int_value.to_be_bytes().to_vec())
} else {
let int_value = u32::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Value {value} out of range for unsigned 32-bit integer"),
)
})?;
Ok(int_value.to_be_bytes().to_vec())
}
}
64 => {
if signed {
Ok(value.to_be_bytes().to_vec())
} else {
let int_value = u64::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Value {value} cannot be negative for unsigned 64-bit integer"),
)
})?;
Ok(int_value.to_be_bytes().to_vec())
}
}
_ => Err(Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Unsupported binary field width: {bits} bits"),
)),
}
}
#[inline]
#[must_use]
pub fn get_binary_width_from_digits(digits: u16) -> u16 {
match digits {
1..=4 => 16, 5..=9 => 32, _ => 64, }
}
#[inline]
#[must_use = "Handle the Result or propagate the error"]
pub fn validate_explicit_binary_width(width_bytes: u8) -> Result<u16> {
match width_bytes {
1 => Ok(8), 2 => Ok(16), 4 => Ok(32), 8 => Ok(64), _ => Err(Error::new(
ErrorCode::CBKE501_JSON_TYPE_MISMATCH,
format!("Invalid explicit binary width: {width_bytes} bytes. Must be 1, 2, 4, or 8"),
)),
}
}
#[inline]
#[must_use = "Handle the Result or propagate the error"]
pub fn decode_binary_int_fast(data: &[u8], bits: u16, signed: bool) -> Result<i64> {
match (bits, data.len()) {
(16, 2) => {
let bytes = [data[0], data[1]];
if signed {
Ok(i64::from(i16::from_be_bytes(bytes)))
} else {
Ok(i64::from(u16::from_be_bytes(bytes)))
}
}
(32, 4) => {
let bytes = [data[0], data[1], data[2], data[3]];
if signed {
Ok(i64::from(i32::from_be_bytes(bytes)))
} else {
Ok(i64::from(u32::from_be_bytes(bytes)))
}
}
(64, 8) => {
let bytes = [
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
];
if signed {
Ok(i64::from_be_bytes(bytes))
} else {
let value = u64::from_be_bytes(bytes);
let max_i64 = u64::try_from(i64::MAX).unwrap_or(u64::MAX);
if value > max_i64 {
return Err(Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
format!("Unsigned 64-bit value {value} exceeds i64::MAX"),
));
}
i64::try_from(value).map_err(|_| {
Error::new(
ErrorCode::CBKD401_COMP3_INVALID_NIBBLE,
format!("Unsigned 64-bit value {value} exceeds i64::MAX"),
)
})
}
}
_ => {
decode_binary_int(data, bits, signed)
}
}
}