Skip to main content

bsv/wallet/serializer/
frame.rs

1//! Request and result frame protocol for wallet wire communication.
2
3use super::read_varint;
4use crate::wallet::error::WalletError;
5
6/// A wallet wire protocol request frame.
7pub struct RequestFrame {
8    pub call: u8,
9    pub originator: String,
10    pub params: Vec<u8>,
11}
12
13/// Serialize a request frame: `[call_byte][originator_len_byte][originator_bytes][params_bytes]`.
14pub fn write_request_frame(frame: &RequestFrame) -> Vec<u8> {
15    let originator_bytes = frame.originator.as_bytes();
16    let mut buf = Vec::with_capacity(1 + 1 + originator_bytes.len() + frame.params.len());
17    buf.push(frame.call);
18    buf.push(originator_bytes.len() as u8);
19    buf.extend_from_slice(originator_bytes);
20    buf.extend_from_slice(&frame.params);
21    buf
22}
23
24/// Parse a request frame from raw bytes.
25pub fn read_request_frame(data: &[u8]) -> Result<RequestFrame, WalletError> {
26    if data.len() < 2 {
27        return Err(WalletError::Internal("request frame too short".to_string()));
28    }
29    let call = data[0];
30    let originator_len = data[1] as usize;
31    if data.len() < 2 + originator_len {
32        return Err(WalletError::Internal(
33            "request frame originator truncated".to_string(),
34        ));
35    }
36    let originator = String::from_utf8(data[2..2 + originator_len].to_vec())
37        .map_err(|e| WalletError::Internal(e.to_string()))?;
38    let params = data[2 + originator_len..].to_vec();
39    Ok(RequestFrame {
40        call,
41        originator,
42        params,
43    })
44}
45
46/// Write a result frame.
47/// Success: `[0x00][result_bytes]`.
48/// Error: `[code_byte][msg_varint_len][msg][stack_varint_len][stack]`.
49pub fn write_result_frame(result: Option<&[u8]>, error: Option<&WalletError>) -> Vec<u8> {
50    let mut buf = Vec::new();
51    if let Some(err) = error {
52        match err {
53            WalletError::Protocol { code, message } => {
54                buf.push(*code);
55                let msg_bytes = message.as_bytes();
56                buf.extend_from_slice(&super::varint_bytes(msg_bytes.len() as u64));
57                buf.extend_from_slice(msg_bytes);
58                // Empty stack
59                buf.extend_from_slice(&super::varint_bytes(0));
60            }
61            _ => {
62                let msg = err.to_string();
63                buf.push(1); // generic error code
64                let msg_bytes = msg.as_bytes();
65                buf.extend_from_slice(&super::varint_bytes(msg_bytes.len() as u64));
66                buf.extend_from_slice(msg_bytes);
67                buf.extend_from_slice(&super::varint_bytes(0));
68            }
69        }
70    } else {
71        buf.push(0x00); // success
72        if let Some(data) = result {
73            buf.extend_from_slice(data);
74        }
75    }
76    buf
77}
78
79/// Read a result frame. Returns Ok(result_bytes) on success, Err on error frame.
80pub fn read_result_frame(data: &[u8]) -> Result<Vec<u8>, WalletError> {
81    if data.is_empty() {
82        return Err(WalletError::Internal("empty result frame".to_string()));
83    }
84    let error_byte = data[0];
85    if error_byte == 0 {
86        // Success
87        return Ok(data[1..].to_vec());
88    }
89    // Error frame
90    let mut cursor = std::io::Cursor::new(&data[1..]);
91    let msg_len = read_varint(&mut cursor)?;
92    let mut msg_buf = vec![0u8; msg_len as usize];
93    std::io::Read::read_exact(&mut cursor, &mut msg_buf)
94        .map_err(|e| WalletError::Internal(e.to_string()))?;
95    let message = String::from_utf8(msg_buf).map_err(|e| WalletError::Internal(e.to_string()))?;
96
97    let stack_len = read_varint(&mut cursor)?;
98    let mut stack_buf = vec![0u8; stack_len as usize];
99    std::io::Read::read_exact(&mut cursor, &mut stack_buf)
100        .map_err(|e| WalletError::Internal(e.to_string()))?;
101
102    Err(WalletError::Protocol {
103        code: error_byte,
104        message,
105    })
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_request_frame_roundtrip() {
114        let frame = RequestFrame {
115            call: 11,
116            originator: "test-app".to_string(),
117            params: vec![1, 2, 3, 4],
118        };
119        let wire = write_request_frame(&frame);
120        let parsed = read_request_frame(&wire).unwrap();
121        assert_eq!(parsed.call, 11);
122        assert_eq!(parsed.originator, "test-app");
123        assert_eq!(parsed.params, vec![1, 2, 3, 4]);
124    }
125
126    #[test]
127    fn test_request_frame_empty_originator() {
128        let frame = RequestFrame {
129            call: 1,
130            originator: String::new(),
131            params: vec![5, 6],
132        };
133        let wire = write_request_frame(&frame);
134        assert_eq!(wire[0], 1);
135        assert_eq!(wire[1], 0);
136        assert_eq!(&wire[2..], &[5, 6]);
137        let parsed = read_request_frame(&wire).unwrap();
138        assert_eq!(parsed.call, 1);
139        assert_eq!(parsed.originator, "");
140        assert_eq!(parsed.params, vec![5, 6]);
141    }
142
143    #[test]
144    fn test_result_frame_success() {
145        let wire = write_result_frame(Some(&[1, 2, 3]), None);
146        assert_eq!(wire[0], 0);
147        let data = read_result_frame(&wire).unwrap();
148        assert_eq!(data, vec![1, 2, 3]);
149    }
150
151    #[test]
152    fn test_result_frame_success_empty() {
153        let wire = write_result_frame(None, None);
154        assert_eq!(wire, vec![0]);
155        let data = read_result_frame(&wire).unwrap();
156        assert!(data.is_empty());
157    }
158
159    #[test]
160    fn test_result_frame_error() {
161        let err = WalletError::Protocol {
162            code: 5,
163            message: "test error".to_string(),
164        };
165        let wire = write_result_frame(None, Some(&err));
166        assert_ne!(wire[0], 0);
167        let result = read_result_frame(&wire);
168        assert!(result.is_err());
169    }
170}