From 10cf627243b979cf3e248ef12555cb96769ac33c Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Tue, 12 Dec 2023 15:04:18 +0100 Subject: [PATCH 1/2] Add Host Configuration This is to change the backend configuration of the PT. --- zvt/data/change_host_config.blob | 1 + zvt/src/feig/packets/mod.rs | 37 +++++++++++++++++++++++++++ zvt/src/feig/packets/tlv.rs | 27 ++++++++++++++++++++ zvt/src/feig/sequences.rs | 43 +++++++++++++++++++++++++++++++ zvt_builder/src/encoding.rs | 5 ++-- zvt_cli/src/main.rs | 44 ++++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 zvt/data/change_host_config.blob diff --git a/zvt/data/change_host_config.blob b/zvt/data/change_host_config.blob new file mode 100644 index 0000000..5894d34 --- /dev/null +++ b/zvt/data/change_host_config.blob @@ -0,0 +1 @@ +äÿ@4VÿAÕ·ivÁ \ No newline at end of file diff --git a/zvt/src/feig/packets/mod.rs b/zvt/src/feig/packets/mod.rs index 1820622..4035169 100644 --- a/zvt/src/feig/packets/mod.rs +++ b/zvt/src/feig/packets/mod.rs @@ -61,6 +61,17 @@ pub struct WriteFile { pub tlv: Option, } +/// Configuration packages. They all use the "Change Configuration" flow, but +/// with vastly different parameters, hence we have one for each flow. The Change Configuration +/// is described in 2.40, but since this is very hardware manufacturer specific, we put this one +/// here. So mostly see cVEND 6.7-6.16. +#[derive(Debug, PartialEq, Zvt)] +#[zvt_control_field(class = 0x08, instr = 0x13)] +pub struct ChangeConfiguration { + #[zvt_bmp(number = 0x06, length = length::Tlv)] + pub tlv: tlv::ChangeConfiguration, +} + /// Feig, 5.1 #[derive(Debug, PartialEq, Zvt)] #[zvt_control_field(class = 0x0f, instr = 0xa1)] @@ -86,6 +97,7 @@ mod test { use super::*; use crate::packets::tests::get_bytes; use crate::ZvtSerializer; + use std::net::Ipv4Addr; #[test] fn test_request_for_data() { @@ -230,4 +242,29 @@ mod test { let actual_bytes = expected.zvt_serialize(); assert_eq!(actual_bytes[..26], bytes[..26]); } + + #[test] + fn test_change_host_config() { + let bytes = get_bytes("change_host_config.blob"); + let addr = Ipv4Addr::new(213, 183, 19, 105); + let addr_u32: u32 = addr.into(); + + let expected = ChangeConfiguration { + tlv: tlv::ChangeConfiguration { + system_information: tlv::SystemInformation { + password: 123456, + host_configuration_data: Some(tlv::HostConfigurationData { + ip: addr_u32, + port: 30401, + config_byte: 1, + }), + }, + }, + }; + assert_eq!( + ChangeConfiguration::zvt_deserialize(&bytes).unwrap().0, + expected + ); + assert_eq!(bytes, expected.zvt_serialize()); + } } diff --git a/zvt/src/feig/packets/tlv.rs b/zvt/src/feig/packets/tlv.rs index 59fb450..b61c507 100644 --- a/zvt/src/feig/packets/tlv.rs +++ b/zvt/src/feig/packets/tlv.rs @@ -76,3 +76,30 @@ pub struct WriteFile { #[zvt_tlv(tag = 0x2d)] pub files: Vec, } + +#[derive(Debug, PartialEq, Zvt, Default)] +pub struct HostConfigurationData { + #[zvt_bmp(encoding = encoding::BigEndian)] + pub ip: u32, + + #[zvt_bmp(encoding = encoding::BigEndian)] + pub port: u16, + + #[zvt_bmp(encoding = encoding::BigEndian)] + pub config_byte: u8, +} + +#[derive(Debug, PartialEq, Zvt, Default)] +pub struct SystemInformation { + #[zvt_tlv(encoding = encoding::Bcd, tag = 0xff40)] + pub password: usize, + + #[zvt_tlv(tag = 0xff41)] + pub host_configuration_data: Option, +} + +#[derive(Debug, PartialEq, Zvt, Default)] +pub struct ChangeConfiguration { + #[zvt_tlv(tag = 0xe4)] + pub system_information: SystemInformation, +} diff --git a/zvt/src/feig/sequences.rs b/zvt/src/feig/sequences.rs index ac1e11e..a2f5b60 100644 --- a/zvt/src/feig/sequences.rs +++ b/zvt/src/feig/sequences.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::io::Seek; use std::io::{Error, ErrorKind}; use std::marker::Unpin; +use std::net::Ipv4Addr; use std::os::unix::fs::FileExt; use std::path::{Path, PathBuf}; use std::pin::Pin; @@ -213,3 +214,45 @@ impl Sequence for FactoryReset { type Input = super::packets::CVendFunctions; type Output = FactoryResetResponse; } + +pub struct ChangeHostConfiguration; + +#[derive(Debug, ZvtEnum)] +pub enum ChangeHostConfigurationResponse { + CompletionData(packets::CompletionData), + Abort(packets::Abort), +} + +impl ChangeHostConfiguration { + pub fn into_stream( + password: usize, + ip: Ipv4Addr, + port: u16, + config_byte: u8, + src: &mut PacketTransport, + ) -> Pin> + '_>> + where + Source: AsyncReadExt + AsyncWriteExt + Unpin + Send, + { + let s = try_stream! { + let packet = super::packets::ChangeConfiguration { + tlv: super::packets::tlv::ChangeConfiguration { + system_information: super::packets::tlv::SystemInformation { + password, + host_configuration_data: Some(super::packets::tlv::HostConfigurationData { + ip: ip.into(), + port, + config_byte, + }), + }, + }, + }; + src.write_packet_with_ack(&packet).await?; + + let response = src.read_packet().await?; + src.write_packet(&packets::Ack {}).await?; + yield response; + }; + Box::pin(s) + } +} diff --git a/zvt_builder/src/encoding.rs b/zvt_builder/src/encoding.rs index f5065ec..dd0f88b 100644 --- a/zvt_builder/src/encoding.rs +++ b/zvt_builder/src/encoding.rs @@ -142,7 +142,8 @@ impl Encoding for Default { /// The default is when the [Tag] is used as a Bmp-number or as a Tlv-tag. impl encoding::Encoding for Default { fn encode(input: &Tag) -> Vec { - if (input.0 >> 8) == 0x1f { + let low = input.0 >> 8; + if low == 0x1f || low == 0xff { input.0.to_be_bytes().to_vec() } else { vec![input.0 as u8] @@ -151,7 +152,7 @@ impl encoding::Encoding for Default { fn decode(bytes: &[u8]) -> ZVTResult<(Tag, &[u8])> { let (tag, new_bytes): (u8, _) = encoding::BigEndian::decode(bytes)?; - if tag == 0x1f { + if tag == 0x1f || tag == 0xff { if bytes.len() < 2 { Err(ZVTError::IncompleteData) } else { diff --git a/zvt_cli/src/main.rs b/zvt_cli/src/main.rs index 480c04a..b179c2c 100644 --- a/zvt_cli/src/main.rs +++ b/zvt_cli/src/main.rs @@ -2,6 +2,7 @@ use anyhow::{bail, Result}; use argh::FromArgs; use env_logger::{Builder, Env}; use std::io::Write; +use std::net::Ipv4Addr; use tokio::net::TcpStream; use tokio_stream::StreamExt; use zvt::sequences::Sequence; @@ -23,6 +24,7 @@ enum SubCommands { ReadCard(ReadCardArgs), Reservation(ReservationArgs), PartialReversal(PartialReversalArgs), + ChangeHostConfiguration(ChangeHostConfigurationArgs), } #[derive(FromArgs, PartialEq, Debug)] @@ -181,6 +183,23 @@ struct PartialReversalArgs { bmp_data: Option, } +#[derive(FromArgs, PartialEq, Debug)] +/// Changes the Host the payment terminal connects to. +#[argh(subcommand, name = "change_host_config")] +struct ChangeHostConfigurationArgs { + /// the IP the terminal should connect to. + #[argh(option)] + ip: Ipv4Addr, + + /// the port the terminal should connect to. + #[argh(option, default = "30401")] + port: u16, + + /// see reservation. + #[argh(option, default = "1")] + configuration_byte: u8, +} + #[derive(FromArgs, Debug)] /// Example tool to interact with the payment terminal. struct Args { @@ -477,6 +496,28 @@ async fn partial_reversal(socket: &mut PacketTransport, args: PartialReversalArg Ok(()) } +async fn change_host_config( + socket: &mut PacketTransport, + password: usize, + args: ChangeHostConfigurationArgs, +) -> Result<()> { + let mut stream = feig::sequences::ChangeHostConfiguration::into_stream( + password, + args.ip, + args.port, + args.configuration_byte, + socket, + ); + use feig::sequences::ChangeHostConfigurationResponse::*; + while let Some(response) = stream.next().await { + match response? { + CompletionData(_) => (), + Abort(data) => bail!("Received Abort: {:?}", data), + } + } + Ok(()) +} + #[tokio::main] async fn main() -> Result<()> { init_logger(); @@ -499,6 +540,9 @@ async fn main() -> Result<()> { SubCommands::ReadCard(a) => read_card(&mut socket, &a).await?, SubCommands::Reservation(a) => reservation(&mut socket, a).await?, SubCommands::PartialReversal(a) => partial_reversal(&mut socket, a).await?, + SubCommands::ChangeHostConfiguration(a) => { + change_host_config(&mut socket, args.password, a).await? + } } Ok(()) From 9815f88a9e968af314af6c1ffd569a81e1bc32ab Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Wed, 13 Dec 2023 10:09:59 +0100 Subject: [PATCH 2/2] Addressed reviewer comments. --- zvt/src/feig/sequences.rs | 36 +++--------------------------------- zvt_builder/src/encoding.rs | 4 ++++ zvt_cli/src/main.rs | 21 ++++++++++++++------- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/zvt/src/feig/sequences.rs b/zvt/src/feig/sequences.rs index a2f5b60..8db1d1a 100644 --- a/zvt/src/feig/sequences.rs +++ b/zvt/src/feig/sequences.rs @@ -8,7 +8,6 @@ use std::collections::HashMap; use std::io::Seek; use std::io::{Error, ErrorKind}; use std::marker::Unpin; -use std::net::Ipv4Addr; use std::os::unix::fs::FileExt; use std::path::{Path, PathBuf}; use std::pin::Pin; @@ -223,36 +222,7 @@ pub enum ChangeHostConfigurationResponse { Abort(packets::Abort), } -impl ChangeHostConfiguration { - pub fn into_stream( - password: usize, - ip: Ipv4Addr, - port: u16, - config_byte: u8, - src: &mut PacketTransport, - ) -> Pin> + '_>> - where - Source: AsyncReadExt + AsyncWriteExt + Unpin + Send, - { - let s = try_stream! { - let packet = super::packets::ChangeConfiguration { - tlv: super::packets::tlv::ChangeConfiguration { - system_information: super::packets::tlv::SystemInformation { - password, - host_configuration_data: Some(super::packets::tlv::HostConfigurationData { - ip: ip.into(), - port, - config_byte, - }), - }, - }, - }; - src.write_packet_with_ack(&packet).await?; - - let response = src.read_packet().await?; - src.write_packet(&packets::Ack {}).await?; - yield response; - }; - Box::pin(s) - } +impl Sequence for ChangeHostConfiguration { + type Input = super::packets::ChangeConfiguration; + type Output = ChangeHostConfigurationResponse; } diff --git a/zvt_builder/src/encoding.rs b/zvt_builder/src/encoding.rs index dd0f88b..5d814d0 100644 --- a/zvt_builder/src/encoding.rs +++ b/zvt_builder/src/encoding.rs @@ -152,6 +152,10 @@ impl encoding::Encoding for Default { fn decode(bytes: &[u8]) -> ZVTResult<(Tag, &[u8])> { let (tag, new_bytes): (u8, _) = encoding::BigEndian::decode(bytes)?; + // §9.4.1 of the PA00P015 defines a lot of tags, there is a bunch of 2 byte tags that start + // with 0xf1xx, there is also 0xff01-0xff04 defined there. + // TODO(hrapp): The same section also mentions tags 0x9f5a, 0x9f5b, and three byte tags + // 0x1f8000 and 0x1f8001 which we are not handling correctly currently. if tag == 0x1f || tag == 0xff { if bytes.len() < 2 { Err(ZVTError::IncompleteData) diff --git a/zvt_cli/src/main.rs b/zvt_cli/src/main.rs index b179c2c..c41d988 100644 --- a/zvt_cli/src/main.rs +++ b/zvt_cli/src/main.rs @@ -501,13 +501,20 @@ async fn change_host_config( password: usize, args: ChangeHostConfigurationArgs, ) -> Result<()> { - let mut stream = feig::sequences::ChangeHostConfiguration::into_stream( - password, - args.ip, - args.port, - args.configuration_byte, - socket, - ); + let request = feig::packets::ChangeConfiguration { + tlv: feig::packets::tlv::ChangeConfiguration { + system_information: feig::packets::tlv::SystemInformation { + password, + host_configuration_data: Some(feig::packets::tlv::HostConfigurationData { + ip: args.ip.into(), + port: args.port, + config_byte: args.configuration_byte, + }), + }, + }, + }; + + let mut stream = feig::sequences::ChangeHostConfiguration::into_stream(&request, socket); use feig::sequences::ChangeHostConfigurationResponse::*; while let Some(response) = stream.next().await { match response? {