use crate::apdu::ConfirmedRequestHeader;
use crate::encoding::{primitives::encode_ctx_unsigned, tag::Tag, writer::Writer};
use crate::EncodeError;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
use crate::encoding::{primitives::decode_unsigned, reader::Reader};
#[cfg(feature = "alloc")]
use crate::DecodeError;
pub const SERVICE_CONFIRMED_PRIVATE_TRANSFER: u8 = 18;
pub const SERVICE_UNCONFIRMED_PRIVATE_TRANSFER: u8 = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfirmedPrivateTransferRequest<'a> {
pub vendor_id: u32,
pub service_number: u32,
pub service_parameters: Option<&'a [u8]>,
pub invoke_id: u8,
}
impl<'a> ConfirmedPrivateTransferRequest<'a> {
pub fn encode(&self, w: &mut Writer<'_>) -> Result<(), EncodeError> {
ConfirmedRequestHeader {
segmented: false,
more_follows: false,
segmented_response_accepted: false,
max_segments: 0,
max_apdu: 5,
invoke_id: self.invoke_id,
sequence_number: None,
proposed_window_size: None,
service_choice: SERVICE_CONFIRMED_PRIVATE_TRANSFER,
}
.encode(w)?;
encode_ctx_unsigned(w, 0, self.vendor_id)?;
encode_ctx_unsigned(w, 1, self.service_number)?;
if let Some(params) = self.service_parameters {
Tag::Opening { tag_num: 2 }.encode(w)?;
w.write_all(params)?;
Tag::Closing { tag_num: 2 }.encode(w)?;
}
Ok(())
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfirmedPrivateTransferAck {
pub vendor_id: u32,
pub service_number: u32,
pub result_block: Option<Vec<u8>>,
}
#[cfg(feature = "alloc")]
impl ConfirmedPrivateTransferAck {
pub fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> {
let vendor_id = decode_ctx_unsigned(r, 0)?;
let service_number = decode_ctx_unsigned(r, 1)?;
let result_block = if !r.is_empty() {
match Tag::decode(r)? {
Tag::Opening { tag_num: 2 } => Some(decode_constructed_block_inner_bytes(r, 2)?),
_ => return Err(DecodeError::InvalidTag),
}
} else {
None
};
if !r.is_empty() {
return Err(DecodeError::InvalidTag);
}
Ok(Self {
vendor_id,
service_number,
result_block,
})
}
}
#[cfg(feature = "alloc")]
fn decode_constructed_block_inner_bytes(
r: &mut Reader<'_>,
expected_tag_num: u8,
) -> Result<Vec<u8>, DecodeError> {
let start = r.position();
let mut probe = *r;
let mut stack = Vec::new();
loop {
let tag_start = probe.position();
let tag = Tag::decode(&mut probe)?;
match tag {
Tag::Application {
tag: crate::encoding::tag::AppTag::Boolean,
..
} => {}
Tag::Application { len, .. } | Tag::Context { len, .. } => {
probe.read_exact(len as usize)?;
}
Tag::Opening { tag_num } => stack.push(tag_num),
Tag::Closing { tag_num } => {
if let Some(opening) = stack.pop() {
if opening != tag_num {
return Err(DecodeError::InvalidTag);
}
} else if tag_num == expected_tag_num {
let inner_len = tag_start
.checked_sub(start)
.ok_or(DecodeError::InvalidLength)?;
let inner = r.read_exact(inner_len)?.to_vec();
match Tag::decode(r)? {
Tag::Closing { tag_num: closing } if closing == expected_tag_num => {}
_ => return Err(DecodeError::InvalidTag),
}
return Ok(inner);
} else {
return Err(DecodeError::InvalidTag);
}
}
}
}
}
#[cfg(feature = "alloc")]
fn decode_ctx_unsigned(r: &mut Reader<'_>, expected_tag_num: u8) -> Result<u32, DecodeError> {
match Tag::decode(r)? {
Tag::Context { tag_num, len } if tag_num == expected_tag_num => {
decode_unsigned(r, len as usize)
}
_ => Err(DecodeError::InvalidTag),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::apdu::ConfirmedRequestHeader;
#[cfg(feature = "alloc")]
use crate::encoding::{primitives::encode_ctx_unsigned, tag::AppTag};
use crate::encoding::{reader::Reader, writer::Writer};
#[test]
fn encode_private_transfer_request() {
let req = ConfirmedPrivateTransferRequest {
vendor_id: 42,
service_number: 1,
service_parameters: Some(&[0x01, 0x02, 0x03]),
invoke_id: 5,
};
let mut buf = [0u8; 64];
let mut w = Writer::new(&mut buf);
req.encode(&mut w).unwrap();
let mut r = Reader::new(w.as_written());
let header = ConfirmedRequestHeader::decode(&mut r).unwrap();
assert_eq!(header.invoke_id, 5);
assert_eq!(header.service_choice, SERVICE_CONFIRMED_PRIVATE_TRANSFER);
assert!(!r.is_empty());
}
#[test]
fn encode_private_transfer_no_params() {
let req = ConfirmedPrivateTransferRequest {
vendor_id: 100,
service_number: 7,
service_parameters: None,
invoke_id: 1,
};
let mut buf = [0u8; 64];
let mut w = Writer::new(&mut buf);
req.encode(&mut w).unwrap();
let mut r = Reader::new(w.as_written());
let header = ConfirmedRequestHeader::decode(&mut r).unwrap();
assert_eq!(header.service_choice, SERVICE_CONFIRMED_PRIVATE_TRANSFER);
}
#[cfg(feature = "alloc")]
#[test]
fn decode_private_transfer_ack_preserves_nested_result_block_bytes() {
let mut payload = [0u8; 128];
let mut w = Writer::new(&mut payload);
encode_ctx_unsigned(&mut w, 0, 77).unwrap();
encode_ctx_unsigned(&mut w, 1, 9).unwrap();
Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
let mut expected_inner = [0u8; 64];
let mut ew = Writer::new(&mut expected_inner);
Tag::Application {
tag: AppTag::UnsignedInt,
len: 1,
}
.encode(&mut ew)
.unwrap();
ew.write_u8(0x2A).unwrap();
Tag::Opening { tag_num: 0 }.encode(&mut ew).unwrap();
Tag::Application {
tag: AppTag::Boolean,
len: 1,
}
.encode(&mut ew)
.unwrap();
Tag::Closing { tag_num: 0 }.encode(&mut ew).unwrap();
w.write_all(ew.as_written()).unwrap();
Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
let mut r = Reader::new(w.as_written());
let ack = ConfirmedPrivateTransferAck::decode(&mut r).unwrap();
assert_eq!(ack.vendor_id, 77);
assert_eq!(ack.service_number, 9);
assert_eq!(ack.result_block, Some(ew.as_written().to_vec()));
}
#[cfg(feature = "alloc")]
#[test]
fn decode_private_transfer_ack_rejects_trailing_or_invalid_optional_block() {
let mut invalid_optional = [0u8; 64];
let mut w = Writer::new(&mut invalid_optional);
encode_ctx_unsigned(&mut w, 0, 1).unwrap();
encode_ctx_unsigned(&mut w, 1, 2).unwrap();
encode_ctx_unsigned(&mut w, 3, 9).unwrap();
let mut r = Reader::new(w.as_written());
assert_eq!(
ConfirmedPrivateTransferAck::decode(&mut r).unwrap_err(),
DecodeError::InvalidTag
);
let mut trailing = [0u8; 64];
let mut w = Writer::new(&mut trailing);
encode_ctx_unsigned(&mut w, 0, 1).unwrap();
encode_ctx_unsigned(&mut w, 1, 2).unwrap();
Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
encode_ctx_unsigned(&mut w, 3, 9).unwrap();
let mut r = Reader::new(w.as_written());
assert_eq!(
ConfirmedPrivateTransferAck::decode(&mut r).unwrap_err(),
DecodeError::InvalidTag
);
}
#[cfg(feature = "alloc")]
#[test]
fn decode_private_transfer_ack_distinguishes_absent_vs_empty_result_block() {
let mut payload = [0u8; 64];
let mut w = Writer::new(&mut payload);
encode_ctx_unsigned(&mut w, 0, 11).unwrap();
encode_ctx_unsigned(&mut w, 1, 22).unwrap();
Tag::Opening { tag_num: 2 }.encode(&mut w).unwrap();
Tag::Closing { tag_num: 2 }.encode(&mut w).unwrap();
let mut r = Reader::new(w.as_written());
let ack = ConfirmedPrivateTransferAck::decode(&mut r).unwrap();
assert_eq!(ack.result_block, Some(Vec::new()));
}
}