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(())