Refactored write orders to be independent objects, to support parsing reads

This commit is contained in:
2020-11-11 19:38:20 +01:00
parent cfdfedb5b4
commit 3f0aaa259d
5 changed files with 289 additions and 115 deletions

View File

@@ -12,4 +12,5 @@ structopt = "0.3.20"
anyhow = "1.0.33"
thiserror = "1.0.21"
bitflags = "1.2.1"
hex = "0.4.2"
hex = "0.4.2"
snafu = "0.6.9"

View File

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

View File

@@ -1,4 +1,4 @@
mod cp037;
pub(crate) mod cp037;
pub trait SBCS {
fn from_unicode(ch: char) -> Option<u8>;

View File

@@ -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,

View File

@@ -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<u8>,
pub command: WriteCommandCode,
pub wcc: WCC,
pub orders: Vec<WriteOrder>,
}
#[derive(Copy, Clone, Debug)]
@@ -214,6 +224,18 @@ impl ExtendedFieldAttribute {
ExtendedFieldAttribute::FieldValidation(v) => (0xC1, v.bits()),
}
}
fn encode_into(&self, output: &mut Vec<u8>) {
let (typ, val) = self.encoded();
output.extend_from_slice(&[typ, val]);
}
}
impl Into<ExtendedFieldAttribute> 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<ExtendedFieldAttribute>),
SetBufferAddress(u16),
SetAttribute(ExtendedFieldAttribute),
ModifyField(Vec<ExtendedFieldAttribute>),
InsertCursor(u16),
ProgramTab,
RepeatToAddress(u16, char),
EraseUnprotectedToAddress(u16),
GraphicEscape(u8),
SendText(String),
}
impl WriteOrder {
pub fn serialize(&self, output: &mut Vec<u8>) {
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<u8>) {
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<Item=ExtendedFieldAttribute>) -> &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<Item=ExtendedFieldAttribute>) -> &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<Vec<u8>> for WriteCommand {
impl Into<Vec<u8>> for &WriteCommand {
fn into(self) -> Vec<u8> {
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<AID> 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<u8> for AID {
type Error = StreamFormatError;
fn try_from(aid: u8) -> Result<Self, Self::Error> {
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 }),
})
}
}