use indexmap::IndexMap;
use crate::reader::Reader;
use crate::types::Elementary;
use crate::value::py_bytes_repr;
use crate::{Error, Result, Value};
pub const SLC_CMD_CODE: u8 = 0x0F;
pub const SLC_FNC_READ: u8 = 0xA2;
pub const SLC_FNC_WRITE: u8 = 0xAB;
pub const SLC_REPLY_START: usize = 61;
pub const PCCC_STATUS_OFFSET: usize = 58;
pub const PCCC_PATH: [u8; 3] = [0x67, 0x24, 0x01];
pub const FILE_DIRECTORY_CHUNK: u8 = 0x50;
pub const CT_PRE: u16 = 1;
pub const CT_ACC: u16 = 2;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FileType {
O,
I,
S,
B,
T,
C,
R,
N,
F,
ST,
A,
L,
MG,
PD,
PLS,
}
const FILE_TYPES: [FileType; 15] = [
FileType::O,
FileType::I,
FileType::S,
FileType::B,
FileType::T,
FileType::C,
FileType::R,
FileType::N,
FileType::F,
FileType::ST,
FileType::A,
FileType::L,
FileType::MG,
FileType::PD,
FileType::PLS,
];
impl FileType {
pub fn from_name(name: &str) -> Option<Self> {
FILE_TYPES.into_iter().find(|t| t.name() == name)
}
pub fn from_code(code: u8) -> Option<Self> {
FILE_TYPES.into_iter().find(|t| t.code() == code)
}
pub fn name(self) -> &'static str {
match self {
FileType::O => "O",
FileType::I => "I",
FileType::S => "S",
FileType::B => "B",
FileType::T => "T",
FileType::C => "C",
FileType::R => "R",
FileType::N => "N",
FileType::F => "F",
FileType::ST => "ST",
FileType::A => "A",
FileType::L => "L",
FileType::MG => "MG",
FileType::PD => "PD",
FileType::PLS => "PLS",
}
}
pub fn code(self) -> u8 {
match self {
FileType::O => 0x82,
FileType::I => 0x83,
FileType::S => 0x84,
FileType::B => 0x85,
FileType::T => 0x86,
FileType::C => 0x87,
FileType::R => 0x88,
FileType::N => 0x89,
FileType::F => 0x8A,
FileType::ST => 0x8D,
FileType::A => 0x8E,
FileType::L => 0x91,
FileType::MG => 0x92,
FileType::PD => 0x93,
FileType::PLS => 0x94,
}
}
pub fn element_size(self) -> usize {
match self {
FileType::N | FileType::B | FileType::S | FileType::A | FileType::O | FileType::I => 2,
FileType::L | FileType::F => 4,
FileType::T | FileType::C | FileType::R => 6,
FileType::ST => 84,
FileType::MG => 50,
FileType::PD => 46,
FileType::PLS => 12,
}
}
}
pub fn ct_bit(name: &str) -> Option<u16> {
CT_NAMES
.iter()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|&(_, v)| v)
}
const CT_NAMES: [(&str, u16); 10] = [
("PRE", 1),
("ACC", 2),
("EN", 15),
("TT", 14),
("DN", 13),
("CU", 15),
("CD", 14),
("OV", 12),
("UN", 11),
("UA", 10),
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Address {
pub file_type: FileType,
pub file_number: u16,
pub element_number: u16,
pub pos_number: Option<u16>,
pub sub_element: Option<u16>,
pub bit: bool,
pub element_count: usize,
pub tag: String,
}
pub fn parse_tag(tag: &str) -> Option<Address> {
let chars: Vec<char> = tag.chars().collect();
let base = tag.find('{').map_or(tag, |i| &tag[..i]);
parse_ct(&chars, tag)
.or_else(|| parse_lfbn(&chars, base))
.or_else(|| parse_io(&chars, base))
.or_else(|| parse_st_a(&chars, base, FileType::ST))
.or_else(|| parse_st_a(&chars, base, FileType::A))
.or_else(|| parse_s(&chars, tag, base))
.or_else(|| parse_b(&chars, base))
}
struct Cur<'a> {
s: &'a [char],
i: usize,
}
impl Cur<'_> {
fn eat(&mut self, c: char) -> bool {
if self.s.get(self.i) == Some(&c) {
self.i += 1;
true
} else {
false
}
}
fn letter(&mut self, upper: char) -> bool {
let Some(&c) = self.s.get(self.i) else {
return false;
};
let matched = c.to_ascii_uppercase() == upper
|| (upper == 'S' && c == '\u{17f}')
|| (upper == 'I' && c == '\u{131}');
if matched {
self.i += 1;
}
matched
}
fn file_letter(&mut self, options: &[(char, FileType)]) -> Option<FileType> {
options
.iter()
.find(|(c, _)| self.letter(*c))
.map(|&(_, t)| t)
}
fn digit_run(&self) -> usize {
self.s[self.i..]
.iter()
.take_while(|c| c.is_ascii_digit())
.count()
}
fn digits(&mut self, max: usize) -> Option<u32> {
let n = self.digit_run();
if n == 0 || n > max {
return None;
}
let value = number(&self.s[self.i..self.i + n]);
self.i += n;
u32::try_from(value).ok()
}
fn count(&mut self, only_1_or_2: bool) -> Option<Option<usize>> {
if !self.eat('{') {
return Some(None);
}
let n = self.digit_run();
if n == 0 || (only_1_or_2 && (n != 1 || !matches!(self.s[self.i], '1' | '2'))) {
return None;
}
let value = number(&self.s[self.i..self.i + n]);
self.i += n;
self.eat('}').then_some(Some(value))
}
fn done(&self) -> bool {
self.i == self.s.len()
}
}
fn number(digits: &[char]) -> usize {
digits.iter().fold(0usize, |acc, c| {
acc.saturating_mul(10)
.saturating_add(c.to_digit(10).map_or(0, |d| d as usize))
})
}
fn word_address(
file_type: FileType,
file: u32,
element: u32,
count: Option<usize>,
tag: &str,
) -> Address {
Address {
file_type,
file_number: file as u16,
element_number: element as u16,
pos_number: None,
sub_element: None,
bit: false,
element_count: count.unwrap_or(1),
tag: tag.to_owned(),
}
}
fn parse_ct(s: &[char], tag: &str) -> Option<Address> {
let mut c = Cur { s, i: 0 };
let file_type = c.file_letter(&[('C', FileType::C), ('T', FileType::T)])?;
let file = c.digits(3)?;
if !c.eat(':') {
return None;
}
let run = c.digit_run();
let (element, sub) = (1..=run.min(3)).rev().find_map(|k| {
let sep = *s.get(c.i + k)?;
if sep == '\n' {
return None;
}
let rest: String = s[c.i + k + 1..].iter().collect();
let sub = ct_bit(&rest)?;
Some((number(&s[c.i..c.i + k]), sub))
})?;
if !(1..=255).contains(&file) || element > 255 {
return None;
}
Some(Address {
sub_element: Some(sub),
bit: true,
..word_address(file_type, file, element as u32, None, tag)
})
}
fn parse_lfbn(s: &[char], base: &str) -> Option<Address> {
let mut c = Cur { s, i: 0 };
let file_type = c.file_letter(&[
('L', FileType::L),
('F', FileType::F),
('B', FileType::B),
('N', FileType::N),
])?;
let file = c.digits(3)?;
if !c.eat(':') {
return None;
}
let element = c.digits(3)?;
let sub = if c.eat('/') { Some(c.digits(2)?) } else { None };
let count = c.count(false)?;
if !c.done() || !(1..=255).contains(&file) || element > 255 || sub.is_some_and(|b| b > 15) {
return None;
}
Some(Address {
sub_element: sub.map(|b| b as u16),
bit: sub.is_some(),
..word_address(file_type, file, element, count, base)
})
}
fn parse_io(s: &[char], base: &str) -> Option<Address> {
let mut c = Cur { s, i: 0 };
let file_type = c.file_letter(&[('I', FileType::I), ('O', FileType::O)])?;
if c.digit_run() > 0 {
c.digits(3)?;
}
if !c.eat(':') {
return None;
}
let element = c.digits(3)?;
let pos = if c.eat('.') { c.digits(3)? } else { 0 };
let sub = if c.eat('/') { Some(c.digits(2)?) } else { None };
let count = c.count(false)?;
if !c.done() || element > 255 || sub.is_some_and(|b| b > 15) {
return None;
}
let file = if file_type == FileType::O { 0 } else { 1 };
Some(Address {
pos_number: Some(pos as u16),
sub_element: Some(sub.unwrap_or(0) as u16),
bit: sub.is_some(),
..word_address(file_type, file, element, count, base)
})
}
fn parse_st_a(s: &[char], base: &str, file_type: FileType) -> Option<Address> {
let mut c = Cur { s, i: 0 };
let letters = if file_type == FileType::ST {
c.letter('S') && c.letter('T')
} else {
c.letter('A')
};
if !letters {
return None;
}
let file = c.digits(3)?;
if !c.eat(':') {
return None;
}
let element = c.digits(4)?;
let count = c.count(file_type == FileType::ST)?;
if !c.done() || !(1..=255).contains(&file) || element > 255 {
return None;
}
Some(word_address(file_type, file, element, count, base))
}
fn parse_s(s: &[char], tag: &str, base: &str) -> Option<Address> {
let mut c = Cur { s, i: 0 };
if !(c.letter('S') && c.eat(':')) {
return None;
}
let element = c.digits(3)?;
let sub = if c.eat('/') { Some(c.digits(2)?) } else { None };
let count = c.count(false)?;
if !c.done() || element > 255 || sub.is_some_and(|b| b > 15) {
return None;
}
let name = if sub.is_some() { tag } else { base };
Some(Address {
sub_element: sub.map(|b| b as u16),
bit: sub.is_some(),
..word_address(FileType::S, 2, element, count, name)
})
}
fn parse_b(s: &[char], base: &str) -> Option<Address> {
let mut c = Cur { s, i: 0 };
if !c.letter('B') {
return None;
}
let file = c.digits(3)?;
if !c.eat('/') {
return None;
}
let bit = c.digits(4)?;
let count = c.count(false)?;
if !c.done() || !(1..=255).contains(&file) || bit > 4095 {
return None;
}
Some(Address {
sub_element: Some((bit % 16) as u16),
bit: true,
..word_address(FileType::B, file, bit / 16, count, base)
})
}
pub fn get_bit(value: i64, idx: u32) -> bool {
(value >> idx.min(63)) & 1 != 0
}
pub fn slc_string_swap(data: &[u8]) -> Vec<u8> {
let mut out = data.to_vec();
if out.len() % 2 == 1 {
out.push(0);
}
for pair in out.chunks_exact_mut(2) {
pair.swap(0, 1);
}
out
}
fn truthy(value: &Value) -> bool {
match value {
Value::Bool(b) => *b,
Value::Real(x) => *x != 0.0,
Value::LReal(x) => *x != 0.0,
Value::String(s) => !s.is_empty(),
Value::Bytes(b) => !b.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Struct(m) => !m.is_empty(),
other => other.as_i128() != Some(0),
}
}
fn latin1_or_empty(value: &Value) -> Option<Vec<u8>> {
if !truthy(value) {
return Some(Vec::new());
}
let Value::String(text) = value else {
return None;
};
text.chars()
.map(|c| u8::try_from(u32::from(c)).ok())
.collect()
}
fn latin1_decode(bytes: &[u8]) -> String {
bytes.iter().map(|&b| char::from(b)).collect()
}
pub fn encode_pccc_ascii(value: &Value) -> Result<Vec<u8>> {
let mut data = latin1_or_empty(value).ok_or_else(|| packing_error(value, "PCCC_ASCII"))?;
data.truncate(2);
data.resize(2, b' ');
Ok(slc_string_swap(&data))
}
pub fn decode_pccc_ascii(reader: &mut Reader<'_>) -> String {
let n = reader.remaining().min(2);
let bytes = reader.take(n).unwrap_or_default();
latin1_decode(&slc_string_swap(bytes))
}
pub fn encode_pccc_string(value: &Value) -> Result<Vec<u8>> {
let mut data = latin1_or_empty(value).ok_or_else(|| packing_error(value, "PCCC_STRING"))?;
data.truncate(82);
let len = data.len() as u16; data.resize(82, 0);
let mut out = Vec::with_capacity(84);
out.extend(len.to_le_bytes());
out.extend(slc_string_swap(&data));
Ok(out)
}
pub fn decode_pccc_string(reader: &mut Reader<'_>) -> Result<String> {
let start = reader.position();
let len = match reader.u16() {
Ok(len) => usize::from(len),
Err(Error::BufferEmpty) => return Err(Error::BufferEmpty),
Err(_) => {
return Err(Error::Data(format!(
"Error unpacking {} as PCCC_STRING",
py_bytes_repr(&reader.buffer()[start..])
)));
}
};
let n = reader.remaining().min(82);
let raw = reader.take(n).unwrap_or_default();
let swapped = slc_string_swap(raw);
Ok(latin1_decode(&swapped[..len.min(swapped.len())]))
}
fn packing_error(value: &Value, type_name: &str) -> Error {
Error::Data(format!("Error packing {} as {type_name}", value.py_repr()))
}
fn encode_element(file_type: FileType, value: &Value) -> Result<Vec<u8>> {
match file_type {
FileType::N
| FileType::B
| FileType::T
| FileType::C
| FileType::S
| FileType::O
| FileType::I => Elementary::Int.encode(value),
FileType::F => Elementary::Real.encode(value),
FileType::R | FileType::L => Elementary::DInt.encode(value),
FileType::A => encode_pccc_ascii(value),
FileType::ST => encode_pccc_string(value),
FileType::MG | FileType::PD | FileType::PLS => Err(Error::data(format!(
"no data type for file type {}",
file_type.name()
))),
}
}
fn decode_element(file_type: FileType, data: &[u8]) -> Result<Value> {
let mut reader = Reader::new(data);
match file_type {
FileType::N
| FileType::B
| FileType::T
| FileType::C
| FileType::S
| FileType::O
| FileType::I => Elementary::Int.decode(&mut reader),
FileType::F => Elementary::Real.decode(&mut reader),
FileType::R | FileType::L => Elementary::DInt.decode(&mut reader),
FileType::A => Ok(Value::String(decode_pccc_ascii(&mut reader))),
FileType::ST => decode_pccc_string(&mut reader).map(Value::String),
FileType::MG | FileType::PD | FileType::PLS => Err(Error::data(format!(
"no data type for file type {}",
file_type.name()
))),
}
}
fn py_str(value: &Value) -> String {
match value {
Value::String(s) => s.clone(),
other => other.py_repr(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Requestor {
pub vid: [u8; 2],
pub vsn: [u8; 4],
}
impl Default for Requestor {
fn default() -> Self {
Requestor {
vid: [0x09, 0x10],
vsn: [0x09, 0x10, 0x19, 0x71],
}
}
}
pub fn msg_start(requestor: &Requestor) -> Vec<u8> {
let mut out = Vec::with_capacity(32);
out.extend([0x4B, 0x02, 0x20]);
out.extend(PCCC_PATH);
out.push(0x07);
out.extend(requestor.vid);
out.extend(requestor.vsn);
out
}
fn push_addr(out: &mut Vec<u8>, value: u16) {
if value >= 255 {
out.push(0xFF);
out.extend(value.to_le_bytes());
} else {
out.push(value as u8); }
}
fn usint(value: i128) -> Result<u8> {
u8::try_from(value).map_err(|_| Error::Data(format!("Error packing {value} as USINT")))
}
fn request_address(tag: &str, method: &str) -> Result<Address> {
match parse_tag(tag) {
Some(addr) if !(addr.bit && addr.element_count > 1) => Ok(addr),
_ => Err(Error::request(format!(
"Error parsing the tag passed to {method}() - {tag}"
))),
}
}
pub fn read_request(tag: &str, requestor: &Requestor, sequence: u16) -> Result<(Address, Vec<u8>)> {
let addr = request_address(tag, "read")?;
let size = (addr.file_type.element_size() as i128).saturating_mul(addr.element_count as i128);
let size = usint(size)?;
let mut msg = msg_start(requestor);
msg.extend([SLC_CMD_CODE, 0x00]);
msg.extend(sequence.to_le_bytes());
msg.extend([SLC_FNC_READ, size]);
push_addr(&mut msg, addr.file_number);
msg.push(addr.file_type.code());
push_addr(&mut msg, addr.element_number);
push_addr(&mut msg, addr.pos_number.unwrap_or(0));
Ok((addr, msg))
}
pub fn write_request(
tag: &str,
value: &Value,
requestor: &Requestor,
sequence: u16,
) -> Result<(Address, Vec<u8>)> {
let addr = request_address(tag, "write")?;
let data = writeable_value(&addr, value)?;
let mut sub_element = addr.pos_number.unwrap_or(0);
if matches!(addr.file_type, FileType::T | FileType::C)
&& matches!(addr.sub_element, Some(CT_PRE | CT_ACC))
{
sub_element = addr.sub_element.unwrap_or(0); }
let size = usint(data.len() as i128 - 2)?; let mut msg = msg_start(requestor);
msg.extend([SLC_CMD_CODE, 0x00]);
msg.extend(sequence.to_le_bytes());
msg.extend([SLC_FNC_WRITE, size]);
push_addr(&mut msg, addr.file_number);
msg.push(addr.file_type.code());
push_addr(&mut msg, addr.element_number);
push_addr(&mut msg, sub_element);
msg.extend(data);
Ok((addr, msg))
}
fn items(value: &Value) -> Result<Vec<Value>> {
match value {
Value::Array(items) => Ok(items.clone()),
Value::String(s) => Ok(s.chars().map(|c| Value::String(c.into())).collect()),
other => Err(Error::request(format!(
"object {} has no len()",
other.py_repr()
))),
}
}
pub fn writeable_value(addr: &Address, value: &Value) -> Result<Vec<u8>> {
if let Value::Bytes(bytes) = value {
return Ok(bytes.clone());
}
let bit_position = if addr.bit {
addr.sub_element.unwrap_or(0)
} else {
0
};
let mut mask = if addr.bit {
1u16.checked_shl(u32::from(bit_position)).unwrap_or(0)
} else {
0xFFFF
};
let count = addr.element_count.max(1);
let mut value = value.clone();
if count > 1 {
let mut list = items(&value)?;
if list.len() < count {
return Err(Error::request(format!(
"Insufficient data for requested elements, expected {count} and got {}",
list.len()
)));
}
list.truncate(count);
value = match value {
Value::String(_) => Value::String(
list.iter()
.filter_map(|v| match v {
Value::String(s) => Some(s.as_str()),
_ => None,
})
.collect(),
),
_ => Value::Array(list),
};
}
let encoded = (|| -> Result<Vec<u8>> {
if count > 1 {
let mut out = Vec::new();
for item in items(&value)? {
out.extend(encode_element(addr.file_type, &item)?);
}
Ok(out)
} else if addr.bit {
if matches!(addr.file_type, FileType::T | FileType::C)
&& matches!(bit_position, CT_PRE | CT_ACC)
{
mask = 0xFFFF;
encode_element(addr.file_type, &value)
} else if truthy(&value) {
Ok(mask.to_le_bytes().to_vec())
} else {
Ok(vec![0, 0])
}
} else {
encode_element(addr.file_type, &value)
}
})();
let data = encoded.map_err(|err| {
Error::request(format!(
"Failed to create a writeable value for {} from {} - {err}",
addr.tag,
py_str(&value)
))
})?;
let mut out = Vec::with_capacity(2 + data.len());
out.extend(mask.to_le_bytes());
out.extend(data);
Ok(out)
}
pub fn parse_read_reply(addr: &Address, data: &[u8]) -> Result<Value> {
parse_read_data(addr, data).map_err(|_| Error::response("Failed parsing tag read reply"))
}
fn parse_read_data(addr: &Address, data: &[u8]) -> Result<Value> {
let size = addr.file_type.element_size();
let slice = |from: usize| &data[from.min(data.len())..(from + size).min(data.len())];
if addr.bit {
let bit_position = addr.sub_element.unwrap_or(0);
if matches!(addr.file_type, FileType::T | FileType::C) {
match bit_position {
CT_PRE => return decode_element(addr.file_type, slice(2)),
CT_ACC => return decode_element(addr.file_type, slice(4)),
_ => {}
}
}
let word = decode_element(addr.file_type, slice(0))?;
let word = word
.as_i128()
.ok_or_else(|| Error::data("bit of a non-integer value"))?;
return Ok(Value::Bool(get_bit(word as i64, u32::from(bit_position))));
}
let mut values = data
.chunks(size)
.map(|chunk| decode_element(addr.file_type, chunk))
.collect::<Result<Vec<_>>>()?;
match values.len() {
0 => Err(Error::data("no data")),
1 => Ok(values.remove(0)),
_ => Ok(Value::Array(values)),
}
}
pub fn request_status(raw: &[u8]) -> Option<String> {
let Some(&status) = raw.get(PCCC_STATUS_OFFSET) else {
return Some("Unknown Status".into());
};
if status == 0 {
return None;
}
let mut msg = match pccc_error_text(status) {
Some(text) => text.to_owned(),
None => format!("Unknown Status (0x{status:02x})"),
};
if status == 0xF0
&& let Some(ext) = raw.get(SLC_REPLY_START)
{
msg.push_str(&format!(" (EXT STS 0x{ext:02x})"));
}
Some(msg)
}
pub fn pccc_error_text(status: u8) -> Option<&'static str> {
Some(match status {
0x10 => {
"Illegal Command or Format, Address may not exist or not enough elements in data file"
}
0x20 => "PLC Has a Problem and Will Not Communicate",
0x30 => "Remote Node Host is Missing, Disconnected, or Shut Down",
0x40 => "Host Could Not Complete Function Due To Hardware Fault",
0x50 => "Addressing problem or Memory Protect Rungs",
0x60 => "Function not allows due to command protection selection",
0x70 => "Processor is in Program mode",
0x80 => "Compatibility mode file missing or communication zone problem",
0x90 => "Remote node cannot buffer command",
0xF0 => "Error code in EXT STS Byte",
_ => return None,
})
}
pub fn processor_type_request(requestor: &Requestor, sequence: u16) -> Vec<u8> {
let mut msg = msg_start(requestor);
msg.extend([0x06, 0x00]);
msg.extend(sequence.to_le_bytes());
msg.push(0x03);
msg
}
pub fn parse_processor_type(raw: &[u8]) -> Option<String> {
let from = (SLC_REPLY_START + 5).min(raw.len());
let to = (SLC_REPLY_START + 16).min(raw.len());
let text: String = raw[from..to].utf8_chunks().map(|c| c.valid()).collect();
let text = text.trim_matches(|c: char| c.is_whitespace() || ('\x1c'..='\x1f').contains(&c));
(!text.is_empty()).then(|| text.to_owned())
}
pub fn datalog_request(queue: u8) -> Vec<u8> {
vec![
0x4B, 0x02, 0x20, 0x67, 0x24, 0x01, 0x07, 0x4D, 0x00, 0xA1, 0x4E, 0xC3, 0x30, 0x0F, 0x00,
0x30, 0x00, 0xA2, 0x6D, 0x00, 0xA5, queue, 0x00,
]
}
pub fn parse_datalog(raw: &[u8]) -> Value {
let entry = &raw[SLC_REPLY_START.min(raw.len())..];
match std::str::from_utf8(entry) {
Ok(text) => Value::String(text.to_owned()),
Err(_) => Value::Bytes(entry.to_vec()),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Sys0Info {
pub file_position: usize,
pub row_size: usize,
pub file_type: u8,
pub size_element: u8,
pub size_len: u8,
pub size_const: u16,
}
pub fn sys0_info(plc_type: &str) -> Sys0Info {
let prefix: String = plc_type.chars().take(4).collect();
let info = |file_position, row_size, file_type, size_element, size_len, size_const| Sys0Info {
file_position,
row_size,
file_type,
size_element,
size_len,
size_const,
};
match prefix.as_str() {
"1761" => info(93, 8, 0x00, 0x23, 0x04, 0),
"1763" | "1762" | "1764" => info(233, 10, 0x02, 0x28, 0x08, 19968),
"1766" => info(233, 10, 0x03, 0x2B, 0x08, 19968),
_ => info(79, 10, 0x01, 0x23, 0x04, 0),
}
}
pub fn file_directory_size_request(
info: &Sys0Info,
requestor: &Requestor,
sequence: u16,
) -> Vec<u8> {
let mut msg = msg_start(requestor);
msg.extend([SLC_CMD_CODE, 0x00]);
msg.extend(sequence.to_le_bytes());
msg.extend([0xA1, info.size_len, 0x00, info.file_type, info.size_element]);
msg
}
pub fn parse_file_directory_size(raw: &[u8], info: &Sys0Info) -> Option<i32> {
let data = raw.get(SLC_REPLY_START..)?;
let size = Reader::new(data).u16().ok()?;
Some(i32::from(size) - i32::from(info.size_const))
}
pub fn file_directory_read_request(
file_type: u8,
offset: usize,
size: u8,
requestor: &Requestor,
sequence: u16,
) -> Result<Vec<u8>> {
let offset = u16::try_from(offset)
.map_err(|_| Error::Data(format!("Error packing {offset} as UINT")))?;
let mut msg = msg_start(requestor);
msg.extend([SLC_CMD_CODE, 0x00]);
msg.extend(sequence.to_le_bytes());
msg.extend([0xA1, size, 0x00, file_type]);
push_addr(&mut msg, offset);
Ok(msg)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DataFile {
pub elements: usize,
pub length: u16,
}
pub fn parse_file_directory(
file_position: usize,
row_size: usize,
data: &[u8],
) -> Result<IndexMap<String, DataFile>> {
if row_size == 0 {
return Err(Error::data("file directory row size must not be zero"));
}
let mut files = IndexMap::new();
let mut file_num = 0usize;
let mut pos = file_position;
while pos < data.len() {
let code = data[pos];
let file_type = FileType::from_code(code);
if file_type == Some(FileType::I) && file_num == 0 {
file_num = 1; }
if let Some(file_type) = file_type {
let length = match Elementary::UInt.decode(&mut Reader::new(&data[pos + 1..]))? {
Value::UInt(n) => n,
_ => 0,
};
files.insert(
format!("{}{file_num}", file_type.name()),
DataFile {
elements: usize::from(length) / file_type.element_size(),
length,
},
);
}
if file_type.is_some() || code == 0x81 {
file_num += 1;
}
pos = pos.saturating_add(row_size);
}
Ok(files)
}