pub mod handlers;
pub mod iter;
mod options;
#[cfg(test)]
mod test;
#[cfg(any(test, doc, feature = "mock"))]
use crate::backend::Mock;
use crate::backend::{Backend, Serial, UNKNOWN_BACKEND_NAME};
#[allow(clippy::wildcard_imports)]
use crate::error::*;
#[cfg(unstable)]
use crate::{
ascii::chain::Chain,
routine::{IntoRoutine, Routine},
};
use crate::{
ascii::{
checksum::Lrc,
command::{Command, CommandWriter, MaxPacketSize, Target},
id,
packet::{Packet, PacketKind},
response::{
check::{self, NotChecked},
Alert, AnyResponse, Info, Reply, Response, ResponseBuilder, Status,
},
},
timeout_guard::TimeoutGuard,
};
use handlers::{Handlers, LocalHandlers, SendHandlers};
#[cfg(any(test, doc, feature = "mock"))]
pub use options::OpenMockOptions;
pub use options::{OpenGeneralOptions, OpenSerialOptions, OpenTcpOptions};
use std::{
convert::TryFrom,
io,
net::{TcpStream, ToSocketAddrs},
time::Duration,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Direction {
Tx,
Recv,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DefaultTag {}
pub type SendPort<'a, B, Tag = DefaultTag> = Port<'a, B, Tag, SendHandlers<'a>>;
pub struct Port<'a, B, Tag = DefaultTag, H = LocalHandlers<'a>> {
backend: B,
ids: id::Counter,
generate_id: bool,
generate_checksum: bool,
max_packet_size: MaxPacketSize,
poison: Option<io::Error>,
builder: ResponseBuilder,
handlers: H,
tag: std::marker::PhantomData<Tag>,
lifetime: std::marker::PhantomData<&'a ()>,
}
impl<B: Backend, Tag, H> std::fmt::Debug for Port<'_, B, Tag, H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Port")
.field("name", &self.backend.name())
.finish_non_exhaustive()
}
}
impl<'a> Port<'a, Serial> {
pub fn open_serial(path: &str) -> Result<Port<'a, Serial>, AsciiError> {
OpenSerialOptions::new().open(path)
}
pub fn open_serial_options() -> OpenSerialOptions {
OpenSerialOptions::default()
}
}
impl<'a> Port<'a, TcpStream> {
pub fn open_tcp<A: ToSocketAddrs>(address: A) -> Result<Port<'a, TcpStream>, io::Error> {
OpenTcpOptions::default().open(address)
}
pub fn open_tcp_options() -> OpenTcpOptions {
OpenTcpOptions::default()
}
}
impl<'a, B: Backend> Port<'a, B> {
pub fn open_general(backend: B) -> Port<'a, B> {
OpenGeneralOptions::default().open(backend)
}
}
impl Port<'_, ()> {
pub fn open_general_options() -> OpenGeneralOptions {
OpenGeneralOptions::default()
}
}
#[cfg(any(test, doc, feature = "mock"))]
#[cfg_attr(all(doc, feature = "doc_cfg"), doc(cfg(feature = "mock")))]
impl<'a> Port<'a, Mock> {
pub fn open_mock() -> Port<'a, Mock> {
OpenMockOptions::new().open()
}
pub fn open_mock_options() -> OpenMockOptions {
OpenMockOptions::default()
}
}
impl<'a, B, Tag, H> Port<'a, B, Tag, H>
where
B: Backend,
H: Handlers,
H::PacketHandler: FnMut(&[u8], Direction) + 'a,
H::UnexpectedAlertHandler: FnMut(Alert) -> Result<(), Alert> + 'a,
{
fn from_backend(
backend: B,
generate_id: bool,
generate_checksum: bool,
max_packet_size: MaxPacketSize,
) -> Self {
Port {
backend,
ids: id::Counter::default(),
generate_id,
generate_checksum,
max_packet_size,
poison: None,
builder: ResponseBuilder::default(),
handlers: H::default(),
tag: std::marker::PhantomData,
lifetime: std::marker::PhantomData,
}
}
fn check_poisoned(&mut self) -> Result<(), io::Error> {
if let Some(poison) = self.poison.take() {
Err(poison)
} else {
Ok(())
}
}
#[cfg_attr(
unstable,
doc = "
# Examples
When communicating with products over more than one port it can be easy
to pass the wrong port to the [`Routine`]s generated from a port's
[`Chain`]. In the best case, the command fails at run time, but in many
cases it simply results in incorrect data.
```
# use zproto::{ascii::Port, backend::Backend, error::AsciiError};
# fn wrapper<B: Backend>(mut port_a: Port<B>, mut port_b: Port<B>) -> Result<(), AsciiError> {
use zproto::ascii::setting::v_latest::SystemSerial;
let chain = port_a.chain()?;
for device in &chain {
let get_system_serial = device.settings().get(SystemSerial);
let _serial = port_b.run(get_system_serial)?;
// ^^^^^^ OOPS! This is the wrong port!
}
# Ok(())
# }
```
However, by tagging each port's type, we change the type of the port and
the types of the routines generated from it, turning the above runtime
error into a compiler error.
```compile_fail
# use zproto::{ascii::Port, backend::Backend, error::AsciiError};
# fn wrapper<B: Backend>(mut port_a: Port<B>, mut port_b: Port<B>) -> Result<(), AsciiError> {
use zproto::ascii::setting::v_latest::SystemSerial;
struct PortA;
let mut port_a = port_a.into_tagged::<PortA>();
let chain = port_a.chain()?; // Has a different type
for device in &chain {
let get_system_serial = device.settings().get(SystemSerial); // Has a different type
// Yay! This no longer compiles because the type of port_b is
// incompatible with the type of get_system_serial.
let _id = port_b.run(get_system_serial)?;
// ERROR: ^^^ the trait `Routine<Port<B>>` is not implemented for
// `Get<SystemSerial, PortA>`
}
# Ok(())
# }
```
Using tagged ports can also be useful for ensuring functions receive
appropriate types.
```
# use zproto::ascii::{Port, chain::Chain};
fn do_something<Backend, Tag>(port: &mut Port<Backend, Tag>, chain: &Chain<Tag>) {
// The chain is guaranteed to be associated with the port, assuming
// each type Tag is a only used to tag one port.
todo!()
}
```"
)]
pub fn into_tagged<T>(self) -> Port<'a, B, T, H> {
Port {
backend: self.backend,
ids: self.ids,
generate_id: self.generate_id,
generate_checksum: self.generate_checksum,
max_packet_size: self.max_packet_size,
poison: self.poison,
builder: self.builder,
handlers: self.handlers,
tag: std::marker::PhantomData,
lifetime: std::marker::PhantomData,
}
}
pub fn try_into_send(mut self) -> Result<SendPort<'a, B, Tag>, TryIntoSendError>
where
B: Send,
{
if self.handlers.packet().is_some() || self.handlers.unexpected_alert().is_some() {
return Err(TryIntoSendError::new());
}
Ok(Port {
backend: self.backend,
ids: self.ids,
generate_id: self.generate_id,
generate_checksum: self.generate_checksum,
max_packet_size: self.max_packet_size,
poison: self.poison,
builder: self.builder,
handlers: SendHandlers::default(),
tag: std::marker::PhantomData,
lifetime: std::marker::PhantomData,
})
}
pub fn command<C: Command>(&mut self, cmd: C) -> Result<Option<u8>, AsciiError> {
self.check_poisoned()?;
let mut buffer = Vec::new();
let mut writer = CommandWriter::new(
&cmd,
&mut self.ids,
self.generate_id,
self.generate_checksum,
self.max_packet_size,
)?;
let mut more_packets = true;
while more_packets {
more_packets = writer.write_packet(&mut buffer)?;
log::debug!(
"{} TX: {}",
self.backend
.name()
.unwrap_or_else(|| UNKNOWN_BACKEND_NAME.to_string()),
String::from_utf8_lossy(buffer.as_slice()).trim_end()
);
self.backend.write_all(buffer.as_slice())?;
if let Some(callback) = self.handlers.packet() {
(callback)(buffer.as_slice(), Direction::Tx);
}
buffer.clear();
}
Ok(writer.id)
}
pub fn command_reply<C>(&mut self, cmd: C) -> Result<NotChecked<Reply>, AsciiError>
where
C: Command,
{
self.internal_command_reply(&cmd)
}
fn internal_command_reply(
&mut self,
cmd: &dyn Command,
) -> Result<NotChecked<Reply>, AsciiError> {
let id = self.command(cmd)?;
self.pre_receive_response();
let response = self.receive_response(HeaderCheck::Matches {
target: cmd.target(),
id,
})?;
self.post_receive_response()?;
Ok(response)
}
pub fn command_reply_infos<C, K>(
&mut self,
cmd: C,
checker: K,
) -> Result<(Reply, Vec<Info>), AsciiError>
where
C: Command,
K: check::Check<AnyResponse>,
{
let checker: &dyn check::Check<_> = &checker;
let reply_checker = |reply| {
checker
.check(AnyResponse::from(reply))
.map(|response| Reply::try_from(response).unwrap())
.map_err(|err| AsciiCheckError::try_from(err).unwrap())
};
let info_checker = |info| {
checker
.check(AnyResponse::from(info))
.map(|response| Info::try_from(response).unwrap())
.map_err(|err| AsciiCheckError::try_from(err).unwrap())
};
let (reply, info_iter) = self.command_reply_infos_iter(cmd)?;
let reply = reply.check(reply_checker)?;
let mut infos = Vec::new();
for result in info_iter {
let info = result?.check(info_checker)?;
infos.push(info);
}
Ok((reply, infos))
}
#[allow(clippy::type_complexity)]
pub fn command_reply_infos_iter<C: Command>(
&mut self,
cmd: C,
) -> Result<
(
NotChecked<Reply>,
iter::InfosUntilSentinel<'_, 'a, B, Tag, H>,
),
AsciiError,
> {
self.internal_command_reply_infos_iter(&cmd)
}
#[allow(clippy::type_complexity)]
fn internal_command_reply_infos_iter(
&mut self,
cmd: &dyn Command,
) -> Result<
(
NotChecked<Reply>,
iter::InfosUntilSentinel<'_, 'a, B, Tag, H>,
),
AsciiError,
> {
let target = cmd.target();
let reply = self.internal_command_reply(cmd)?;
let old_generate_id = self.set_message_ids(true);
let result = self.command((target, ""));
self.set_message_ids(old_generate_id);
let sentinel_id = result?;
let info_id = reply.id();
Ok((
reply,
iter::InfosUntilSentinel::new(self, target, info_id, sentinel_id),
))
}
pub fn command_reply_n<C, K>(
&mut self,
cmd: C,
n: usize,
checker: K,
) -> Result<Vec<Reply>, AsciiError>
where
C: Command,
K: check::Check<Reply>,
{
let mut replies = Vec::new();
let checker: &dyn check::Check<_> = &checker;
for result in self.internal_command_reply_n_iter(&cmd, n)? {
replies.push(result?.check(checker)?);
}
Ok(replies)
}
pub fn command_reply_n_iter<C>(
&mut self,
cmd: C,
n: usize,
) -> Result<iter::NResponses<'_, 'a, B, Reply, Tag, H>, AsciiError>
where
C: Command,
{
self.internal_command_reply_n_iter(&cmd, n)
}
fn internal_command_reply_n_iter(
&mut self,
cmd: &dyn Command,
n: usize,
) -> Result<iter::NResponses<'_, 'a, B, Reply, Tag, H>, AsciiError> {
let id = self.command(cmd)?;
Ok(self.internal_response_n_iter(
n,
HeaderCheck::Matches {
target: cmd.target(),
id,
},
))
}
pub fn command_replies_until_timeout<C, K>(
&mut self,
cmd: C,
checker: K,
) -> Result<Vec<Reply>, AsciiError>
where
C: Command,
K: check::Check<Reply>,
{
let mut replies = Vec::new();
let checker: &dyn check::Check<_> = &checker;
for result in self.internal_command_replies_until_timeout_iter(&cmd)? {
replies.push(result?.check(checker)?);
}
Ok(replies)
}
pub fn command_replies_until_timeout_iter<C>(
&mut self,
cmd: C,
) -> Result<iter::ResponsesUntilTimeout<'_, 'a, B, Reply, Tag, H>, AsciiError>
where
C: Command,
{
self.internal_command_replies_until_timeout_iter(&cmd)
}
fn internal_command_replies_until_timeout_iter(
&mut self,
cmd: &dyn Command,
) -> Result<iter::ResponsesUntilTimeout<'_, 'a, B, Reply, Tag, H>, AsciiError> {
let id = self.command(cmd)?;
Ok(
self.internal_responses_until_timeout_iter(HeaderCheck::Matches {
target: cmd.target(),
id,
}),
)
}
fn read_packet_bytes(&mut self) -> Result<Vec<u8>, AsciiError> {
use crate::ascii::packet::AsciiExt as _;
let mut buf = Vec::with_capacity(100);
let mut found_start = false;
let byte = std::io::Read::bytes(&mut self.backend).next().unwrap()?;
if byte.is_packet_start() {
buf.push(byte);
found_start = true;
}
let timeout = self.backend.read_timeout()?;
let inter_char_timeout = Duration::from_millis(300);
let effective_timeout = timeout.unwrap_or(Duration::MAX);
let use_inter_char_timeout = inter_char_timeout < effective_timeout;
if use_inter_char_timeout {
self.backend.set_read_timeout(Some(inter_char_timeout))?;
}
let result = || -> Result<(), AsciiError> {
for byte in std::io::Read::bytes(&mut self.backend) {
let byte = byte?;
if byte.is_packet_start() {
if found_start {
return Err(AsciiPacketMissingEndError::new(buf.clone()).into());
}
found_start = true;
}
if found_start {
buf.push(byte);
}
if byte == crate::ascii::packet::LINE_FEED {
break;
}
}
Ok(())
}();
if use_inter_char_timeout {
self.backend.set_read_timeout(timeout)?;
}
if !found_start {
return Err(AsciiPacketMissingStartError::new(buf).into());
}
if buf.is_empty() || *buf.last().unwrap() != crate::ascii::packet::LINE_FEED {
return Err(AsciiPacketMissingEndError::new(buf).into());
}
result.map(move |()| buf)
}
fn response_packet(&mut self) -> Result<Packet, AsciiError> {
let backend_name = self
.backend
.name()
.unwrap_or_else(|| UNKNOWN_BACKEND_NAME.to_string());
let raw_packet = self.read_packet_bytes()?;
log::debug!(
"{} RECV: {}",
&backend_name,
String::from_utf8_lossy(&raw_packet).trim_end()
);
if let Some(callback) = self.handlers.packet() {
(callback)(raw_packet.as_slice(), Direction::Recv);
}
let packet = Packet::try_from(&*raw_packet)?;
if let Some(checksum) = packet.checksum() {
if !Lrc::verify(packet.hashed_content(), checksum) {
return Err(AsciiInvalidChecksumError::new(packet).into());
}
}
if packet.kind() == PacketKind::Command {
Err(AsciiUnexpectedPacketError::new(packet).into())
} else {
Ok(packet)
}
}
fn build_response(&mut self) -> Result<AnyResponse, AsciiError> {
loop {
if let Some(response) = self.builder.get_complete_response() {
return Ok(response);
}
let packet = self.response_packet()?;
self.builder.push(packet)?;
}
}
#[inline]
fn pre_receive_response(&mut self) {
self.builder.clear();
}
fn post_receive_response(&mut self) -> Result<(), AsciiError> {
let mut inner = || -> Result<(), AsciiError> {
if let Some(response) = self.builder.get_complete_response() {
return Err(AsciiUnexpectedResponseError::new(response).into());
}
if let Some(packet) = self.builder.get_incomplete_response_packet() {
return Err(AsciiUnexpectedPacketError::new(packet).into());
}
Ok(())
};
if let Some(callback) = &mut self.handlers.unexpected_alert() {
loop {
match inner() {
Ok(()) => return Ok(()), Err(AsciiError::UnexpectedResponse(err)) => {
let response = err.into();
match response {
AnyResponse::Alert(alert) => match (callback)(alert) {
Ok(()) => {}
Err(alert) => {
self.builder.clear();
return Err(AsciiUnexpectedResponseError::new(alert).into());
}
},
response => {
self.builder.clear();
return Err(AsciiUnexpectedResponseError::new(response).into());
}
}
}
err => {
self.builder.clear();
return err;
}
}
}
} else {
let result = inner();
self.builder.clear();
result
}
}
fn receive_response<R>(
&mut self,
header_check: HeaderCheck,
) -> Result<NotChecked<R>, AsciiError>
where
R: Response,
AnyResponse: From<<R as TryFrom<AnyResponse>>::Error>,
AsciiError: From<AsciiCheckError<R>>,
{
self.check_poisoned()?;
loop {
let result = || -> Result<NotChecked<R>, AsciiError> {
let mut response = self.build_response()?;
response = header_check.check(response)?;
R::try_from(response)
.map(NotChecked::new)
.map_err(AsciiUnexpectedResponseError::new)
.map_err(From::from)
}();
if let Some(callback) = &mut self.handlers.unexpected_alert() {
match result {
Err(AsciiError::UnexpectedResponse(err)) => {
let response = err.into();
match response {
AnyResponse::Alert(alert) => match (callback)(alert) {
Ok(()) => {}
Err(alert) => {
return Err(AsciiUnexpectedResponseError::new(alert).into());
}
},
_ => return Err(AsciiUnexpectedResponseError::new(response).into()),
}
}
_ => return result,
}
} else {
return result;
}
}
}
fn internal_response_n_iter<R>(
&mut self,
n: usize,
header_check: HeaderCheck,
) -> iter::NResponses<'_, 'a, B, R, Tag, H>
where
R: Response,
{
iter::NResponses::new(self, header_check, n)
}
pub fn response<R>(&mut self) -> Result<NotChecked<R>, AsciiError>
where
R: Response,
AnyResponse: From<<R as TryFrom<AnyResponse>>::Error>,
AsciiError: From<AsciiCheckError<R>>,
{
self.pre_receive_response();
let response = self.receive_response(HeaderCheck::DoNotCheck)?;
self.post_receive_response()?;
Ok(response)
}
pub fn response_n_iter<R>(&mut self, n: usize) -> iter::NResponses<'_, 'a, B, R, Tag, H>
where
R: Response,
{
self.internal_response_n_iter(n, HeaderCheck::DoNotCheck)
}
pub fn response_n<R, K>(&mut self, n: usize, checker: K) -> Result<Vec<R>, AsciiError>
where
R: Response,
K: check::Check<R>,
AnyResponse: From<<R as TryFrom<AnyResponse>>::Error>,
AsciiError: From<AsciiCheckError<R>>,
{
let mut responses = Vec::new();
let checker: &dyn check::Check<R> = &checker;
for result in self.internal_response_n_iter(n, HeaderCheck::DoNotCheck) {
responses.push(result?.check(checker)?);
}
Ok(responses)
}
pub fn responses_until_timeout<R, K>(&mut self, checker: K) -> Result<Vec<R>, AsciiError>
where
R: Response,
K: check::Check<R>,
AnyResponse: From<<R as TryFrom<AnyResponse>>::Error>,
AsciiError: From<AsciiCheckError<R>>,
{
let mut responses = Vec::new();
let checker: &dyn check::Check<_> = &checker;
for result in self.internal_responses_until_timeout_iter(HeaderCheck::DoNotCheck) {
responses.push(result?.check(checker)?);
}
Ok(responses)
}
pub fn responses_until_timeout_iter<R>(
&mut self,
) -> iter::ResponsesUntilTimeout<'_, 'a, B, R, Tag, H>
where
R: Response,
{
self.internal_responses_until_timeout_iter(HeaderCheck::DoNotCheck)
}
fn internal_responses_until_timeout_iter<R>(
&mut self,
header_check: HeaderCheck,
) -> iter::ResponsesUntilTimeout<'_, 'a, B, R, Tag, H>
where
R: Response,
{
iter::ResponsesUntilTimeout::new(self, header_check)
}
pub fn poll<C>(&mut self, command: C) -> iter::Poll<'_, 'a, B, C, Tag, H>
where
C: Command,
{
iter::Poll {
port: self,
command,
}
}
pub fn poll_until<C, K, F>(
&mut self,
cmd: C,
checker: K,
predicate: F,
) -> Result<Reply, AsciiError>
where
C: Command,
K: check::Check<Reply>,
F: FnMut(&Reply) -> bool,
{
self.internal_poll_until(&cmd, &checker, predicate)
}
fn internal_poll_until<F>(
&mut self,
cmd: &dyn Command,
checker: &dyn check::Check<Reply>,
mut predicate: F,
) -> Result<Reply, AsciiError>
where
F: FnMut(&Reply) -> bool,
{
let mut reply;
loop {
reply = self.internal_command_reply(cmd)?.check(checker)?;
if predicate(&reply) {
break;
}
}
Ok(reply)
}
pub fn poll_until_idle<T, K>(&mut self, target: T, checker: K) -> Result<Reply, AsciiError>
where
T: Into<Target>,
K: check::Check<Reply>,
{
self.internal_poll_until(&(target.into(), ""), &checker, |reply| {
reply.status() == Status::Idle
})
}
pub fn timeout_guard(
&mut self,
timeout: Option<Duration>,
) -> Result<TimeoutGuard<'_, B, Self>, io::Error> {
self.check_poisoned()?;
TimeoutGuard::new(self, timeout)
}
pub fn set_checksums(&mut self, value: bool) -> bool {
std::mem::replace(&mut self.generate_checksum, value)
}
pub fn checksums(&self) -> bool {
self.generate_checksum
}
pub fn set_message_ids(&mut self, value: bool) -> bool {
std::mem::replace(&mut self.generate_id, value)
}
pub fn message_ids(&self) -> bool {
self.generate_id
}
pub fn set_max_packet_size(&mut self, value: MaxPacketSize) -> MaxPacketSize {
std::mem::replace(&mut self.max_packet_size, value)
}
pub fn max_packet_size(&self) -> MaxPacketSize {
self.max_packet_size
}
pub fn set_read_timeout(
&mut self,
timeout: Option<Duration>,
) -> Result<Option<Duration>, io::Error> {
let old = self.backend.read_timeout()?;
self.backend.set_read_timeout(timeout)?;
Ok(old)
}
pub fn read_timeout(&self) -> Result<Option<Duration>, io::Error> {
self.backend.read_timeout()
}
pub fn name(&self) -> Option<String> {
self.backend.name()
}
pub fn backend(&self) -> &B {
&self.backend
}
pub fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
pub fn into_backend(self) -> B {
self.backend
}
#[cfg(unstable)]
pub fn chain(&mut self) -> Result<Chain<Tag>, AsciiError> {
Chain::new(self)
}
#[cfg(unstable)]
pub fn run<R: IntoRoutine<Self>>(&mut self, item: R) -> Result<R::Output, R::Error> {
item.into_routine().run(self)
}
pub fn set_packet_handler<F>(&mut self, callback: F) -> Option<H::PacketHandler>
where
H::PacketHandler: crate::convert::From<F>,
{
std::mem::replace(
self.handlers.packet(),
Some(crate::convert::From::from(callback)),
)
}
pub fn clear_packet_handler(&mut self) -> Option<H::PacketHandler> {
self.handlers.packet().take()
}
pub fn set_unexpected_alert_handler<F>(
&mut self,
callback: F,
) -> Option<H::UnexpectedAlertHandler>
where
H::UnexpectedAlertHandler: crate::convert::From<F>,
{
std::mem::replace(
self.handlers.unexpected_alert(),
Some(crate::convert::From::from(callback)),
)
}
pub fn clear_unexpected_alert_handler(&mut self) -> Option<H::UnexpectedAlertHandler> {
self.handlers.unexpected_alert().take()
}
}
impl<'a, B, Tag, H> io::Write for Port<'a, B, Tag, H>
where
B: Backend,
H: Handlers,
H::PacketHandler: FnMut(&[u8], Direction) + 'a,
H::UnexpectedAlertHandler: FnMut(Alert) -> Result<(), Alert> + 'a,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.check_poisoned()?;
self.backend.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.check_poisoned()?;
self.backend.flush()
}
}
impl<'a, B, Tag, H> io::Read for Port<'a, B, Tag, H>
where
B: Backend,
H: Handlers,
H::PacketHandler: FnMut(&[u8], Direction) + 'a,
H::UnexpectedAlertHandler: FnMut(Alert) -> Result<(), Alert> + 'a,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.check_poisoned()?;
self.backend.read(buf)
}
}
impl<B: Backend, Tag, H> crate::timeout_guard::Port<B> for Port<'_, B, Tag, H> {
fn backend_mut(&mut self) -> &mut B {
&mut self.backend
}
fn poison(&mut self, e: io::Error) {
self.poison = Some(e);
}
}
#[derive(Debug, Copy, Clone)]
pub(super) enum HeaderCheck {
DoNotCheck,
Matches { target: Target, id: Option<u8> },
InfoSentinelReplyMatches {
target: Target,
info_id: Option<u8>,
sentinel_id: Option<u8>,
},
}
impl HeaderCheck {
fn check(self, response: AnyResponse) -> Result<AnyResponse, AsciiError> {
use HeaderCheck as HC;
match self {
HC::DoNotCheck => Ok(response),
HC::Matches { target, id } => {
if !response.target().elicited_by_command_to(target) || response.id() != id {
Err(AsciiUnexpectedResponseError::new(response).into())
} else {
Ok(response)
}
}
HC::InfoSentinelReplyMatches {
target,
info_id,
sentinel_id,
} => {
if !response.target().elicited_by_command_to(target) {
return Err(AsciiUnexpectedResponseError::new(response).into());
}
match response {
AnyResponse::Info(ref info) if info.id() == info_id => Ok(response),
AnyResponse::Reply(ref reply) if reply.id() == sentinel_id => Ok(response),
_ => Err(AsciiUnexpectedResponseError::new(response).into()),
}
}
}
}
}