use crate::{NeoArray, NeoString, NeoValue};
pub const MAX_NOTIFICATION_SIZE: usize = 1024;
pub const MAX_STACK_SIZE: usize = 1024;
const TAG_NULL: u8 = 0x00;
const TAG_BOOLEAN: u8 = 0x20;
const TAG_INTEGER: u8 = 0x21;
const TAG_BYTESTRING: u8 = 0x28;
const TAG_ARRAY: u8 = 0x40;
const TAG_STRUCT: u8 = 0x41;
fn push_varint(out: &mut Vec<u8>, value: usize) {
let value = value as u64;
if value < 0xFD {
out.push(value as u8);
} else if value <= u16::MAX as u64 {
out.push(0xFD);
out.extend_from_slice(&(value as u16).to_le_bytes());
} else if value <= u32::MAX as u64 {
out.push(0xFE);
out.extend_from_slice(&(value as u32).to_le_bytes());
} else {
out.push(0xFF);
out.extend_from_slice(&value.to_le_bytes());
}
}
fn push_integer(out: &mut Vec<u8>, n: &crate::NeoInteger) {
let bigint = n.as_bigint();
out.push(TAG_INTEGER);
if bigint.sign() == num_bigint::Sign::NoSign {
push_varint(out, 0);
return;
}
let bytes = bigint.to_signed_bytes_le();
push_varint(out, bytes.len());
out.extend_from_slice(&bytes);
}
fn push_bytestring(out: &mut Vec<u8>, bytes: &[u8]) {
out.push(TAG_BYTESTRING);
push_varint(out, bytes.len());
out.extend_from_slice(bytes);
}
fn push_boolean(out: &mut Vec<u8>, b: bool) {
out.push(TAG_BOOLEAN);
out.push(if b { 0x01 } else { 0x00 });
}
fn push_stack_item(out: &mut Vec<u8>, value: &NeoValue) {
match value {
NeoValue::Null => out.push(TAG_NULL),
NeoValue::Boolean(b) => push_boolean(out, b.as_bool()),
NeoValue::Integer(i) => push_integer(out, i),
NeoValue::ByteString(bs) => push_bytestring(out, bs.as_slice()),
NeoValue::String(s) => push_bytestring(out, s.as_str().as_bytes()),
NeoValue::Array(arr) => {
out.push(TAG_ARRAY);
push_varint(out, arr.len());
for item in arr.iter() {
push_stack_item(out, item);
}
}
NeoValue::Struct(items) => {
out.push(TAG_STRUCT);
push_varint(out, items.len());
for (_name, value) in items.iter() {
push_stack_item(out, value);
}
}
NeoValue::Map(_) => {
out.push(TAG_NULL);
}
}
}
pub fn serialise_array(items: &NeoArray<NeoValue>) -> Vec<u8> {
let mut out = Vec::with_capacity(items.len() * 4 + 2);
out.push(TAG_ARRAY);
push_varint(&mut out, items.len());
for item in items.iter() {
push_stack_item(&mut out, item);
}
out
}
pub fn serialise_value(value: &NeoValue) -> Vec<u8> {
let mut out = Vec::with_capacity(8);
push_stack_item(&mut out, value);
out
}
pub fn serialise_notification(event: &NeoString, state: &NeoArray<NeoValue>) -> Vec<u8> {
let mut out = Vec::with_capacity(event.as_str().len() + state.len() * 4 + 4);
out.push(TAG_ARRAY);
push_varint(&mut out, 2);
push_bytestring(&mut out, event.as_str().as_bytes());
push_stack_item(&mut out, &NeoValue::Array(state.clone()));
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{NeoBoolean, NeoByteString, NeoInteger};
#[test]
fn varint_single_byte() {
let mut out = Vec::new();
push_varint(&mut out, 0);
push_varint(&mut out, 127);
push_varint(&mut out, 128);
push_varint(&mut out, 252);
assert_eq!(out, vec![0, 127, 128, 252]);
}
#[test]
fn varint_0xfd_marker() {
let mut out = Vec::new();
push_varint(&mut out, 253);
assert_eq!(out, vec![0xFD, 0xFD, 0x00]);
let mut out = Vec::new();
push_varint(&mut out, 0x1234);
assert_eq!(out, vec![0xFD, 0x34, 0x12]);
}
#[test]
fn varint_0xfe_marker() {
let mut out = Vec::new();
push_varint(&mut out, 0x0001_0000);
assert_eq!(out, vec![0xFE, 0x00, 0x00, 0x01, 0x00]);
}
#[test]
fn bytestring_length_128_uses_single_byte_prefix() {
let payload = vec![0xABu8; 128];
let bytes = serialise_value(&NeoValue::ByteString(crate::NeoByteString::from_slice(
&payload,
)));
assert_eq!(bytes[0], TAG_BYTESTRING);
assert_eq!(bytes[1], 0x80); assert_eq!(&bytes[2..], &payload[..]);
assert_eq!(bytes.len(), 130);
}
#[test]
fn tag_values_match_neo_stack_item_type() {
assert_eq!(TAG_NULL, 0x00);
assert_eq!(TAG_BOOLEAN, 0x20);
assert_eq!(TAG_INTEGER, 0x21);
assert_eq!(TAG_BYTESTRING, 0x28);
assert_eq!(TAG_ARRAY, 0x40);
assert_eq!(TAG_STRUCT, 0x41);
}
#[test]
fn integer_positive() {
let n = NeoInteger::new(42i32);
let mut out = Vec::new();
push_integer(&mut out, &n);
assert_eq!(out, vec![0x21, 0x01, 0x2A]);
}
#[test]
fn integer_zero_is_empty() {
let mut out = Vec::new();
push_integer(&mut out, &NeoInteger::new(0i32));
assert_eq!(out, vec![0x21, 0x00]);
}
#[test]
fn integer_multibyte_is_little_endian() {
let mut out = Vec::new();
push_integer(&mut out, &NeoInteger::new(1000i32));
assert_eq!(out, vec![0x21, 0x02, 0xE8, 0x03]);
let mut out = Vec::new();
push_integer(&mut out, &NeoInteger::new(128i32));
assert_eq!(out, vec![0x21, 0x02, 0x80, 0x00]);
}
#[test]
fn integer_negative_minimum_length() {
let n = NeoInteger::new(-1i32);
let mut out = Vec::new();
push_integer(&mut out, &n);
assert_eq!(out, vec![0x21, 0x01, 0xFF]);
let mut out = Vec::new();
push_integer(&mut out, &NeoInteger::new(-256i32));
assert_eq!(out, vec![0x21, 0x02, 0x00, 0xFF]);
}
#[test]
fn boolean() {
let mut out = Vec::new();
push_boolean(&mut out, true);
assert_eq!(out, vec![0x20, 0x01]);
push_boolean(&mut out, false);
assert_eq!(out, vec![0x20, 0x01, 0x20, 0x00]);
}
#[test]
fn empty_array() {
let arr = NeoArray::<NeoValue>::new();
let bytes = serialise_array(&arr);
assert_eq!(bytes, vec![TAG_ARRAY, 0x00]);
}
#[test]
fn array_with_int_and_bool() {
let mut arr = NeoArray::new();
arr.push(NeoValue::Integer(NeoInteger::new(7i32)));
arr.push(NeoValue::Boolean(NeoBoolean::new(true)));
let bytes = serialise_array(&arr);
assert_eq!(
bytes,
vec![TAG_ARRAY, 0x02, TAG_INTEGER, 0x01, 0x07, TAG_BOOLEAN, 0x01]
);
}
#[test]
fn notification_event_plus_state() {
let mut arr = NeoArray::new();
arr.push(NeoValue::Integer(NeoInteger::new(99i32)));
let event = NeoString::from_str("Transfer");
let bytes = serialise_notification(&event, &arr);
let mut expected = vec![TAG_ARRAY, 0x02];
expected.push(TAG_BYTESTRING);
expected.push(8);
expected.extend_from_slice(b"Transfer");
expected.push(TAG_ARRAY);
expected.push(0x01);
expected.push(TAG_INTEGER);
expected.push(0x01);
expected.push(0x63);
assert_eq!(bytes, expected);
}
#[test]
fn bytestring_value() {
let v = NeoValue::ByteString(NeoByteString::from_slice(&[1, 2, 3]));
let bytes = serialise_value(&v);
assert_eq!(bytes, vec![TAG_BYTESTRING, 0x03, 0x01, 0x02, 0x03]);
}
}