use crate::address::{
KvDeviceAddress, bit_bank_logical_number, is_direct_bit_device_type,
is_optimizable_read_named_device_type, offset_device, parse_device, parse_logical_address,
parse_named_address_parts, resolve_effective_format, uses_bit_bank_address,
validate_device_count, validate_device_span,
};
use crate::client::{HostLinkClient, HostLinkPayloadValue};
use crate::error::HostLinkError;
use futures_core::Stream;
use indexmap::IndexMap;
use std::str::FromStr;
use std::time::Duration;
#[derive(Debug, Clone, PartialEq)]
pub enum HostLinkValue {
U16(u16),
I16(i16),
U32(u32),
I32(i32),
F32(f32),
Bool(bool),
Text(String),
}
pub type NamedSnapshot = IndexMap<String, HostLinkValue>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimerCounterValue {
pub status: u32,
pub current: u32,
pub preset: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ReadPlanValueKind {
Unsigned16,
Signed16,
Unsigned32,
Signed32,
Float32,
BitInWord,
DirectBit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ReadPlanSegmentMode {
Words,
DirectBits,
}
#[derive(Debug, Clone)]
struct ReadPlanRequest {
index: usize,
address: String,
base_address: KvDeviceAddress,
kind: ReadPlanValueKind,
bit_index: u8,
}
#[derive(Debug, Clone)]
struct ReadPlanSegment {
start_address: KvDeviceAddress,
start_number: u32,
count: usize,
mode: ReadPlanSegmentMode,
requests: Vec<ReadPlanRequest>,
}
#[derive(Debug, Clone)]
pub(crate) struct CompiledReadNamedPlan {
requests_in_input_order: Vec<ReadPlanRequest>,
segments: Vec<ReadPlanSegment>,
}
impl From<HostLinkValue> for u16 {
fn from(value: HostLinkValue) -> Self {
match value {
HostLinkValue::U16(value) => value,
_ => 0,
}
}
}
impl HostLinkPayloadValue for HostLinkValue {
fn format_for_suffix(&self, data_format: &str) -> String {
let mut value = String::new();
self.append_to_payload(data_format, &mut value);
value
}
fn append_to_payload(&self, data_format: &str, output: &mut String) {
match self {
HostLinkValue::U16(value) => value.append_to_payload(data_format, output),
HostLinkValue::I16(value) => value.append_to_payload(data_format, output),
HostLinkValue::U32(value) => value.append_to_payload(data_format, output),
HostLinkValue::I32(value) => value.append_to_payload(data_format, output),
HostLinkValue::F32(value) => value.append_to_payload(data_format, output),
HostLinkValue::Bool(value) => value.append_to_payload(data_format, output),
HostLinkValue::Text(value) => value.append_to_payload(data_format, output),
}
}
}
pub async fn read_comments(
client: &HostLinkClient,
device: &str,
strip_padding: bool,
) -> Result<String, HostLinkError> {
client.read_comments(device, strip_padding).await
}
pub async fn read_typed(
client: &HostLinkClient,
device: &str,
dtype: &str,
) -> Result<HostLinkValue, HostLinkError> {
let (device, dtype) = if dtype.trim().is_empty() {
let logical = parse_logical_address(device)?;
(logical.base_address.to_text()?, logical.data_type)
} else {
(
device.trim().to_ascii_uppercase(),
dtype.trim_start_matches('.').to_ascii_uppercase(),
)
};
match dtype.as_str() {
"F" => {
let words = read_words(client, &device, 2).await?;
let bits = (words[0] as u32) | ((words[1] as u32) << 16);
Ok(HostLinkValue::F32(f32::from_bits(bits)))
}
"S" => Ok(HostLinkValue::I16(
read_single_parsed(client, &device, Some("S"), "Invalid signed 16-bit response")
.await?,
)),
"D" => {
if is_timer_counter_composite_device(&device)? {
let response = read_single_response(client, &device, Some("D")).await?;
Ok(HostLinkValue::U32(parse_last_token(
&response,
"Invalid unsigned 32-bit response",
)?))
} else {
Ok(HostLinkValue::U32(
read_single_parsed::<u32>(
client,
&device,
Some("D"),
"Invalid unsigned 32-bit response",
)
.await?,
))
}
}
"L" => {
if is_timer_counter_composite_device(&device)? {
let response = read_single_response(client, &device, Some("L")).await?;
Ok(HostLinkValue::I32(parse_last_token(
&response,
"Invalid signed 32-bit response",
)?))
} else {
Ok(HostLinkValue::I32(
read_single_parsed(
client,
&device,
Some("L"),
"Invalid signed 32-bit response",
)
.await?,
))
}
}
"U" => Ok(HostLinkValue::U16(
read_single_parsed::<u16>(
client,
&device,
Some("U"),
"Invalid unsigned 16-bit response",
)
.await?,
)),
"" => Ok(HostLinkValue::Bool(
read_single_bool(client, &device, None).await?,
)),
other => Err(HostLinkError::protocol(format!(
"Unsupported logical data type '{other}'."
))),
}
}
pub async fn read_timer_counter(
client: &HostLinkClient,
device: &str,
) -> Result<TimerCounterValue, HostLinkError> {
let mut address = parse_device(device)?;
if !matches!(address.device_type.as_str(), "T" | "C") {
return Err(HostLinkError::protocol(
"read_timer_counter requires a T or C device.",
));
}
address.suffix.clear();
let target = address.to_text()?;
let response = read_single_response(client, &target, Some("D")).await?;
let values = parse_all_tokens::<u32>(
&response,
"Invalid timer/counter status/current/preset response",
)?;
if values.len() < 3 {
return Err(HostLinkError::protocol(
"Timer/counter response did not contain status/current/preset.",
));
}
Ok(TimerCounterValue {
status: values[0],
current: values[1],
preset: values[2],
})
}
pub async fn read_timer(
client: &HostLinkClient,
device: &str,
) -> Result<TimerCounterValue, HostLinkError> {
if parse_device(device)?.device_type != "T" {
return Err(HostLinkError::protocol("read_timer requires a T device."));
}
read_timer_counter(client, device).await
}
pub async fn read_counter(
client: &HostLinkClient,
device: &str,
) -> Result<TimerCounterValue, HostLinkError> {
if parse_device(device)?.device_type != "C" {
return Err(HostLinkError::protocol("read_counter requires a C device."));
}
read_timer_counter(client, device).await
}
pub async fn write_typed<T: HostLinkPayloadValue>(
client: &HostLinkClient,
device: &str,
dtype: &str,
value: &T,
) -> Result<(), HostLinkError> {
match dtype.trim_start_matches('.').to_ascii_uppercase().as_str() {
"F" => {
let single = value
.format_for_suffix("")
.parse::<f32>()
.map_err(|_| HostLinkError::protocol("Invalid float32 input"))?;
let bits = single.to_bits();
let words = [(bits & 0xFFFF) as u16, (bits >> 16) as u16];
client.write_consecutive(device, &words, Some("U")).await
}
"" => client.write(device, value, None).await,
"S" | "D" | "L" | "U" => client.write(device, value, Some(dtype)).await,
other => Err(HostLinkError::protocol(format!(
"Unsupported logical data type '{other}'."
))),
}
}
fn parse_bool_token(token: &str) -> Result<bool, HostLinkError> {
let token = token.trim();
if token == "1" || token.eq_ignore_ascii_case("ON") || token.eq_ignore_ascii_case("TRUE") {
Ok(true)
} else if token == "0"
|| token.eq_ignore_ascii_case("OFF")
|| token.eq_ignore_ascii_case("FALSE")
{
Ok(false)
} else {
Err(HostLinkError::protocol(format!(
"Invalid direct bit response token: {token}"
)))
}
}
fn response_tokens(response_text: &str) -> impl Iterator<Item = &str> {
response_text
.split([' ', ','])
.filter(|token| !token.is_empty())
}
fn first_response_token(response_text: &str) -> Result<&str, HostLinkError> {
response_tokens(response_text)
.next()
.ok_or_else(|| HostLinkError::protocol("Missing response token"))
}
fn last_response_token(response_text: &str) -> Result<&str, HostLinkError> {
response_tokens(response_text)
.last()
.ok_or_else(|| HostLinkError::protocol("Missing response token"))
}
fn is_timer_counter_composite_device(device: &str) -> Result<bool, HostLinkError> {
let address = parse_device(device)?;
Ok(matches!(address.device_type.as_str(), "T" | "C"))
}
fn parse_first_token<T: FromStr>(
response_text: &str,
invalid_message: &'static str,
) -> Result<T, HostLinkError> {
first_response_token(response_text)?
.parse::<T>()
.map_err(|_| HostLinkError::protocol(invalid_message))
}
fn parse_last_token<T: FromStr>(
response_text: &str,
invalid_message: &'static str,
) -> Result<T, HostLinkError> {
last_response_token(response_text)?
.parse::<T>()
.map_err(|_| HostLinkError::protocol(invalid_message))
}
fn parse_all_tokens<T: FromStr>(
response_text: &str,
invalid_message: &'static str,
) -> Result<Vec<T>, HostLinkError> {
let mut values = Vec::new();
for token in response_tokens(response_text) {
values.push(
token
.parse::<T>()
.map_err(|_| HostLinkError::protocol(invalid_message))?,
);
}
if values.is_empty() {
return Err(HostLinkError::protocol("Missing response token"));
}
Ok(values)
}
fn prepare_read_address(
device: &str,
data_format: Option<&str>,
count: usize,
) -> Result<KvDeviceAddress, HostLinkError> {
let mut address = parse_device(device)?;
let suffix = if let Some(data_format) = data_format {
crate::address::normalize_suffix(data_format)?
} else {
address.suffix.clone()
};
let suffix = resolve_effective_format(&address.device_type, &suffix);
if count > 1 {
validate_device_count(&address.device_type, &suffix, count)?;
}
validate_device_span(&address.device_type, address.number, &suffix, count)?;
address.suffix = suffix;
Ok(address)
}
async fn read_single_response(
client: &HostLinkClient,
device: &str,
data_format: Option<&str>,
) -> Result<String, HostLinkError> {
let address = prepare_read_address(device, data_format, 1)?;
client.send_raw(&format!("RD {}", address.to_text()?)).await
}
async fn read_single_parsed<T: FromStr>(
client: &HostLinkClient,
device: &str,
data_format: Option<&str>,
invalid_message: &'static str,
) -> Result<T, HostLinkError> {
let response = read_single_response(client, device, data_format).await?;
parse_first_token(&response, invalid_message)
}
async fn read_single_bool(
client: &HostLinkClient,
device: &str,
data_format: Option<&str>,
) -> Result<bool, HostLinkError> {
let response = read_single_response(client, device, data_format).await?;
parse_bool_token(first_response_token(&response)?)
}
async fn read_consecutive_parsed<T: FromStr>(
client: &HostLinkClient,
device: &str,
count: usize,
data_format: Option<&str>,
invalid_message: &'static str,
) -> Result<Vec<T>, HostLinkError> {
let address = prepare_read_address(device, data_format, count)?;
let response = client
.send_raw(&format!("RDS {} {}", address.to_text()?, count))
.await?;
parse_all_tokens(&response, invalid_message)
}
pub async fn write_bit_in_word(
client: &HostLinkClient,
device: &str,
bit_index: u8,
value: bool,
) -> Result<(), HostLinkError> {
if bit_index > 15 {
return Err(HostLinkError::protocol("bitIndex must be 0-15."));
}
let mut current = read_single_parsed::<u16>(
client,
device,
Some("U"),
"Invalid unsigned 16-bit response",
)
.await?;
if value {
current |= 1 << bit_index;
} else {
current &= !(1 << bit_index);
}
client.write(device, current, Some("U")).await
}
pub async fn read_named<S: AsRef<str>>(
client: &HostLinkClient,
addresses: &[S],
) -> Result<NamedSnapshot, HostLinkError> {
let addr_list = addresses
.iter()
.map(|item| item.as_ref().to_owned())
.collect::<Vec<_>>();
if addr_list.is_empty() {
return Ok(NamedSnapshot::new());
}
if let Some(plan) = compile_read_named_plan(&addr_list) {
execute_read_named_plan(client, &plan).await
} else {
read_named_sequential(client, &addr_list).await
}
}
pub(crate) async fn read_named_sequential(
client: &HostLinkClient,
addresses: &[String],
) -> Result<NamedSnapshot, HostLinkError> {
let mut result = NamedSnapshot::new();
for address in addresses {
let (base_address, dtype, bit_index) = parse_named_address_parts(address)?;
if dtype == "BIT_IN_WORD" {
let word = read_single_parsed::<u16>(
client,
&base_address,
Some("U"),
"Invalid unsigned 16-bit response",
)
.await?;
let bit_index = bit_index.unwrap_or(0);
result.insert(
address.clone(),
HostLinkValue::Bool(((word >> bit_index) & 1) != 0),
);
} else if dtype == "COMMENT" {
result.insert(
address.clone(),
HostLinkValue::Text(read_comments(client, &base_address, true).await?),
);
} else {
result.insert(
address.clone(),
read_typed(client, &base_address, &dtype).await?,
);
}
}
Ok(result)
}
pub(crate) fn compile_read_named_plan(addresses: &[String]) -> Option<CompiledReadNamedPlan> {
let mut requests_in_input_order = Vec::new();
let mut requests_by_device_type: IndexMap<String, Vec<ReadPlanRequest>> = IndexMap::new();
for (index, address) in addresses.iter().enumerate() {
let request = try_parse_optimizable_read_named_request(address, index)?;
requests_by_device_type
.entry(request.base_address.device_type.clone())
.or_default()
.push(request.clone());
requests_in_input_order.push(request);
}
let mut segments = Vec::new();
for bucket in requests_by_device_type.values() {
let mut sorted = bucket.clone();
sorted.sort_by_key(|request| {
(
read_plan_number(request),
usize::MAX - get_word_width(request.kind),
)
});
let mut pending = Vec::new();
let mut current_start: Option<KvDeviceAddress> = None;
let mut current_start_number = 0u32;
let mut current_end_exclusive = 0u32;
let mut current_mode: Option<ReadPlanSegmentMode> = None;
for request in sorted {
let request_start = read_plan_number(&request);
let request_end_exclusive = request_start + get_word_width(request.kind) as u32;
let request_mode = segment_mode_for_kind(request.kind);
if current_start.is_none()
|| request_start > current_end_exclusive
|| current_mode != Some(request_mode)
{
if let Some(start_address) = current_start.take() {
segments.push(ReadPlanSegment {
start_address,
start_number: current_start_number,
count: (current_end_exclusive - current_start_number) as usize,
mode: current_mode.unwrap_or(ReadPlanSegmentMode::Words),
requests: pending.clone(),
});
pending.clear();
}
current_start = Some(KvDeviceAddress {
device_type: request.base_address.device_type.clone(),
number: request.base_address.number,
suffix: String::new(),
});
current_start_number = request_start;
current_end_exclusive = request_end_exclusive;
current_mode = Some(request_mode);
} else if request_end_exclusive > current_end_exclusive {
current_end_exclusive = request_end_exclusive;
}
pending.push(request);
}
if let Some(start_address) = current_start {
segments.push(ReadPlanSegment {
start_address,
start_number: current_start_number,
count: (current_end_exclusive - current_start_number) as usize,
mode: current_mode.unwrap_or(ReadPlanSegmentMode::Words),
requests: pending,
});
}
}
Some(CompiledReadNamedPlan {
requests_in_input_order,
segments,
})
}
pub(crate) async fn execute_read_named_plan(
client: &HostLinkClient,
plan: &CompiledReadNamedPlan,
) -> Result<NamedSnapshot, HostLinkError> {
let mut resolved = vec![HostLinkValue::U16(0); plan.requests_in_input_order.len()];
for segment in &plan.segments {
match segment.mode {
ReadPlanSegmentMode::Words => {
let words =
read_words(client, &segment.start_address.to_text()?, segment.count).await?;
for request in &segment.requests {
let offset = (read_plan_number(request) - segment.start_number) as usize;
resolved[request.index] =
resolve_planned_value(&words, offset, request.kind, request.bit_index)?;
}
}
ReadPlanSegmentMode::DirectBits => {
let tokens = client
.read_consecutive(&segment.start_address.to_text()?, segment.count, None)
.await?;
for request in &segment.requests {
let offset = (read_plan_number(request) - segment.start_number) as usize;
resolved[request.index] = resolve_direct_bit_value(&tokens, offset)?;
}
}
}
}
let mut result = NamedSnapshot::new();
for request in &plan.requests_in_input_order {
result.insert(request.address.clone(), resolved[request.index].clone());
}
Ok(result)
}
pub fn poll<'a, S: AsRef<str> + 'a>(
client: &'a HostLinkClient,
addresses: &'a [S],
interval: Duration,
) -> impl Stream<Item = Result<NamedSnapshot, HostLinkError>> + 'a {
async_stream::try_stream! {
let addr_list = addresses.iter().map(|item| item.as_ref().to_owned()).collect::<Vec<_>>();
let compiled = compile_read_named_plan(&addr_list);
loop {
let snapshot = if let Some(plan) = &compiled {
execute_read_named_plan(client, plan).await?
} else {
read_named_sequential(client, &addr_list).await?
};
yield snapshot;
tokio::time::sleep(interval).await;
}
}
}
pub async fn read_words(
client: &HostLinkClient,
device: &str,
count: usize,
) -> Result<Vec<u16>, HostLinkError> {
read_words_single_request(client, device, count).await
}
pub async fn read_dwords(
client: &HostLinkClient,
device: &str,
count: usize,
) -> Result<Vec<u32>, HostLinkError> {
read_dwords_single_request(client, device, count).await
}
pub async fn read_words_single_request(
client: &HostLinkClient,
device: &str,
count: usize,
) -> Result<Vec<u16>, HostLinkError> {
if count == 0 {
return Err(HostLinkError::protocol("count must be 1 or greater."));
}
read_consecutive_parsed::<u16>(
client,
device,
count,
Some("U"),
"Invalid unsigned 16-bit response",
)
.await
}
pub async fn read_dwords_single_request(
client: &HostLinkClient,
device: &str,
count: usize,
) -> Result<Vec<u32>, HostLinkError> {
if count == 0 {
return Err(HostLinkError::protocol("count must be 1 or greater."));
}
let words = read_words_single_request(client, device, count * 2).await?;
let mut result = Vec::with_capacity(count);
for index in 0..count {
let lo = words[index * 2] as u32;
let hi = words[(index * 2) + 1] as u32;
result.push(lo | (hi << 16));
}
Ok(result)
}
pub async fn write_words_single_request(
client: &HostLinkClient,
device: &str,
values: &[u16],
) -> Result<(), HostLinkError> {
if values.is_empty() {
return Err(HostLinkError::protocol("values must not be empty"));
}
client.write_consecutive(device, values, Some("U")).await
}
pub async fn write_dwords_single_request(
client: &HostLinkClient,
device: &str,
values: &[u32],
) -> Result<(), HostLinkError> {
if values.is_empty() {
return Err(HostLinkError::protocol("values must not be empty"));
}
let mut words = Vec::with_capacity(values.len() * 2);
for value in values {
words.push((value & 0xFFFF) as u16);
words.push((value >> 16) as u16);
}
write_words_single_request(client, device, &words).await
}
pub async fn read_words_chunked(
client: &HostLinkClient,
device: &str,
count: usize,
max_words_per_request: usize,
) -> Result<Vec<u16>, HostLinkError> {
validate_chunk_arguments(count, max_words_per_request, "count", "maxWordsPerRequest")?;
let mut start = parse_device(device)?;
start.suffix.clear();
let mut result = vec![0u16; count];
let mut offset = 0usize;
while offset < count {
let chunk_count = max_words_per_request.min(count - offset);
let chunk_start = offset_device(&start, offset as u32)?;
let chunk = read_words_single_request(client, &chunk_start, chunk_count).await?;
result[offset..offset + chunk_count].copy_from_slice(&chunk);
offset += chunk_count;
}
Ok(result)
}
pub async fn read_dwords_chunked(
client: &HostLinkClient,
device: &str,
count: usize,
max_dwords_per_request: usize,
) -> Result<Vec<u32>, HostLinkError> {
validate_chunk_arguments(
count,
max_dwords_per_request,
"count",
"maxDwordsPerRequest",
)?;
let mut start = parse_device(device)?;
start.suffix.clear();
let mut result = vec![0u32; count];
let mut offset = 0usize;
while offset < count {
let chunk_count = max_dwords_per_request.min(count - offset);
let chunk_start = offset_device(&start, (offset * 2) as u32)?;
let chunk = read_dwords_single_request(client, &chunk_start, chunk_count).await?;
result[offset..offset + chunk_count].copy_from_slice(&chunk);
offset += chunk_count;
}
Ok(result)
}
pub async fn write_words_chunked(
client: &HostLinkClient,
device: &str,
values: &[u16],
max_words_per_request: usize,
) -> Result<(), HostLinkError> {
if values.is_empty() {
return Err(HostLinkError::protocol("values must not be empty"));
}
validate_chunk_size(max_words_per_request, "maxWordsPerRequest")?;
let mut start = parse_device(device)?;
start.suffix.clear();
let mut offset = 0usize;
while offset < values.len() {
let chunk_count = max_words_per_request.min(values.len() - offset);
let chunk_start = offset_device(&start, offset as u32)?;
write_words_single_request(client, &chunk_start, &values[offset..offset + chunk_count])
.await?;
offset += chunk_count;
}
Ok(())
}
pub async fn write_dwords_chunked(
client: &HostLinkClient,
device: &str,
values: &[u32],
max_dwords_per_request: usize,
) -> Result<(), HostLinkError> {
if values.is_empty() {
return Err(HostLinkError::protocol("values must not be empty"));
}
validate_chunk_size(max_dwords_per_request, "maxDwordsPerRequest")?;
let mut start = parse_device(device)?;
start.suffix.clear();
let mut offset = 0usize;
while offset < values.len() {
let chunk_count = max_dwords_per_request.min(values.len() - offset);
let chunk_start = offset_device(&start, (offset * 2) as u32)?;
write_dwords_single_request(client, &chunk_start, &values[offset..offset + chunk_count])
.await?;
offset += chunk_count;
}
Ok(())
}
fn try_parse_optimizable_read_named_request(
address: &str,
index: usize,
) -> Option<ReadPlanRequest> {
let (base_address, dtype, bit_index) = parse_named_address_parts(address).ok()?;
let mut base_address = parse_device(&base_address).ok()?;
if !is_optimizable_read_named_device_type(&base_address.device_type)
&& !is_direct_bit_device_type(&base_address.device_type)
{
return None;
}
base_address.suffix.clear();
let (kind, bit_index) =
if dtype.is_empty() && is_direct_bit_device_type(&base_address.device_type) {
(ReadPlanValueKind::DirectBit, 0)
} else if dtype == "BIT_IN_WORD" {
(ReadPlanValueKind::BitInWord, bit_index.unwrap_or(0))
} else {
(try_map_read_plan_value_kind(&dtype)?, 0)
};
Some(ReadPlanRequest {
index,
address: address.to_owned(),
base_address,
kind,
bit_index,
})
}
fn try_map_read_plan_value_kind(dtype: &str) -> Option<ReadPlanValueKind> {
match dtype.trim_start_matches('.').to_ascii_uppercase().as_str() {
"U" => Some(ReadPlanValueKind::Unsigned16),
"S" => Some(ReadPlanValueKind::Signed16),
"D" => Some(ReadPlanValueKind::Unsigned32),
"L" => Some(ReadPlanValueKind::Signed32),
"F" => Some(ReadPlanValueKind::Float32),
_ => None,
}
}
fn segment_mode_for_kind(kind: ReadPlanValueKind) -> ReadPlanSegmentMode {
when_direct_bit(
kind,
ReadPlanSegmentMode::DirectBits,
ReadPlanSegmentMode::Words,
)
}
fn when_direct_bit<T>(kind: ReadPlanValueKind, direct: T, other: T) -> T {
match kind {
ReadPlanValueKind::DirectBit => direct,
_ => other,
}
}
fn get_word_width(kind: ReadPlanValueKind) -> usize {
match kind {
ReadPlanValueKind::Unsigned32
| ReadPlanValueKind::Signed32
| ReadPlanValueKind::Float32 => 2,
_ => 1,
}
}
fn read_plan_number(request: &ReadPlanRequest) -> u32 {
if request.kind == ReadPlanValueKind::DirectBit
&& uses_bit_bank_address(&request.base_address.device_type)
{
bit_bank_logical_number(request.base_address.number)
} else {
request.base_address.number
}
}
fn resolve_planned_value(
words: &[u16],
offset: usize,
kind: ReadPlanValueKind,
bit_index: u8,
) -> Result<HostLinkValue, HostLinkError> {
let word = *words
.get(offset)
.ok_or_else(|| HostLinkError::protocol("Batched read response was too short"))?;
let next_word = || {
words
.get(offset + 1)
.copied()
.ok_or_else(|| HostLinkError::protocol("Batched read response was too short"))
};
Ok(match kind {
ReadPlanValueKind::Unsigned16 => HostLinkValue::U16(word),
ReadPlanValueKind::Signed16 => HostLinkValue::I16(word as i16),
ReadPlanValueKind::Unsigned32 => {
let hi = next_word()? as u32;
HostLinkValue::U32((word as u32) | (hi << 16))
}
ReadPlanValueKind::Signed32 => {
let hi = next_word()? as u32;
HostLinkValue::I32(((word as u32) | (hi << 16)) as i32)
}
ReadPlanValueKind::Float32 => {
let hi = next_word()? as u32;
HostLinkValue::F32(f32::from_bits((word as u32) | (hi << 16)))
}
ReadPlanValueKind::BitInWord => HostLinkValue::Bool(((word >> bit_index) & 1) != 0),
ReadPlanValueKind::DirectBit => {
return Err(HostLinkError::protocol(
"Direct bit values must be resolved from bit tokens.",
));
}
})
}
fn resolve_direct_bit_value(
tokens: &[String],
offset: usize,
) -> Result<HostLinkValue, HostLinkError> {
let token = tokens
.get(offset)
.ok_or_else(|| HostLinkError::protocol("Batched direct bit response was too short"))?;
Ok(HostLinkValue::Bool(parse_bool_token(token)?))
}
fn validate_chunk_arguments(
count: usize,
max_per_request: usize,
count_name: &str,
chunk_name: &str,
) -> Result<(), HostLinkError> {
if count == 0 {
return Err(HostLinkError::protocol(format!(
"{count_name} must be 1 or greater."
)));
}
validate_chunk_size(max_per_request, chunk_name)
}
fn validate_chunk_size(max_per_request: usize, param_name: &str) -> Result<(), HostLinkError> {
if max_per_request == 0 {
return Err(HostLinkError::protocol(format!(
"{param_name} must be 1 or greater."
)));
}
Ok(())
}