Added support for parsing input streams
This commit is contained in:
@@ -36,17 +36,19 @@ fn run(mut session: tn3270::Session) -> anyhow::Result<()> {
|
|||||||
WriteOrder::SetBufferAddress(0),
|
WriteOrder::SetBufferAddress(0),
|
||||||
WriteOrder::EraseUnprotectedToAddress(bufsz.last_address()),
|
WriteOrder::EraseUnprotectedToAddress(bufsz.last_address()),
|
||||||
WriteOrder::SetBufferAddress(bufsz.encode_address(1, 31)),
|
WriteOrder::SetBufferAddress(bufsz.encode_address(1, 31)),
|
||||||
WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![
|
WriteOrder::StartFieldExtended(vec![
|
||||||
|
ExtendedFieldAttribute::FieldAttribute(FieldAttribute::PROTECTED),
|
||||||
// ExtendedFieldAttribute::ForegroundColor(Color::Red),
|
// ExtendedFieldAttribute::ForegroundColor(Color::Red),
|
||||||
]),
|
]),
|
||||||
WriteOrder::SendText("Hello from Rust!".into()),
|
WriteOrder::SendText("Hello from Rust!".into()),
|
||||||
WriteOrder::SetBufferAddress(bufsz.encode_address(8, 21)),
|
WriteOrder::SetBufferAddress(bufsz.encode_address(8, 21)),
|
||||||
WriteOrder::StartFieldExtended(FieldAttribute::INTENSE_SELECTOR_PEN_DETECTABLE, vec![]),
|
WriteOrder::StartField(FieldAttribute::INTENSE_SELECTOR_PEN_DETECTABLE),
|
||||||
WriteOrder::SendText(" ".into()),
|
WriteOrder::SendText(" ".into()),
|
||||||
WriteOrder::StartField(FieldAttribute::PROTECTED),
|
WriteOrder::StartField(FieldAttribute::PROTECTED),
|
||||||
WriteOrder::SetBufferAddress(bufsz.encode_address(8, 10)),
|
WriteOrder::SetBufferAddress(bufsz.encode_address(8, 10)),
|
||||||
WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![
|
WriteOrder::StartFieldExtended(vec![
|
||||||
ExtendedFieldAttribute::ForegroundColor(Color::Turquoise),
|
ExtendedFieldAttribute::FieldAttribute(FieldAttribute::PROTECTED),
|
||||||
|
// ExtendedFieldAttribute::ForegroundColor(Color::Turquoise),
|
||||||
]),
|
]),
|
||||||
WriteOrder::SendText("Name:".into()),
|
WriteOrder::SendText("Name:".into()),
|
||||||
],
|
],
|
||||||
@@ -54,7 +56,8 @@ fn run(mut session: tn3270::Session) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
for (i, line) in rust_logo.iter().enumerate() {
|
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::SetBufferAddress(bufsz.encode_address(3+i as u16, 31)));
|
||||||
record.orders.push(WriteOrder::StartFieldExtended(FieldAttribute::PROTECTED, vec![
|
record.orders.push(WriteOrder::StartFieldExtended(vec![
|
||||||
|
ExtendedFieldAttribute::FieldAttribute(FieldAttribute::PROTECTED),
|
||||||
ExtendedFieldAttribute::ForegroundColor(Color::Red),
|
ExtendedFieldAttribute::ForegroundColor(Color::Red),
|
||||||
]));
|
]));
|
||||||
record.orders.push(WriteOrder::SendText((*line).into()));
|
record.orders.push(WriteOrder::SendText((*line).into()));
|
||||||
@@ -68,13 +71,25 @@ fn run(mut session: tn3270::Session) -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let record = session.receive_record(None)?;
|
let record = session.receive_record(None)?;
|
||||||
if let Some(record) = record {
|
if let Some(record) = record {
|
||||||
eprintln!("Incoming record: {:?}", hex::encode(record));
|
eprintln!("Incoming record: {:?}", hex::encode(&record));
|
||||||
|
eprintln!("Decoded: {:#?}", IncomingRecord::parse_record(record.as_slice()))
|
||||||
} else {
|
} else {
|
||||||
eprintln!("No record");
|
eprintln!("No record");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.send_record(&WriteCommand{
|
||||||
|
command: WriteCommandCode::Write,
|
||||||
|
wcc: WCC::RESET_MDT,
|
||||||
|
orders: vec![
|
||||||
|
WriteOrder::SetBufferAddress(bufsz.encode_address(8,21)),
|
||||||
|
WriteOrder::ModifyField(vec![
|
||||||
|
ExtendedFieldAttribute::FieldAttribute(FieldAttribute::PROTECTED),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
})?;
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_secs(5));
|
|
||||||
|
std::thread::sleep(Duration::from_secs(50));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::convert::TryFrom;
|
use std::convert::{TryFrom, TryInto};
|
||||||
use snafu::Snafu;
|
use snafu::{Snafu, ensure};
|
||||||
|
use hex::encode;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Snafu)]
|
#[derive(Clone, Debug, Snafu)]
|
||||||
pub enum StreamFormatError {
|
pub enum StreamFormatError {
|
||||||
#[snafu(display("Invalid AID: {:02x}", aid))]
|
#[snafu(display("Invalid AID: {:02x}", aid))]
|
||||||
InvalidAID { aid: u8, }
|
InvalidAID { aid: u8, },
|
||||||
|
#[snafu(display("Record ended early"))]
|
||||||
|
UnexpectedEOR,
|
||||||
|
#[snafu(display("Invalid data"))]
|
||||||
|
InvalidData,
|
||||||
}
|
}
|
||||||
|
|
||||||
const WCC_TRANS: [u8; 64] = [
|
const WCC_TRANS: [u8; 64] = [
|
||||||
@@ -35,12 +41,15 @@ bitflags! {
|
|||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct FieldAttribute: u8 {
|
pub struct FieldAttribute: u8 {
|
||||||
|
const HI_1 = 0x40;
|
||||||
|
const HI_2 = 0x80;
|
||||||
const PROTECTED = 0x20;
|
const PROTECTED = 0x20;
|
||||||
const NUMERIC = 0x10;
|
const NUMERIC = 0x10;
|
||||||
const NON_DISPLAY = 0x0C;
|
const NON_DISPLAY = 0x0C;
|
||||||
const DISPLAY_SELECTOR_PEN_DETECTABLE = 0x04;
|
const DISPLAY_SELECTOR_PEN_DETECTABLE = 0x04;
|
||||||
const INTENSE_SELECTOR_PEN_DETECTABLE = 0x08;
|
const INTENSE_SELECTOR_PEN_DETECTABLE = 0x08;
|
||||||
const MODIFIED = 0x01;
|
const MODIFIED = 0x01;
|
||||||
|
const NONE = 0x00;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,24 +147,60 @@ impl Into<u8> for Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Color {
|
||||||
|
type Error = StreamFormatError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
0x00 => Color::Default,
|
||||||
|
0xF0 => Color::NeutralBG,
|
||||||
|
0xF1 => Color::Blue,
|
||||||
|
0xF2 => Color::Red,
|
||||||
|
0xF3 => Color::Pink,
|
||||||
|
0xF4 => Color::Green,
|
||||||
|
0xF5 => Color::Turquoise,
|
||||||
|
0xF6 => Color::Yellow,
|
||||||
|
0xF7 => Color::NeutralFG,
|
||||||
|
0xF8 => Color::Black,
|
||||||
|
0xF9 => Color::DeepBlue,
|
||||||
|
0xFA => Color::Orange,
|
||||||
|
0xFB => Color::Purple,
|
||||||
|
0xFC => Color::PaleGreen,
|
||||||
|
0xFD => Color::PaleTurquoise,
|
||||||
|
0xFE => Color::Grey,
|
||||||
|
0xFF => Color::White,
|
||||||
|
_ => return Err(StreamFormatError::InvalidData),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash)]
|
#[derive(Copy, Clone, Debug, Hash)]
|
||||||
pub enum Highlighting {
|
pub enum Highlighting {
|
||||||
Default,
|
Default = 0x00,
|
||||||
Normal,
|
Normal = 0xF0,
|
||||||
Blink,
|
Blink = 0xF1,
|
||||||
Reverse,
|
Reverse = 0xF2,
|
||||||
Underscore,
|
Underscore = 0xF4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Highlighting {
|
||||||
|
type Error = StreamFormatError;
|
||||||
|
|
||||||
|
fn try_from(v: u8) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match v {
|
||||||
|
0x00 => Highlighting::Default,
|
||||||
|
0xF0 => Highlighting::Normal,
|
||||||
|
0xF1 => Highlighting::Blink,
|
||||||
|
0xF2 => Highlighting::Reverse,
|
||||||
|
0xF4 => Highlighting::Underscore,
|
||||||
|
_ => return Err(StreamFormatError::InvalidData)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u8> for Highlighting {
|
impl Into<u8> for Highlighting {
|
||||||
fn into(self) -> u8 {
|
fn into(self) -> u8 {
|
||||||
match self {
|
self as u8
|
||||||
Highlighting::Default => 0x00,
|
|
||||||
Highlighting::Normal => 0xF0,
|
|
||||||
Highlighting::Blink => 0xF1,
|
|
||||||
Highlighting::Reverse => 0xF2,
|
|
||||||
Highlighting::Underscore => 0xF4,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,9 +222,9 @@ pub enum Transparency {
|
|||||||
Opaque,
|
Opaque,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u8> for Transparency {
|
impl From<Transparency> for u8 {
|
||||||
fn into(self) -> u8 {
|
fn from(v: Transparency) -> u8 {
|
||||||
match self {
|
match v {
|
||||||
Transparency::Default => 0x00,
|
Transparency::Default => 0x00,
|
||||||
Transparency::Or => 0xF0,
|
Transparency::Or => 0xF0,
|
||||||
Transparency::Xor => 0xF1,
|
Transparency::Xor => 0xF1,
|
||||||
@@ -188,6 +233,20 @@ impl Into<u8> for Transparency {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Transparency {
|
||||||
|
type Error = StreamFormatError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
0x00 => Transparency::Default,
|
||||||
|
0xF0 => Transparency::Or,
|
||||||
|
0xF1 => Transparency::Xor,
|
||||||
|
0xF2 => Transparency::Opaque,
|
||||||
|
_ => return Err(StreamFormatError::InvalidData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct FieldValidation: u8 {
|
pub struct FieldValidation: u8 {
|
||||||
const MANDATORY_FILL = 0b100;
|
const MANDATORY_FILL = 0b100;
|
||||||
@@ -207,7 +266,27 @@ pub enum ExtendedFieldAttribute {
|
|||||||
FieldAttribute(FieldAttribute),
|
FieldAttribute(FieldAttribute),
|
||||||
FieldValidation(FieldValidation),
|
FieldValidation(FieldValidation),
|
||||||
FieldOutlining(FieldOutline),
|
FieldOutlining(FieldOutline),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&[u8]> for ExtendedFieldAttribute {
|
||||||
|
type Error = StreamFormatError;
|
||||||
|
|
||||||
|
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
ensure!(value.len() == 2, UnexpectedEOR);
|
||||||
|
Ok(match (value[0], value[1]) {
|
||||||
|
(0x00, 0x00) => ExtendedFieldAttribute::AllAttributes,
|
||||||
|
(0x00, _) => return Err(StreamFormatError::InvalidData),
|
||||||
|
(0xC0, fa) => ExtendedFieldAttribute::FieldAttribute(FieldAttribute::from_bits(fa & 0x3F).ok_or(StreamFormatError::InvalidData)?),
|
||||||
|
(0x41, v) => ExtendedFieldAttribute::ExtendedHighlighting(v.try_into()?),
|
||||||
|
(0x45, v) => ExtendedFieldAttribute::BackgroundColor(v.try_into()?),
|
||||||
|
(0x42, v) => ExtendedFieldAttribute::ForegroundColor(v.try_into()?),
|
||||||
|
(0x43, v) => ExtendedFieldAttribute::CharacterSet(v),
|
||||||
|
(0xC2, v) => ExtendedFieldAttribute::FieldOutlining(FieldOutline::from_bits(v).ok_or(StreamFormatError::InvalidData)?),
|
||||||
|
(0x46, v) => ExtendedFieldAttribute::Transparency(v.try_into()?),
|
||||||
|
(0xC1, v) => ExtendedFieldAttribute::FieldValidation(FieldValidation::from_bits(v).ok_or(StreamFormatError::InvalidData)?),
|
||||||
|
_ => return Err(StreamFormatError::InvalidData),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtendedFieldAttribute {
|
impl ExtendedFieldAttribute {
|
||||||
@@ -257,7 +336,8 @@ impl BufferAddressCalculator {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum WriteOrder {
|
pub enum WriteOrder {
|
||||||
StartField(FieldAttribute),
|
StartField(FieldAttribute),
|
||||||
StartFieldExtended(FieldAttribute, Vec<ExtendedFieldAttribute>),
|
/// The list of attributes MUST include a FieldAttribute
|
||||||
|
StartFieldExtended(Vec<ExtendedFieldAttribute>),
|
||||||
SetBufferAddress(u16),
|
SetBufferAddress(u16),
|
||||||
SetAttribute(ExtendedFieldAttribute),
|
SetAttribute(ExtendedFieldAttribute),
|
||||||
ModifyField(Vec<ExtendedFieldAttribute>),
|
ModifyField(Vec<ExtendedFieldAttribute>),
|
||||||
@@ -274,10 +354,9 @@ impl WriteOrder {
|
|||||||
pub fn serialize(&self, output: &mut Vec<u8>) {
|
pub fn serialize(&self, output: &mut Vec<u8>) {
|
||||||
match self {
|
match self {
|
||||||
WriteOrder::StartField(attr) => output.extend_from_slice(&[0x1D, attr.bits()]),
|
WriteOrder::StartField(attr) => output.extend_from_slice(&[0x1D, attr.bits()]),
|
||||||
WriteOrder::StartFieldExtended(attr, rest) => {
|
WriteOrder::StartFieldExtended(attrs) => {
|
||||||
output.extend_from_slice(&[0x29, rest.len() as u8 + 1]);
|
output.extend_from_slice(&[0x29, attrs.len() as u8]);
|
||||||
ExtendedFieldAttribute::FieldAttribute(*attr).encode_into(&mut* output);
|
for attr in attrs {
|
||||||
for attr in rest {
|
|
||||||
attr.encode_into(&mut *output);
|
attr.encode_into(&mut *output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,6 +408,7 @@ impl Into<Vec<u8>> for &WriteCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
|
||||||
pub enum AID {
|
pub enum AID {
|
||||||
NoAIDGenerated,
|
NoAIDGenerated,
|
||||||
NoAIDGeneratedPrinter,
|
NoAIDGeneratedPrinter,
|
||||||
@@ -467,4 +547,135 @@ impl TryFrom<u8> for AID {
|
|||||||
_ => return Err(StreamFormatError::InvalidAID { aid }),
|
_ => return Err(StreamFormatError::InvalidAID { aid }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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])?;
|
||||||
|
// TODO: Handle AID 88 structured fields
|
||||||
|
let addr = parse_addr(&record[1..3])?;
|
||||||
|
|
||||||
|
let mut result = Self {
|
||||||
|
aid,
|
||||||
|
addr,
|
||||||
|
orders: vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
record = &record[3..];
|
||||||
|
|
||||||
|
while record.len() > 0 {
|
||||||
|
match record[0] {
|
||||||
|
0x1D => {
|
||||||
|
ensure!(record.len() >= 2, UnexpectedEOR);
|
||||||
|
result.orders.push(
|
||||||
|
WriteOrder::StartField(FieldAttribute::from_bits(record[1] & 0x3F)
|
||||||
|
.ok_or(StreamFormatError::InvalidData)?));
|
||||||
|
record = &record[2..];
|
||||||
|
|
||||||
|
},
|
||||||
|
0x29 => {
|
||||||
|
ensure!(record.len() >= 2, UnexpectedEOR);
|
||||||
|
let (header, body) = record.split_at(2);
|
||||||
|
let count = header[1] as usize;
|
||||||
|
ensure!(body.len() >= count * 2, UnexpectedEOR);
|
||||||
|
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, UnexpectedEOR);
|
||||||
|
result.orders.push(WriteOrder::SetBufferAddress(parse_addr(&record[1..3])?));
|
||||||
|
record = &record[3..];
|
||||||
|
}
|
||||||
|
0x28 => {
|
||||||
|
ensure!(record.len() >= 3, UnexpectedEOR);
|
||||||
|
result.orders.push(WriteOrder::SetAttribute(ExtendedFieldAttribute::try_from(&record[1..3])?));
|
||||||
|
record = &record[3..];
|
||||||
|
}
|
||||||
|
0x2C => {
|
||||||
|
ensure!(record.len() >= 2, UnexpectedEOR);
|
||||||
|
let (header, body) = record.split_at(2);
|
||||||
|
let count = header[1] as usize;
|
||||||
|
ensure!(body.len() >= count * 2, UnexpectedEOR);
|
||||||
|
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, UnexpectedEOR);
|
||||||
|
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, UnexpectedEOR);
|
||||||
|
// TODO: Handle graphic escape properly
|
||||||
|
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, UnexpectedEOR);
|
||||||
|
result.orders.push(WriteOrder::EraseUnprotectedToAddress(parse_addr(&record[1..3])?));
|
||||||
|
record = &record[3..];
|
||||||
|
}
|
||||||
|
0x08 => {
|
||||||
|
ensure!(record.len() >= 2, UnexpectedEOR);
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user