pub fn encode_varint32(data: &[u8]) -> Vec<u8> {
let len = data.len() as u32;
let mut result = Vec::with_capacity(5 + data.len());
let mut value = len;
loop {
if value & !0x7F == 0 {
result.push(value as u8);
break;
}
result.push((value & 0x7F | 0x80) as u8);
value >>= 7;
}
result.extend_from_slice(data);
result
}
pub fn decode_varint32(buffer: &[u8]) -> Option<(&[u8], &[u8])> {
let mut value: u32 = 0;
let mut shift: u32 = 0;
for i in 0..buffer.len().min(5) {
let byte = buffer[i];
value |= ((byte & 0x7F) as u32) << shift;
if byte & 0x80 == 0 {
let header_len = i + 1;
let msg_len = value as usize;
let total = header_len + msg_len;
if buffer.len() < total {
return None;
}
let message = &buffer[header_len..total];
let remaining = &buffer[total..];
return Some((message, remaining));
}
shift += 7;
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_empty() {
let data = b"";
let encoded = encode_varint32(data);
assert_eq!(encoded, vec![0]);
let (msg, remaining) = decode_varint32(&encoded).unwrap();
assert_eq!(msg, data);
assert!(remaining.is_empty());
}
#[test]
fn test_encode_decode_small() {
let data = b"hello";
let encoded = encode_varint32(data);
assert_eq!(encoded[0], 5); assert_eq!(&encoded[1..], b"hello");
let (msg, remaining) = decode_varint32(&encoded).unwrap();
assert_eq!(msg, b"hello");
assert!(remaining.is_empty());
}
#[test]
fn test_encode_decode_128_bytes() {
let data = vec![0xAB; 128];
let encoded = encode_varint32(&data);
assert_eq!(encoded[0], 0x80);
assert_eq!(encoded[1], 0x01);
assert_eq!(&encoded[2..], data.as_slice());
let (msg, remaining) = decode_varint32(&encoded).unwrap();
assert_eq!(msg, data.as_slice());
assert!(remaining.is_empty());
}
#[test]
fn test_decode_insufficient_header() {
let buffer = vec![0x80];
assert!(decode_varint32(&buffer).is_none());
}
#[test]
fn test_decode_insufficient_body() {
let mut buffer = vec![10]; buffer.extend_from_slice(&[1, 2, 3]); assert!(decode_varint32(&buffer).is_none());
}
#[test]
fn test_decode_with_remaining() {
let data1 = b"abc";
let data2 = b"xyz";
let mut buffer = encode_varint32(data1);
buffer.extend_from_slice(&encode_varint32(data2));
let (msg1, remaining) = decode_varint32(&buffer).unwrap();
assert_eq!(msg1, b"abc");
let (msg2, remaining) = decode_varint32(remaining).unwrap();
assert_eq!(msg2, b"xyz");
assert!(remaining.is_empty());
}
#[test]
fn test_decode_empty_buffer() {
assert!(decode_varint32(&[]).is_none());
}
use proptest::prelude::*;
use prost::Message;
proptest! {
#[test]
fn prop_varint32_roundtrip(data in proptest::collection::vec(any::<u8>(), 0..10000)) {
let encoded = encode_varint32(&data);
let (decoded, remaining) = decode_varint32(&encoded).expect("decode should succeed");
prop_assert_eq!(decoded, data.as_slice());
prop_assert!(remaining.is_empty());
}
}
proptest! {
#[test]
fn prop_varint32_chunked_decode(
data in proptest::collection::vec(any::<u8>(), 0..5000),
split_pct in 0.0f64..=1.0f64,
) {
let encoded = encode_varint32(&data);
let split_point = (encoded.len() as f64 * split_pct) as usize;
let split_point = split_point.min(encoded.len());
let chunk1 = &encoded[..split_point];
let chunk2 = &encoded[split_point..];
let full = [chunk1, chunk2].concat();
let (decoded, remaining) = decode_varint32(&full).expect("full buffer decode should succeed");
prop_assert_eq!(decoded, data.as_slice());
prop_assert!(remaining.is_empty());
}
}
fn arb_request() -> impl Strategy<Value = super::super::pb::Request> {
use super::super::pb;
use super::super::pb::socket_common::{Command, DataType};
prop_oneof![
(
".*", ".*", ".*", ".*",
any::<u32>(), any::<u32>(), any::<bool>()
).prop_map(|(tid, sign, sdk, ver, si, ri, ft)| {
pb::Request {
command: Command::Connect as i32,
id: 1,
connect: Some(pb::request::Connect {
tiger_id: tid,
sign,
sdk_version: sdk,
accept_version: Some(ver),
send_interval: Some(si),
receive_interval: Some(ri),
use_full_tick: Some(ft),
}),
subscribe: None,
}
}),
Just(pb::Request {
command: Command::Heartbeat as i32,
id: 2,
connect: None,
subscribe: None,
}),
(1..=12i32, ".*", ".*", ".*").prop_map(|(dt, sym, acc, mkt)| {
pb::Request {
command: Command::Subscribe as i32,
id: 3,
connect: None,
subscribe: Some(pb::request::Subscribe {
data_type: dt,
symbols: Some(sym),
account: Some(acc),
market: Some(mkt),
}),
}
}),
(1..=12i32, ".*", ".*", ".*").prop_map(|(dt, sym, acc, mkt)| {
pb::Request {
command: Command::Unsubscribe as i32,
id: 4,
connect: None,
subscribe: Some(pb::request::Subscribe {
data_type: dt,
symbols: Some(sym),
account: Some(acc),
market: Some(mkt),
}),
}
}),
Just(pb::Request {
command: Command::Disconnect as i32,
id: 5,
connect: None,
subscribe: None,
}),
]
}
proptest! {
#[test]
fn prop_request_frame_roundtrip(request in arb_request()) {
let proto_bytes = request.encode_to_vec();
let framed = encode_varint32(&proto_bytes);
let (decoded_bytes, remaining) = decode_varint32(&framed)
.expect("varint32 decode should succeed");
prop_assert!(remaining.is_empty());
let decoded = super::super::pb::Request::decode(decoded_bytes)
.expect("protobuf decode should succeed");
prop_assert_eq!(decoded, request);
}
}
}