From 3f0aaa259de073303a211e50c6b1a841119c6ff4 Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Wed, 11 Nov 2020 19:38:20 +0100 Subject: [PATCH] Refactored write orders to be independent objects, to support parsing reads --- Cargo.toml | 3 +- examples/demo3270.rs | 59 +++++++- src/encoding.rs | 2 +- src/tn3270.rs | 2 +- src/tn3270/stream.rs | 338 ++++++++++++++++++++++++++++++------------- 5 files changed, 289 insertions(+), 115 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ee2b62..157df46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ structopt = "0.3.20" anyhow = "1.0.33" thiserror = "1.0.21" bitflags = "1.2.1" -hex = "0.4.2" \ No newline at end of file +hex = "0.4.2" +snafu = "0.6.9" \ No newline at end of file diff --git a/examples/demo3270.rs b/examples/demo3270.rs index 13a35a2..7b1ec75 100644 --- a/examples/demo3270.rs +++ b/examples/demo3270.rs @@ -2,6 +2,7 @@ use structopt::StructOpt; use std::time::Duration; use tn3270s::tn3270; +use tn3270s::tn3270::stream::WriteOrder::SetBufferAddress; #[derive(StructOpt)] pub struct Cli { @@ -12,16 +13,58 @@ pub struct Cli { } +// _~^~^~_ +// \) / o o \ (/ +// '_ ¬ _' +// / '-----' \ +// 1234567890123456 + +static rust_logo: [&'static str; 4] = [ + r#" _~^~^~_ "#, + r#" \) / o o \ (/ "#, + r#" '_ ¬ _' "#, + r#" / '-----' \ "#, +]; + fn run(mut session: tn3270::Session) -> anyhow::Result<()> { use tn3270::stream::*; - let mut record = WriteCommand::new(WriteCommandCode::Write, WCC::RESET | WCC::KBD_RESTORE); let bufsz = BufferAddressCalculator { width: 80, height: 24 }; - record.set_buffer_address(0) - .erase_unprotected_to_address(bufsz.encode_address(79, 23)) - .set_buffer_address(bufsz.encode_address(31,1)) - .set_attribute(ExtendedFieldAttribute::ForegroundColor(Color::Red)) - .send_text("Hello from Rust!"); - session.send_record(record)?; + let mut record = WriteCommand { + command: WriteCommandCode::Write, + wcc: WCC::RESET | WCC::KBD_RESTORE | WCC::RESET_MDT, + orders: vec![ + WriteOrder::SetBufferAddress(0), + WriteOrder::EraseUnprotectedToAddress(bufsz.last_address()), + WriteOrder::SetBufferAddress(bufsz.encode_address(1, 31)), + WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![ + // ExtendedFieldAttribute::ForegroundColor(Color::Red), + ]), + WriteOrder::SendText("Hello from Rust!".into()), + WriteOrder::SetBufferAddress(bufsz.encode_address(8, 21)), + WriteOrder::StartFieldExtended(FieldAttribute::INTENSE_SELECTOR_PEN_DETECTABLE, vec![]), + WriteOrder::SendText(" ".into()), + WriteOrder::StartField(FieldAttribute::PROTECTED), + WriteOrder::SetBufferAddress(bufsz.encode_address(8, 10)), + WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![ + ExtendedFieldAttribute::ForegroundColor(Color::Turquoise), + ]), + WriteOrder::SendText("Name:".into()), + ], + }; + + for (i, line) in rust_logo.iter().enumerate() { + record.orders.push(WriteOrder::SetBufferAddress(bufsz.encode_address(3+i as u16, 31))); + record.orders.push(WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![ + ExtendedFieldAttribute::ForegroundColor(Color::Red), + ])); + record.orders.push(WriteOrder::SendText((*line).into())); + } + session.send_record(&record)?; + session.send_record(&WriteCommand{ + command: WriteCommandCode::Write, + wcc: WCC::RESET_MDT | WCC::KBD_RESTORE, + orders: vec![], + })?; let record = session.receive_record(None)?; if let Some(record) = record { @@ -31,7 +74,7 @@ fn run(mut session: tn3270::Session) -> anyhow::Result<()> { } - std::thread::sleep(Duration::from_secs(60)); + std::thread::sleep(Duration::from_secs(5)); Ok(()) } diff --git a/src/encoding.rs b/src/encoding.rs index 134a2d1..0063194 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,4 +1,4 @@ -mod cp037; +pub(crate) mod cp037; pub trait SBCS { fn from_unicode(ch: char) -> Option; diff --git a/src/tn3270.rs b/src/tn3270.rs index 79757c7..3c47e4c 100644 --- a/src/tn3270.rs +++ b/src/tn3270.rs @@ -166,7 +166,7 @@ impl Session { self.stream.set_nonblocking(true)?; while len != 0 { let events = self.parser.receive(&buf[..len]); - self.process_events(events); + self.process_events(events)?; len = match self.stream.read(buf.as_mut_slice()) { Ok(len) => len, Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => 0, diff --git a/src/tn3270/stream.rs b/src/tn3270/stream.rs index 97b2c84..faf6977 100644 --- a/src/tn3270/stream.rs +++ b/src/tn3270/stream.rs @@ -1,7 +1,15 @@ use bitflags::bitflags; use std::io::Write; +use std::convert::TryFrom; +use snafu::Snafu; -static WCC_TRANS: [u8; 64] = [ +#[derive(Clone, Debug, Snafu)] +pub enum StreamFormatError { + #[snafu(display("Invalid AID: {:02x}", aid))] + InvalidAID { aid: u8, } +} + +const WCC_TRANS: [u8; 64] = [ 0x40, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, @@ -57,7 +65,9 @@ pub trait OutputRecord { } pub struct WriteCommand { - data: Vec, + pub command: WriteCommandCode, + pub wcc: WCC, + pub orders: Vec, } #[derive(Copy, Clone, Debug)] @@ -214,6 +224,18 @@ impl ExtendedFieldAttribute { ExtendedFieldAttribute::FieldValidation(v) => (0xC1, v.bits()), } } + + fn encode_into(&self, output: &mut Vec) { + let (typ, val) = self.encoded(); + output.extend_from_slice(&[typ, val]); + } + +} + +impl Into for &ExtendedFieldAttribute { + fn into(self) -> ExtendedFieldAttribute { + *self + } } #[derive(Copy, Clone, Debug)] @@ -223,118 +245,226 @@ pub struct BufferAddressCalculator { } impl BufferAddressCalculator { - pub fn encode_address(self, x: u16, y: u16) -> u16 { + pub fn encode_address(self, y: u16, x: u16) -> u16 { self.width * y + x } + + pub fn last_address(self) -> u16 { + self.width * self.height - 1 + } +} + +#[derive(Clone, Debug)] +pub enum WriteOrder { + StartField(FieldAttribute), + StartFieldExtended(FieldAttribute, Vec), + SetBufferAddress(u16), + SetAttribute(ExtendedFieldAttribute), + ModifyField(Vec), + InsertCursor(u16), + ProgramTab, + RepeatToAddress(u16, char), + EraseUnprotectedToAddress(u16), + GraphicEscape(u8), + SendText(String), +} + +impl WriteOrder { + + pub fn serialize(&self, output: &mut Vec) { + match self { + WriteOrder::StartField(attr) => output.extend_from_slice(&[0x1D, attr.bits()]), + WriteOrder::StartFieldExtended(attr, rest) => { + output.extend_from_slice(&[0x29, rest.len() as u8 + 1]); + ExtendedFieldAttribute::FieldAttribute(*attr).encode_into(&mut* output); + for attr in rest { + attr.encode_into(&mut *output); + } + } + WriteOrder::SetBufferAddress(addr) => output.extend_from_slice(&[0x11, (addr >> 8) as u8, (addr & 0xff) as u8]), + WriteOrder::SetAttribute(attr) => { + let (typ, val) = attr.encoded(); + output.extend_from_slice(&[0x28, typ, val]); + } + WriteOrder::ModifyField(attrs) => { + output.extend_from_slice(&[0x2C, attrs.len() as u8]); + for attr in attrs { + attr.encode_into(&mut* output); + } + } + WriteOrder::InsertCursor(addr) => output.extend_from_slice(&[0x11, (addr >> 8) as u8, (addr & 0xff) as u8]), + WriteOrder::ProgramTab => output.push(0x05), + WriteOrder::RepeatToAddress(addr, ch) => { + // TODO: COme up with a way to allow graphic escape here + output.extend_from_slice(&[0x3C, (addr >> 8) as u8, (addr & 0xff) as u8, crate::encoding::cp037::ENCODE_TBL[*ch as usize]]) + } + WriteOrder::EraseUnprotectedToAddress(addr) => { + output.extend_from_slice(&[0x12, (addr >> 8) as u8, (addr & 0xff) as u8]) + } + WriteOrder::GraphicEscape(ch) => output.extend_from_slice(&[0x08, *ch]), + WriteOrder::SendText(text) => { + output.extend(crate::encoding::to_cp037(text.chars())); + } + } + } } impl WriteCommand { - pub fn new(command: WriteCommandCode, wcc: WCC) -> Self { - WriteCommand { - data: vec![command.to_command_code(), wcc.to_ascii_compat(), ] + pub fn serialize(&self, output: &mut Vec) { + output.push(self.command.to_command_code()); + output.push(self.wcc.to_ascii_compat()); + for order in self.orders.iter() { + order.serialize(&mut *output); } } - - pub fn start_field(&mut self, fa: FieldAttribute) -> &mut Self { - self.data.push(0x1D); - self.data.push(fa.bits()); - self - } - - pub fn start_field_extended(&mut self, fa: FieldAttribute, attrs: impl IntoIterator) -> &mut Self { - self.data.push(0x29); - let nattr_pos = self.data.len(); - self.data.push(0); - let mut i = 1; - self.data.push(0xC0); - self.data.push(make_ascii_translatable(fa.bits())); - - for (typ, value) in attrs.into_iter().map(ExtendedFieldAttribute::encoded) { - self.data.push(typ); - self.data.push(value); - i += 1; - } - self.data[nattr_pos] = i; - self - } - - pub fn set_buffer_address(&mut self, address: u16) -> &mut Self { - self.data.push(0x11); - self.data.push((address >> 8) as u8); - self.data.push((address & 0xFF) as u8); - return self; - } - - pub fn set_attribute(&mut self, attr: ExtendedFieldAttribute) -> &mut Self { - let (typ, val) = attr.encoded(); - self.data.extend_from_slice(&[0x28, typ, val]); - self - } - - pub fn modify_field(&mut self, attrs: impl IntoIterator) -> &mut Self { - self.data.push(0x2C); - let nattr_pos = self.data.len(); - self.data.push(0); - let mut i = 0; - for (typ, value) in attrs.into_iter().map(ExtendedFieldAttribute::encoded) { - self.data.push(typ); - self.data.push(value); - i += 1; - } - self.data[nattr_pos] = i; - self - } - - fn encode_address(&mut self, address: u16) { - self.data.push((address >> 8) as u8); - self.data.push((address & 0xFF) as u8); - - } - - pub fn insert_cursor(&mut self, address: u16) -> &mut Self { - self.data.push(0x13); - self.encode_address(address); - return self; - } - - pub fn program_tab(&mut self) -> &mut Self { - self.data.push(0x05); - self - } - - // This must be followed by either a character or a graphic escape - pub fn repeat_to_address(&mut self, address: u16) -> &mut Self { - self.data.push(0x3C); - self.encode_address(address); - self - } - - pub fn erase_unprotected_to_address(&mut self, address: u16) -> &mut Self { - self.data.push(0x12); - self.encode_address(address); - self - } - - pub fn graphic_escape(&mut self, charcode: u8) -> &mut Self { - self.data.push(0x08); - self.data.push(charcode); - self - } - - pub fn send_text(&mut self, data: &str) -> &mut Self { - self.data.extend(crate::encoding::to_cp037(data.chars())); - self - } } -impl AsRef<[u8]> for WriteCommand { - fn as_ref(&self) -> &[u8] { - self.data.as_slice() - } -} -impl Into> for WriteCommand { +impl Into> for &WriteCommand { fn into(self) -> Vec { - self.data + let mut result = vec![]; + self.serialize(&mut result); + result + } +} + +#[repr(u8)] +pub enum AID { + NoAIDGenerated, + NoAIDGeneratedPrinter, + StructuredField, + ReadPartition, + TriggerAction, + SysReq, + PF1, + PF2, + PF3, + PF4, + PF5, + PF6, + PF7, + PF8, + PF9, + PF10, + PF11, + PF12, + PF13, + PF14, + PF15, + PF16, + PF17, + PF18, + PF19, + PF20, + PF21, + PF22, + PF23, + PF24, + PA1, + PA2, + PA3, + Clear, + ClearPartition, + Enter, + SelectorPenAttention, + MagReaderOperatorID, + MagReaderNumber, +} + +impl From for u8 { + fn from(aid: AID) -> u8 { + use self::AID::*; + match aid { + NoAIDGenerated => 0x60, + NoAIDGeneratedPrinter => 0xE8, + StructuredField => 0x88, + ReadPartition => 0x61, + TriggerAction => 0x7f, + SysReq => 0xf0, + PF1 => 0xF1, + PF2 => 0xF2, + PF3 => 0xF3, + PF4 => 0xF4, + PF5 => 0xF5, + PF6 => 0xF6, + PF7 => 0xF7, + PF8 => 0xF8, + PF9 => 0xF9, + PF10 => 0x7A, + PF11 => 0x7B, + PF12 => 0x7C, + PF13 => 0xC1, + PF14 => 0xC2, + PF15 => 0xC3, + PF16 => 0xC4, + PF17 => 0xC5, + PF18 => 0xC6, + PF19 => 0xC7, + PF20 => 0xC8, + PF21 => 0xC9, + PF22 => 0x4A, + PF23 => 0x4B, + PF24 => 0x4C, + PA1 => 0x6C, + PA2 => 0x6E, + PA3 => 0x6B, + Clear => 0x6D, + ClearPartition => 0x6A, + Enter => 0x7D, + SelectorPenAttention => 0x7E, + MagReaderOperatorID => 0xE6, + MagReaderNumber => 0xE7, + } + } +} + + +impl TryFrom for AID { + type Error = StreamFormatError; + + fn try_from(aid: u8) -> Result { + use self::AID::*; + Ok(match aid { + 0x60 => NoAIDGenerated, + 0xE8 => NoAIDGeneratedPrinter, + 0x88 => StructuredField, + 0x61 => ReadPartition, + 0x7f => TriggerAction, + 0xf0 => SysReq, + 0xF1 => PF1, + 0xF2 => PF2, + 0xF3 => PF3, + 0xF4 => PF4, + 0xF5 => PF5, + 0xF6 => PF6, + 0xF7 => PF7, + 0xF8 => PF8, + 0xF9 => PF9, + 0x7A => PF10, + 0x7B => PF11, + 0x7C => PF12, + 0xC1 => PF13, + 0xC2 => PF14, + 0xC3 => PF15, + 0xC4 => PF16, + 0xC5 => PF17, + 0xC6 => PF18, + 0xC7 => PF19, + 0xC8 => PF20, + 0xC9 => PF21, + 0x4A => PF22, + 0x4B => PF23, + 0x4C => PF24, + 0x6C => PA1, + 0x6E => PA2, + 0x6B => PA3, + 0x6D => Clear, + 0x6A => ClearPartition, + 0x7D => Enter, + 0x7E => SelectorPenAttention, + 0xE6 => MagReaderOperatorID, + 0xE7 => MagReaderNumber, + _ => return Err(StreamFormatError::InvalidAID { aid }), + }) } } \ No newline at end of file