Baseline timed request handling
This commit is contained in:
parent
ed5949253b
commit
d67e134e26
6 changed files with 163 additions and 18 deletions
|
@ -15,6 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use super::core::IMStatusCode;
|
||||
use super::core::OpCode;
|
||||
use super::messages::ib;
|
||||
use super::messages::msg;
|
||||
|
@ -50,12 +51,23 @@ impl InteractionModel {
|
|||
rx_buf: &[u8],
|
||||
proto_tx: &mut Packet,
|
||||
) -> Result<ResponseRequired, Error> {
|
||||
proto_tx.set_proto_opcode(OpCode::InvokeResponse as u8);
|
||||
if InteractionModel::req_timeout_handled(trans, proto_tx)? == true {
|
||||
return Ok(ResponseRequired::Yes);
|
||||
}
|
||||
|
||||
proto_tx.set_proto_opcode(OpCode::InvokeResponse as u8);
|
||||
let mut tw = TLVWriter::new(proto_tx.get_writebuf()?);
|
||||
let root = get_root_node_struct(rx_buf)?;
|
||||
let inv_req = InvReq::from_tlv(&root)?;
|
||||
|
||||
let timed_tx = trans.exch.get_expiry_ts().map(|_| true);
|
||||
let timed_request = inv_req.timed_request.filter(|a| *a == true);
|
||||
// Either both should be None, or both should be Some(true)
|
||||
if timed_tx != timed_request {
|
||||
InteractionModel::create_status_response(proto_tx, IMStatusCode::TimedRequestMisMatch)?;
|
||||
return Ok(ResponseRequired::Yes);
|
||||
}
|
||||
|
||||
tw.start_struct(TagType::Anonymous)?;
|
||||
// Suppress Response -> TODO: Need to revisit this for cases where we send a command back
|
||||
tw.bool(
|
||||
|
|
|
@ -15,10 +15,15 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use crate::{
|
||||
error::*,
|
||||
tlv::{self, FromTLV, TLVElement, TLVWriter, TagType, ToTLV},
|
||||
interaction_model::messages::msg::StatusResp,
|
||||
tlv::{self, get_root_node_struct, FromTLV, TLVElement, TLVWriter, TagType, ToTLV},
|
||||
transport::{
|
||||
exchange::Exchange,
|
||||
packet::Packet,
|
||||
proto_demux::{self, ProtoCtx, ResponseRequired},
|
||||
session::Session,
|
||||
},
|
||||
|
@ -28,10 +33,10 @@ use log::{error, info};
|
|||
use num;
|
||||
use num_derive::FromPrimitive;
|
||||
|
||||
use super::InteractionConsumer;
|
||||
use super::InteractionModel;
|
||||
use super::Transaction;
|
||||
use super::TransactionState;
|
||||
use super::{messages::msg::TimedReq, InteractionConsumer};
|
||||
|
||||
/* Handle messages related to the Interation Model
|
||||
*/
|
||||
|
@ -55,11 +60,11 @@ pub enum OpCode {
|
|||
}
|
||||
|
||||
impl<'a> Transaction<'a> {
|
||||
pub fn new(session: &'a mut Session) -> Self {
|
||||
pub fn new(session: &'a mut Session, exch: &'a mut Exchange) -> Self {
|
||||
Self {
|
||||
state: TransactionState::Ongoing,
|
||||
data: None,
|
||||
session,
|
||||
exch,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,17 +75,77 @@ impl<'a> Transaction<'a> {
|
|||
pub fn is_complete(&self) -> bool {
|
||||
self.state == TransactionState::Complete
|
||||
}
|
||||
|
||||
pub fn set_timeout(&mut self, timeout: u64) {
|
||||
self.exch
|
||||
.set_expiry_ts(SystemTime::now().checked_add(Duration::from_millis(timeout)));
|
||||
}
|
||||
|
||||
pub fn has_timed_out(&self) -> bool {
|
||||
if let Some(timeout) = self.exch.get_expiry_ts() {
|
||||
if SystemTime::now() > timeout {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractionModel {
|
||||
pub fn new(consumer: Box<dyn InteractionConsumer>) -> InteractionModel {
|
||||
InteractionModel { consumer }
|
||||
}
|
||||
|
||||
pub fn handle_timed_req(
|
||||
&mut self,
|
||||
trans: &mut Transaction,
|
||||
rx_buf: &[u8],
|
||||
proto_tx: &mut Packet,
|
||||
) -> Result<ResponseRequired, Error> {
|
||||
proto_tx.set_proto_opcode(OpCode::StatusResponse as u8);
|
||||
|
||||
let root = get_root_node_struct(rx_buf)?;
|
||||
let req = TimedReq::from_tlv(&root)?;
|
||||
trans.set_timeout(req.timeout.into());
|
||||
|
||||
let status = StatusResp {
|
||||
status: IMStatusCode::Sucess,
|
||||
};
|
||||
let mut tw = TLVWriter::new(proto_tx.get_writebuf()?);
|
||||
let _ = status.to_tlv(&mut tw, TagType::Anonymous);
|
||||
Ok(ResponseRequired::Yes)
|
||||
}
|
||||
|
||||
/// Handle Request Timeouts
|
||||
/// This API checks if a request was a timed request, and if so, and if the timeout has
|
||||
/// expired, it will generate the appropriate response as expected
|
||||
pub(super) fn req_timeout_handled(
|
||||
trans: &mut Transaction,
|
||||
proto_tx: &mut Packet,
|
||||
) -> Result<bool, Error> {
|
||||
if trans.has_timed_out() {
|
||||
trans.complete();
|
||||
InteractionModel::create_status_response(proto_tx, IMStatusCode::Timeout)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_status_response(
|
||||
proto_tx: &mut Packet,
|
||||
status: IMStatusCode,
|
||||
) -> Result<(), Error> {
|
||||
proto_tx.set_proto_opcode(OpCode::StatusResponse as u8);
|
||||
let mut tw = TLVWriter::new(proto_tx.get_writebuf()?);
|
||||
let status = StatusResp { status };
|
||||
status.to_tlv(&mut tw, TagType::Anonymous)
|
||||
}
|
||||
}
|
||||
|
||||
impl proto_demux::HandleProto for InteractionModel {
|
||||
fn handle_proto_id(&mut self, ctx: &mut ProtoCtx) -> Result<ResponseRequired, Error> {
|
||||
let mut trans = Transaction::new(&mut ctx.exch_ctx.sess);
|
||||
let mut trans = Transaction::new(&mut ctx.exch_ctx.sess, ctx.exch_ctx.exch);
|
||||
let proto_opcode: OpCode =
|
||||
num::FromPrimitive::from_u8(ctx.rx.get_proto_opcode()).ok_or(Error::Invalid)?;
|
||||
ctx.tx.set_proto_id(PROTO_ID_INTERACTION_MODEL as u16);
|
||||
|
@ -92,6 +157,7 @@ impl proto_demux::HandleProto for InteractionModel {
|
|||
OpCode::InvokeRequest => self.handle_invoke_req(&mut trans, buf, &mut ctx.tx)?,
|
||||
OpCode::ReadRequest => self.handle_read_req(&mut trans, buf, &mut ctx.tx)?,
|
||||
OpCode::WriteRequest => self.handle_write_req(&mut trans, buf, &mut ctx.tx)?,
|
||||
OpCode::TimedRequest => self.handle_timed_req(&mut trans, buf, &mut ctx.tx)?,
|
||||
_ => {
|
||||
error!("Opcode Not Handled: {:?}", proto_opcode);
|
||||
return Err(Error::InvalidOpcode);
|
||||
|
@ -137,6 +203,10 @@ pub enum IMStatusCode {
|
|||
UnsupportedCluster = 0xc3,
|
||||
NoUpstreamSubscription = 0xc5,
|
||||
NeedsTimedInteraction = 0xc6,
|
||||
UnsupportedEvent = 0xc7,
|
||||
PathsExhausted = 0xc8,
|
||||
TimedRequestMisMatch = 0xc9,
|
||||
FailSafeRequired = 0xca,
|
||||
}
|
||||
|
||||
impl From<Error> for IMStatusCode {
|
||||
|
|
|
@ -67,11 +67,22 @@ pub mod msg {
|
|||
|
||||
use crate::{
|
||||
error::Error,
|
||||
interaction_model::core::IMStatusCode,
|
||||
tlv::{FromTLV, TLVArray, TLVElement, TLVWriter, TagType, ToTLV},
|
||||
};
|
||||
|
||||
use super::ib::{AttrData, AttrPath, AttrResp, CmdData, DataVersionFilter};
|
||||
|
||||
#[derive(FromTLV)]
|
||||
pub struct TimedReq {
|
||||
pub timeout: u16,
|
||||
}
|
||||
|
||||
#[derive(ToTLV)]
|
||||
pub struct StatusResp {
|
||||
pub status: IMStatusCode,
|
||||
}
|
||||
|
||||
#[derive(FromTLV)]
|
||||
#[tlvargs(lifetime = "'a")]
|
||||
pub struct InvReq<'a> {
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use crate::{error::Error, tlv::TLVWriter, transport::session::Session};
|
||||
use crate::{
|
||||
error::Error,
|
||||
tlv::TLVWriter,
|
||||
transport::{exchange::Exchange, session::Session},
|
||||
};
|
||||
|
||||
use self::messages::msg::{InvReq, ReadReq, WriteReq};
|
||||
|
||||
|
@ -28,8 +30,8 @@ pub enum TransactionState {
|
|||
}
|
||||
pub struct Transaction<'a> {
|
||||
pub state: TransactionState,
|
||||
pub data: Option<Box<dyn Any>>,
|
||||
pub session: &'a mut Session,
|
||||
pub exch: &'a mut Exchange,
|
||||
}
|
||||
|
||||
pub trait InteractionConsumer {
|
||||
|
|
|
@ -32,6 +32,9 @@ impl InteractionModel {
|
|||
rx_buf: &[u8],
|
||||
proto_tx: &mut Packet,
|
||||
) -> Result<ResponseRequired, Error> {
|
||||
if InteractionModel::req_timeout_handled(trans, proto_tx)? == true {
|
||||
return Ok(ResponseRequired::Yes);
|
||||
}
|
||||
proto_tx.set_proto_opcode(OpCode::WriteResponse as u8);
|
||||
|
||||
let mut tw = TLVWriter::new(proto_tx.get_writebuf()?);
|
||||
|
|
|
@ -20,6 +20,7 @@ use colored::*;
|
|||
use log::{error, info, trace};
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::secure_channel;
|
||||
|
@ -59,17 +60,32 @@ impl Default for State {
|
|||
}
|
||||
}
|
||||
|
||||
// Instead of just doing an Option<>, we create some special handling
|
||||
// where the commonly used higher layer data store does't have to do a Box
|
||||
#[derive(Debug)]
|
||||
pub enum DataOption {
|
||||
Boxed(Box<dyn Any>),
|
||||
Time(SystemTime),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for DataOption {
|
||||
fn default() -> Self {
|
||||
DataOption::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Exchange {
|
||||
id: u16,
|
||||
sess_idx: usize,
|
||||
role: Role,
|
||||
state: State,
|
||||
mrp: ReliableMessage,
|
||||
// Currently I see this primarily used in PASE and CASE. If that is the limited use
|
||||
// of this, we might move this into a separate data structure, so as not to burden
|
||||
// all 'exchanges'.
|
||||
data: Option<Box<dyn Any>>,
|
||||
mrp: ReliableMessage,
|
||||
data: DataOption,
|
||||
}
|
||||
|
||||
impl Exchange {
|
||||
|
@ -79,13 +95,13 @@ impl Exchange {
|
|||
sess_idx,
|
||||
role,
|
||||
state: State::Open,
|
||||
data: None,
|
||||
mrp: ReliableMessage::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.data = None;
|
||||
self.data = DataOption::None;
|
||||
self.state = State::Close;
|
||||
}
|
||||
|
||||
|
@ -106,20 +122,51 @@ impl Exchange {
|
|||
self.role
|
||||
}
|
||||
|
||||
pub fn is_data_none(&self) -> bool {
|
||||
match self.data {
|
||||
DataOption::None => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_exchange_data(&mut self, data: Box<dyn Any>) {
|
||||
self.data = Some(data);
|
||||
self.data = DataOption::Boxed(data);
|
||||
}
|
||||
|
||||
pub fn clear_exchange_data(&mut self) {
|
||||
self.data = None;
|
||||
self.data = DataOption::None;
|
||||
}
|
||||
|
||||
pub fn get_exchange_data<T: Any>(&mut self) -> Option<&mut T> {
|
||||
self.data.as_mut()?.downcast_mut::<T>()
|
||||
if let DataOption::Boxed(a) = &mut self.data {
|
||||
a.downcast_mut::<T>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_exchange_data<T: Any>(&mut self) -> Option<Box<T>> {
|
||||
self.data.take()?.downcast::<T>().ok()
|
||||
let old = std::mem::replace(&mut self.data, DataOption::None);
|
||||
match old {
|
||||
DataOption::Boxed(d) => d.downcast::<T>().ok(),
|
||||
_ => {
|
||||
self.data = old;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_expiry_ts(&mut self, expiry_ts: Option<SystemTime>) {
|
||||
if let Some(t) = expiry_ts {
|
||||
self.data = DataOption::Time(t);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_expiry_ts(&self) -> Option<SystemTime> {
|
||||
match self.data {
|
||||
DataOption::Time(t) => Some(t),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn send(
|
||||
|
|
Loading…
Add table
Reference in a new issue