Refactored write orders to be independent objects, to support parsing reads
This commit is contained in:
@@ -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"
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod cp037;
|
||||
pub(crate) mod cp037;
|
||||
|
||||
pub trait SBCS {
|
||||
fn from_unicode(ch: char) -> Option<u8>;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user