open-standards/openpay/crates/op-emv/src/stream.rs
Transaction Science 70ecd6caa0 Initial import — three open standards under one shelf
Transaction Science open standards monorepo. Each subdirectory is a
self-contained reference protocol with its own README and licence.

- openpay/    Apache-2.0  payment-acceptance stack (Rust, 26 crates)
- smart-byte/ CC-BY-4.0   content-addressed value carrier (spec)
- eoc/        CC-BY-4.0   energy-optimised compute (spec)

The wire format and the right to fork are public. The optional hosted
services are the offer; the protocols are not.
2026-05-23 07:58:09 -04:00

370 lines
11 KiB
Rust

//! Zero-allocation streaming TLV iterator.
//!
//! Parses BER-TLV one entry at a time, returning a [`TlvRef`] that
//! borrows from the input slice. Suitable for `no_std` and for the
//! secure-element interface where heap allocation isn't an option.
//!
//! Padding (`0x00` bytes between TLVs) is silently skipped per
//! EMV Book 3 §B.3.
//!
//! ## Usage
//!
//! ```
//! use op_emv::stream::TlvIter;
//! let bytes = [0x9Fu8, 0x02, 0x06, 0, 0, 0, 0, 1, 0];
//! for tlv in TlvIter::new(&bytes) {
//! let tlv = tlv.unwrap();
//! // tlv.tag, tlv.value, tlv.offset
//! }
//! ```
use crate::error::{Error, Result};
use crate::tag::Tag;
/// One TLV, borrowed from the input buffer.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TlvRef<'a> {
/// The tag.
pub tag: Tag,
/// The value bytes (length is `value.len()`).
pub value: &'a [u8],
/// Byte offset of this TLV's first (tag) byte within the original
/// buffer. Useful for error reporting and reconstruction.
pub offset: usize,
/// Byte offset of the *value* within the original buffer, i.e.
/// `offset` advanced past the tag and length fields. This is the
/// correct base when descending into a constructed TLV's children
/// so their offsets stay relative to the original input.
pub value_offset: usize,
}
impl<'a> TlvRef<'a> {
/// Iterate over the children of a constructed TLV.
///
/// # Errors
/// `NotConstructed` if `self.tag` is primitive.
pub fn children(&self) -> Result<TlvIter<'a>> {
if !self.tag.is_constructed() {
return Err(Error::NotConstructed {
tag: self.tag.0,
offset: self.offset,
});
}
Ok(TlvIter::with_base_offset(self.value, self.value_offset))
}
}
/// Lending-iterator over a BER-TLV byte slice.
///
/// Implements `Iterator<Item = Result<TlvRef<'a>>>`. Failures terminate
/// iteration: once the iterator yields `Err(_)`, subsequent `next()`
/// calls return `None`.
#[derive(Clone, Debug)]
pub struct TlvIter<'a> {
buf: &'a [u8],
pos: usize,
base_offset: usize,
failed: bool,
}
impl<'a> TlvIter<'a> {
/// Construct a fresh iterator over `buf`, treating the start of
/// `buf` as absolute offset 0.
#[must_use]
pub const fn new(buf: &'a [u8]) -> Self {
Self {
buf,
pos: 0,
base_offset: 0,
failed: false,
}
}
/// Construct an iterator over a sub-slice, preserving the absolute
/// offset of the parent so error messages and `TlvRef.offset`
/// values stay accurate across nested constructed TLVs.
#[must_use]
pub const fn with_base_offset(buf: &'a [u8], base_offset: usize) -> Self {
Self {
buf,
pos: 0,
base_offset,
failed: false,
}
}
/// Read a BER-TLV length field starting at `self.pos`.
fn read_length(&mut self) -> Result<usize> {
if self.pos >= self.buf.len() {
return Err(Error::UnexpectedEof {
offset: self.base_offset + self.pos,
});
}
let first = self.buf[self.pos];
self.pos += 1;
if first & 0x80 == 0 {
return Ok(first as usize);
}
let n_bytes = (first & 0x7F) as usize;
if n_bytes == 0 {
return Err(Error::IndefiniteLength {
offset: self.base_offset + self.pos - 1,
});
}
if n_bytes > 4 {
return Err(Error::LengthTooLong {
bytes: n_bytes,
offset: self.base_offset + self.pos - 1,
});
}
if self.pos + n_bytes > self.buf.len() {
return Err(Error::UnexpectedEof {
offset: self.base_offset + self.pos + n_bytes,
});
}
let mut length: usize = 0;
for _ in 0..n_bytes {
length = (length << 8) | self.buf[self.pos] as usize;
self.pos += 1;
}
Ok(length)
}
}
impl<'a> Iterator for TlvIter<'a> {
type Item = Result<TlvRef<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
// Skip 0x00 padding bytes between TLVs. EMV Book 3 §B.3 permits
// arbitrary padding; we silently consume it.
while self.pos < self.buf.len() && self.buf[self.pos] == 0x00 {
self.pos += 1;
}
if self.pos >= self.buf.len() {
return None;
}
let entry_start = self.pos;
// Tag.
let (tag, tag_len) = match Tag::read(self.buf, self.pos) {
Ok(t) => t,
Err(e) => {
self.failed = true;
return Some(Err(match e {
Error::UnexpectedEof { offset } => Error::UnexpectedEof {
offset: self.base_offset + offset,
},
Error::TagTooLong { offset } => Error::TagTooLong {
offset: self.base_offset + offset,
},
other => other,
}));
}
};
self.pos += tag_len;
// Length.
let length = match self.read_length() {
Ok(l) => l,
Err(e) => {
self.failed = true;
return Some(Err(e));
}
};
// Value.
let remaining = self.buf.len() - self.pos;
if length > remaining {
self.failed = true;
return Some(Err(Error::LengthExceedsBuffer {
declared: length,
remaining,
offset: self.base_offset + self.pos,
}));
}
let value_start = self.base_offset + self.pos;
let value = &self.buf[self.pos..self.pos + length];
self.pos += length;
Some(Ok(TlvRef {
tag,
value,
offset: self.base_offset + entry_start,
value_offset: value_start,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
// ---- Length decoding ----
#[test]
fn short_form_length() {
let mut it = TlvIter::new(&[0x6F, 0x05, 1, 2, 3, 4, 5]);
let tlv = it.next().unwrap().unwrap();
assert_eq!(tlv.value.len(), 5);
assert_eq!(tlv.value, &[1, 2, 3, 4, 5]);
}
#[test]
fn long_form_length_one_byte() {
// 0x81 means "one more length byte follows"
let mut buf = vec![0x6F, 0x81, 0x80]; // length 128
buf.extend(std::iter::repeat_n(0xAB, 128));
let mut it = TlvIter::new(&buf);
let tlv = it.next().unwrap().unwrap();
assert_eq!(tlv.value.len(), 128);
}
#[test]
fn long_form_length_two_bytes() {
// 0x82 0x01 0x00 = 256
let mut buf = vec![0x6F, 0x82, 0x01, 0x00];
buf.extend(std::iter::repeat_n(0xCD, 256));
let mut it = TlvIter::new(&buf);
let tlv = it.next().unwrap().unwrap();
assert_eq!(tlv.value.len(), 256);
}
#[test]
fn indefinite_length_rejected() {
// 0x80 alone = indefinite, banned by EMV.
let mut it = TlvIter::new(&[0x6F, 0x80, 0x01, 0x02, 0x00, 0x00]);
let err = it.next().unwrap();
assert!(matches!(err, Err(Error::IndefiniteLength { .. })));
}
#[test]
fn length_too_long_rejected() {
// 0x85 = "5 length bytes follow" — EMV max is 4
let buf = [0x6F, 0x85, 0, 0, 0, 0, 1];
let mut it = TlvIter::new(&buf);
let err = it.next().unwrap();
assert!(matches!(err, Err(Error::LengthTooLong { bytes: 5, .. })));
}
#[test]
fn length_exceeds_buffer_rejected() {
let buf = [0x6F, 0x10, 0x01, 0x02]; // declares 16, only 2 follow
let mut it = TlvIter::new(&buf);
let err = it.next().unwrap();
assert!(matches!(
err,
Err(Error::LengthExceedsBuffer {
declared: 16,
remaining: 2,
..
})
));
}
// ---- Multi-byte tags ----
#[test]
fn multibyte_tag_parsed() {
let buf = [0x9F, 0x02, 0x06, 0, 0, 0, 0, 1, 0];
let mut it = TlvIter::new(&buf);
let tlv = it.next().unwrap().unwrap();
assert_eq!(tlv.tag.0, 0x9F02);
assert_eq!(tlv.value.len(), 6);
}
// ---- Padding ----
#[test]
fn padding_between_tlvs_skipped() {
let buf = [0x00, 0x00, 0x9F, 0x02, 0x01, 0xAA, 0x00, 0x9A, 0x01, 0xBB];
let parsed: Vec<TlvRef<'_>> = TlvIter::new(&buf).map(|t| t.unwrap()).collect();
assert_eq!(parsed.len(), 2);
assert_eq!(parsed[0].tag.0, 0x9F02);
assert_eq!(parsed[0].value, &[0xAA]);
assert_eq!(parsed[1].tag.0, 0x9A);
assert_eq!(parsed[1].value, &[0xBB]);
}
#[test]
fn trailing_padding_ends_cleanly() {
let buf = [0x9F, 0x02, 0x01, 0xAA, 0x00, 0x00, 0x00];
let parsed: Vec<TlvRef<'_>> = TlvIter::new(&buf).map(|t| t.unwrap()).collect();
assert_eq!(parsed.len(), 1);
}
#[test]
fn empty_buffer_yields_nothing() {
let mut it = TlvIter::new(&[]);
assert!(it.next().is_none());
}
#[test]
fn all_padding_yields_nothing() {
let mut it = TlvIter::new(&[0x00, 0x00, 0x00]);
assert!(it.next().is_none());
}
// ---- Offsets ----
#[test]
fn offsets_track_position_in_input() {
// Two TLVs back to back.
let buf = [0x9A, 0x03, 0x01, 0x02, 0x03, 0x9C, 0x01, 0x00];
let parsed: Vec<TlvRef<'_>> = TlvIter::new(&buf).map(|t| t.unwrap()).collect();
assert_eq!(parsed[0].offset, 0);
assert_eq!(parsed[1].offset, 5);
}
// ---- Constructed children ----
#[test]
fn constructed_children_iterate() {
// 6F (constructed) wraps 84 (primitive) and A5 (constructed).
let buf = [0x6F, 0x06, 0x84, 0x02, 0xAA, 0xBB, 0xA5, 0x00];
let outer = TlvIter::new(&buf).next().unwrap().unwrap();
assert_eq!(outer.tag.0, 0x6F);
let children: Vec<TlvRef<'_>> = outer.children().unwrap().map(|t| t.unwrap()).collect();
assert_eq!(children.len(), 2);
assert_eq!(children[0].tag.0, 0x84);
assert_eq!(children[0].value, &[0xAA, 0xBB]);
assert_eq!(children[1].tag.0, 0xA5);
assert!(children[1].value.is_empty());
}
#[test]
fn child_offsets_relative_to_input() {
// Wrapper at offset 0; children at 2 and 6 within input.
let buf = [0x6F, 0x06, 0x84, 0x02, 0xAA, 0xBB, 0xA5, 0x00];
let outer = TlvIter::new(&buf).next().unwrap().unwrap();
let children: Vec<TlvRef<'_>> = outer.children().unwrap().map(|t| t.unwrap()).collect();
assert_eq!(children[0].offset, 2); // 84 02 AA BB starts here
assert_eq!(children[1].offset, 6); // A5 00 starts here
}
#[test]
fn children_on_primitive_errors() {
// 84 is primitive.
let buf = [0x84, 0x02, 0xAA, 0xBB];
let tlv = TlvIter::new(&buf).next().unwrap().unwrap();
assert!(matches!(
tlv.children(),
Err(Error::NotConstructed { tag: 0x84, .. })
));
}
// ---- Iterator contract ----
#[test]
fn error_terminates_iteration() {
let buf = [0x6F, 0x80]; // indefinite length error
let mut it = TlvIter::new(&buf);
assert!(it.next().unwrap().is_err());
assert!(it.next().is_none());
assert!(it.next().is_none()); // idempotent
}
}