rust3270 0.1.1

rust3270 is a terminal server interface to the IBM 3270 terminal protocol, written in Rust.
Documentation
use std::convert::TryFrom;
use std::io::Write;

use snafu::{Snafu, ensure};

use crate::encoding::Encoding;
use crate::server::aid::AID;
use crate::server::extended_field_attributes::ExtendedFieldAttribute;
use crate::server::wcc::{FieldAttribute, WCC};

#[derive(Clone, Debug, Snafu, Eq, PartialEq)]
pub enum StreamFormatError {
    #[snafu(display("Invalid AID: {:02x}", aid))]
    InvalidAID { aid: u8 },
    #[snafu(display("Record ended early"))]
    UnexpectedEOR,
    #[snafu(display("Invalid data"))]
    InvalidData,
}

pub trait OutputRecord {
    type Response;

    fn write_to(&self, writer: &mut dyn Write) -> std::io::Result<()>;
}

#[derive(Debug, Clone)]
pub struct WriteCommand {
    pub command: WriteCommandCode,
    pub wcc: WCC,
    pub orders: Vec<WriteOrder>,
}

#[derive(Copy, Clone, Debug)]
pub enum WriteCommandCode {
    Write,
    EraseWrite,
    EraseWriteAlternate,
    EraseAllUnprotected,
    WriteStructuredField,
}

impl WriteCommandCode {
    pub fn to_command_code(self) -> u8 {
        match self {
            WriteCommandCode::Write => 0xF1,
            WriteCommandCode::EraseWrite => 0xF5,
            WriteCommandCode::EraseWriteAlternate => 0x7E,
            WriteCommandCode::EraseAllUnprotected => 0x6F,
            WriteCommandCode::WriteStructuredField => 0xF3,
        }
    }
}

#[derive(Copy, Clone, Debug)]
pub struct BufferAddressCalculator {
    pub width: u16,
    pub height: u16,
}

impl BufferAddressCalculator {
    pub fn encode_address(self, y: u16, x: u16) -> u16 {
        self.width * (y - 1) + (x - 1)
    }

    pub fn last_address(self) -> u16 {
        self.width * self.height - 1
    }

    pub fn decode_address(self, addr: u16) -> (u16, u16) {
        (addr / self.width, addr % self.width)
    }
}

#[derive(Clone, Debug)]
pub enum WriteOrder {
    StartField(FieldAttribute),
    StartFieldExtended(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(attrs) => {
                output.extend_from_slice(&[0x29, attrs.len() as u8]);
                for attr in attrs {
                    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.clone().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) => 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::encode_ascii_to(text.chars(), &Encoding::CP037));
            }
        }
    }
}

impl WriteCommand {
    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);
        }
    }
}

impl From<&WriteCommand> for Vec<u8> {
    fn from(val: &WriteCommand) -> Self {
        let mut result = vec![];
        val.serialize(&mut result);
        result
    }
}

#[derive(Debug, Clone)]
pub struct IncomingRecord {
    pub aid: AID,
    pub addr: u16,
    pub orders: Vec<WriteOrder>,
}

fn parse_addr(encoded: &[u8]) -> Result<u16, StreamFormatError> {
    match encoded[0] >> 6 {
        0b00 => Ok(((encoded[0] as u16) << 8) + encoded[1] as u16),
        0b01 | 0b11 => Ok((encoded[0] as u16 & 0x3F) << 6 | (encoded[1] as u16 & 0x3F)),
        _ => Err(StreamFormatError::InvalidData),
    }
}

impl IncomingRecord {
    pub fn parse_record(mut record: &[u8]) -> Result<Self, StreamFormatError> {
        if record.len() < 3 {
            return Err(StreamFormatError::UnexpectedEOR);
        }

        let aid = AID::try_from(record[0])?;
        let addr = parse_addr(&record[1..3])?;

        let mut result = Self { aid, addr, orders: vec![] };

        record = &record[3..];

        while !record.is_empty() {
            match record[0] {
                0x1D => {
                    ensure!(record.len() >= 2, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::StartField(
                        FieldAttribute::from_bits(record[1] & 0x3F)
                            .ok_or(StreamFormatError::InvalidData)?,
                    ));
                    record = &record[2..];
                }
                0x29 => {
                    ensure!(record.len() >= 2, UnexpectedEORSnafu);
                    let (header, body) = record.split_at(2);
                    let count = header[1] as usize;
                    ensure!(body.len() >= count * 2, UnexpectedEORSnafu);
                    let (attrs, rest) = body.split_at(2 * count);
                    record = rest;

                    result.orders.push(WriteOrder::StartFieldExtended(
                        attrs.chunks(2).map(ExtendedFieldAttribute::try_from).collect::<Result<
                            Vec<ExtendedFieldAttribute>,
                            StreamFormatError,
                        >>(
                        )?,
                    ))
                }
                0x11 => {
                    ensure!(record.len() >= 3, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::SetBufferAddress(parse_addr(&record[1..3])?));
                    record = &record[3..];
                }
                0x28 => {
                    ensure!(record.len() >= 3, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::SetAttribute(ExtendedFieldAttribute::try_from(
                        &record[1..3],
                    )?));
                    record = &record[3..];
                }
                0x2C => {
                    ensure!(record.len() >= 2, UnexpectedEORSnafu);
                    let (header, body) = record.split_at(2);
                    let count = header[1] as usize;
                    ensure!(body.len() >= count * 2, UnexpectedEORSnafu);
                    let (attrs, rest) = body.split_at(2 * count);
                    record = rest;

                    result.orders.push(WriteOrder::ModifyField(
                        attrs.chunks(2).map(ExtendedFieldAttribute::try_from).collect::<Result<
                            Vec<ExtendedFieldAttribute>,
                            StreamFormatError,
                        >>(
                        )?,
                    ))
                }
                0x13 => {
                    ensure!(record.len() >= 3, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::InsertCursor(parse_addr(&record[1..3])?));
                    record = &record[3..];
                }
                0x05 => {
                    result.orders.push(WriteOrder::ProgramTab);
                    record = &record[1..];
                }
                0x3C => {
                    ensure!(record.len() >= 4, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::RepeatToAddress(
                        parse_addr(&record[1..3])?,
                        crate::encoding::cp037::DECODE_TBL[record[4] as usize] as char,
                    ));
                    record = &record[4..]
                }
                0x12 => {
                    ensure!(record.len() >= 3, UnexpectedEORSnafu);
                    result
                        .orders
                        .push(WriteOrder::EraseUnprotectedToAddress(parse_addr(&record[1..3])?));
                    record = &record[3..];
                }
                0x08 => {
                    ensure!(record.len() >= 2, UnexpectedEORSnafu);
                    result.orders.push(WriteOrder::GraphicEscape(record[2]));
                    record = &record[2..];
                }
                0x40..=0xFF => {
                    let len = record.iter().position(|&v| v < 0x40).unwrap_or(record.len());
                    let data = record[..len]
                        .iter()
                        .map(|&v| crate::encoding::cp037::DECODE_TBL[v as usize] as char)
                        .collect();
                    result.orders.push(WriteOrder::SendText(data));
                    record = &record[len..];
                }
                _ => return Err(StreamFormatError::InvalidData),
            }
        }
        Ok(result)
    }
}