Skip to main content

rustbac_core/services/
private_transfer.rs

1use crate::apdu::ConfirmedRequestHeader;
2use crate::encoding::{primitives::encode_ctx_unsigned, tag::Tag, writer::Writer};
3use crate::EncodeError;
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7#[cfg(feature = "alloc")]
8use alloc::vec::Vec;
9
10#[cfg(feature = "alloc")]
11use crate::encoding::{primitives::decode_unsigned, reader::Reader};
12#[cfg(feature = "alloc")]
13use crate::DecodeError;
14
15pub const SERVICE_CONFIRMED_PRIVATE_TRANSFER: u8 = 18;
16pub const SERVICE_UNCONFIRMED_PRIVATE_TRANSFER: u8 = 4;
17
18/// A ConfirmedPrivateTransfer request as defined in clause 16.
19///
20/// Vendor-specific data is passed as an opaque byte slice in `service_parameters`.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct ConfirmedPrivateTransferRequest<'a> {
23    pub vendor_id: u32,
24    pub service_number: u32,
25    pub service_parameters: Option<&'a [u8]>,
26    pub invoke_id: u8,
27}
28
29impl<'a> ConfirmedPrivateTransferRequest<'a> {
30    pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
31        ConfirmedRequestHeader {
32            segmented: false,
33            more_follows: false,
34            segmented_response_accepted: false,
35            max_segments: 0,
36            max_apdu: 5,
37            invoke_id: self.invoke_id,
38            sequence_number: None,
39            proposed_window_size: None,
40            service_choice: SERVICE_CONFIRMED_PRIVATE_TRANSFER,
41        }
42        .encode(w)?;
43
44        // [0] vendor-id
45        encode_ctx_unsigned(w, 0, self.vendor_id)?;
46        // [1] service-number
47        encode_ctx_unsigned(w, 1, self.service_number)?;
48        // [2] service-parameters (optional, constructed)
49        if let Some(params) = self.service_parameters {
50            Tag::Opening { tag_num: 2 }.encode(w)?;
51            w.write_all(params)?;
52            Tag::Closing { tag_num: 2 }.encode(w)?;
53        }
54        Ok(())
55    }
56}
57
58/// The ack (result) from a ConfirmedPrivateTransfer.
59#[cfg(feature = "alloc")]
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ConfirmedPrivateTransferAck {
62    pub vendor_id: u32,
63    pub service_number: u32,
64    pub result_block: Option<Vec<u8>>,
65}
66
67#[cfg(feature = "alloc")]
68impl ConfirmedPrivateTransferAck {
69    pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
70        // [0] vendor-id
71        let vendor_id = decode_ctx_unsigned(r, 0)?;
72        // [1] service-number
73        let service_number = decode_ctx_unsigned(r, 1)?;
74        // [2] result-block (optional, constructed)
75        let result_block = if !r.is_empty() {
76            match Tag::decode(r)? {
77                Tag::Opening { tag_num: 2 } => Some(decode_constructed_block_inner_bytes(r, 2)?),
78                _ => return Err(DecodeError::InvalidTag),
79            }
80        } else {
81            None
82        };
83        if !r.is_empty() {
84            return Err(DecodeError::InvalidTag);
85        }
86        Ok(Self {
87            vendor_id,
88            service_number,
89            result_block,
90        })
91    }
92}
93
94#[cfg(feature = "alloc")]
95fn decode_constructed_block_inner_bytes(
96    r: &mut Reader<'_>,
97    expected_tag_num: u8,
98) -> Result<Vec<u8>, DecodeError> {
99    // Scan with a probe reader to locate the matching closing tag while validating nesting.
100    let start = r.position();
101    let mut probe = *r;
102    let mut stack = Vec::new();
103    loop {
104        let tag_start = probe.position();
105        let tag = Tag::decode(&mut probe)?;
106        match tag {
107            Tag::Application {
108                tag: crate::encoding::tag::AppTag::Boolean,
109                ..
110            } => {}
111            Tag::Application { len, .. } | Tag::Context { len, .. } => {
112                probe.read_exact(len as usize)?;
113            }
114            Tag::Opening { tag_num } => stack.push(tag_num),
115            Tag::Closing { tag_num } => {
116                if let Some(opening) = stack.pop() {
117                    if opening != tag_num {
118                        return Err(DecodeError::InvalidTag);
119                    }
120                } else if tag_num == expected_tag_num {
121                    let inner_len = tag_start
122                        .checked_sub(start)
123                        .ok_or(DecodeError::InvalidLength)?;
124                    let inner = r.read_exact(inner_len)?.to_vec();
125                    match Tag::decode(r)? {
126                        Tag::Closing { tag_num: closing } if closing == expected_tag_num => {}
127                        _ => return Err(DecodeError::InvalidTag),
128                    }
129                    return Ok(inner);
130                } else {
131                    return Err(DecodeError::InvalidTag);
132                }
133            }
134        }
135    }
136}
137
138#[cfg(feature = "alloc")]
139fn decode_ctx_unsigned(r: &mut Reader<'_>, expected_tag_num: u8) -> Result<u32, DecodeError> {
140    match Tag::decode(r)? {
141        Tag::Context { tag_num, len } if tag_num == expected_tag_num => {
142            decode_unsigned(r, len as usize)
143        }
144        _ => Err(DecodeError::InvalidTag),
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::apdu::ConfirmedRequestHeader;
152    #[cfg(feature = "alloc")]
153    use crate::encoding::{primitives::encode_ctx_unsigned, tag::AppTag};
154    use crate::encoding::{reader::Reader, writer::Writer};
155
156    #[test]
157    fn encode_private_transfer_request() {
158        let req = ConfirmedPrivateTransferRequest {
159            vendor_id: 42,
160            service_number: 1,
161            service_parameters: Some(&[0x01, 0x02, 0x03]),
162            invoke_id: 5,
163        };
164
165        let mut buf = [0u8; 64];
166        let mut w = Writer::new(&mut buf);
167        req.encode(&mut w).unwrap();
168
169        let mut r = Reader::new(w.as_written());
170        let header = ConfirmedRequestHeader::decode(&mut r).unwrap();
171        assert_eq!(header.invoke_id, 5);
172        assert_eq!(header.service_choice, SERVICE_CONFIRMED_PRIVATE_TRANSFER);
173        assert!(!r.is_empty());
174    }
175
176    #[test]
177    fn encode_private_transfer_no_params() {
178        let req = ConfirmedPrivateTransferRequest {
179            vendor_id: 100,
180            service_number: 7,
181            service_parameters: None,
182            invoke_id: 1,
183        };
184
185        let mut buf = [0u8; 64];
186        let mut w = Writer::new(&mut buf);
187        req.encode(&mut w).unwrap();
188
189        let mut r = Reader::new(w.as_written());
190        let header = ConfirmedRequestHeader::decode(&mut r).unwrap();
191        assert_eq!(header.service_choice, SERVICE_CONFIRMED_PRIVATE_TRANSFER);
192    }
193
194    #[cfg(feature = "alloc")]
195    #[test]
196    fn decode_private_transfer_ack_preserves_nested_result_block_bytes() {
197        let mut payload = [0u8; 128];
198        let mut w = Writer::new(&mut payload);
199        encode_ctx_unsigned(&mut w, 0, 77).unwrap();
200        encode_ctx_unsigned(&mut w, 1, 9).unwrap();
201        Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
202
203        let mut expected_inner = [0u8; 64];
204        let mut ew = Writer::new(&mut expected_inner);
205        Tag::Application {
206            tag: AppTag::UnsignedInt,
207            len: 1,
208        }
209        .encode(&mut ew)
210        .unwrap();
211        ew.write_u8(0x2A).unwrap();
212        Tag::Opening { tag_num: 0 }.encode(&mut ew).unwrap();
213        Tag::Application {
214            tag: AppTag::Boolean,
215            len: 1,
216        }
217        .encode(&mut ew)
218        .unwrap();
219        Tag::Closing { tag_num: 0 }.encode(&mut ew).unwrap();
220        w.write_all(ew.as_written()).unwrap();
221
222        Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
223
224        let mut r = Reader::new(w.as_written());
225        let ack = ConfirmedPrivateTransferAck::decode(&mut r).unwrap();
226        assert_eq!(ack.vendor_id, 77);
227        assert_eq!(ack.service_number, 9);
228        assert_eq!(ack.result_block, Some(ew.as_written().to_vec()));
229    }
230
231    #[cfg(feature = "alloc")]
232    #[test]
233    fn decode_private_transfer_ack_rejects_trailing_or_invalid_optional_block() {
234        let mut invalid_optional = [0u8; 64];
235        let mut w = Writer::new(&mut invalid_optional);
236        encode_ctx_unsigned(&mut w, 0, 1).unwrap();
237        encode_ctx_unsigned(&mut w, 1, 2).unwrap();
238        encode_ctx_unsigned(&mut w, 3, 9).unwrap();
239        let mut r = Reader::new(w.as_written());
240        assert_eq!(
241            ConfirmedPrivateTransferAck::decode(&mut r).unwrap_err(),
242            DecodeError::InvalidTag
243        );
244
245        let mut trailing = [0u8; 64];
246        let mut w = Writer::new(&mut trailing);
247        encode_ctx_unsigned(&mut w, 0, 1).unwrap();
248        encode_ctx_unsigned(&mut w, 1, 2).unwrap();
249        Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
250        Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
251        encode_ctx_unsigned(&mut w, 3, 9).unwrap();
252        let mut r = Reader::new(w.as_written());
253        assert_eq!(
254            ConfirmedPrivateTransferAck::decode(&mut r).unwrap_err(),
255            DecodeError::InvalidTag
256        );
257    }
258
259    #[cfg(feature = "alloc")]
260    #[test]
261    fn decode_private_transfer_ack_distinguishes_absent_vs_empty_result_block() {
262        let mut payload = [0u8; 64];
263        let mut w = Writer::new(&mut payload);
264        encode_ctx_unsigned(&mut w, 0, 11).unwrap();
265        encode_ctx_unsigned(&mut w, 1, 22).unwrap();
266        Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
267        Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
268        let mut r = Reader::new(w.as_written());
269        let ack = ConfirmedPrivateTransferAck::decode(&mut r).unwrap();
270        assert_eq!(ack.result_block, Some(Vec::new()));
271    }
272}