Wait for Session disconnect before returning
This commit is contained in:
parent
6c570da60e
commit
81b4928f16
1 changed files with 55 additions and 6 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Mutex, mpsc};
|
use std::sync::{Mutex, mpsc};
|
||||||
|
|
@ -13,9 +13,9 @@ use lazaret_core::identity::{ClientId, SessionAnchorId, SessionId};
|
||||||
use lazaret_core::records::{
|
use lazaret_core::records::{
|
||||||
ClientEnrollmentResponse, ControlRequest, ControlResponse, DeveloperDecision,
|
ClientEnrollmentResponse, ControlRequest, ControlResponse, DeveloperDecision,
|
||||||
DeveloperDecisionKind, InteractiveSessionBatchV2, InteractiveSessionEventV2,
|
DeveloperDecisionKind, InteractiveSessionBatchV2, InteractiveSessionEventV2,
|
||||||
InteractiveSessionRequestV2, NonInteractiveSessionMessage, NonInteractiveSessionRequest,
|
InteractiveSessionMessage, InteractiveSessionRequestV2, NonInteractiveSessionMessage,
|
||||||
ProtocolPublicationIdentity, ProtocolVersion, SessionBinding, SessionExitOutcome,
|
NonInteractiveSessionRequest, ProtocolPublicationIdentity, ProtocolVersion, SessionBinding,
|
||||||
SessionRegistrationRequest, TlsSubjectId,
|
SessionExitOutcome, SessionRegistrationRequest, SessionTerminalMessage, TlsSubjectId,
|
||||||
};
|
};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
|
@ -386,7 +386,7 @@ pub fn execute_over_control_face_as<E>(
|
||||||
}
|
}
|
||||||
NonInteractiveSessionMessage::Exit(exit) => {
|
NonInteractiveSessionMessage::Exit(exit) => {
|
||||||
require_session(exit.session_id(), session_id)?;
|
require_session(exit.session_id(), session_id)?;
|
||||||
return match exit.outcome() {
|
let result = match exit.outcome() {
|
||||||
SessionExitOutcome::Exited => exit
|
SessionExitOutcome::Exited => exit
|
||||||
.exit_code()
|
.exit_code()
|
||||||
.ok_or(ClientError::Protocol("Exited omitted its exit code")),
|
.ok_or(ClientError::Protocol("Exited omitted its exit code")),
|
||||||
|
|
@ -394,6 +394,8 @@ pub fn execute_over_control_face_as<E>(
|
||||||
Err(ClientError::LostConnection)
|
Err(ClientError::LostConnection)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
await_control_close(&mut stream)?;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +578,27 @@ impl Drop for ControlFaceInteractiveTransport {
|
||||||
fn receive_interactive_batch(
|
fn receive_interactive_batch(
|
||||||
stream: &mut impl std::io::Read,
|
stream: &mut impl std::io::Read,
|
||||||
) -> Result<InteractiveSessionBatchV2, ClientError<std::convert::Infallible>> {
|
) -> Result<InteractiveSessionBatchV2, ClientError<std::convert::Infallible>> {
|
||||||
ControlClient::receive(stream).map_err(map_control_connection)
|
let batch: InteractiveSessionBatchV2 =
|
||||||
|
ControlClient::receive(stream).map_err(map_control_connection)?;
|
||||||
|
if batch.messages.iter().any(|message| {
|
||||||
|
matches!(
|
||||||
|
message,
|
||||||
|
InteractiveSessionMessage::Terminal(SessionTerminalMessage::Exit(_))
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
await_control_close(stream)?;
|
||||||
|
}
|
||||||
|
Ok(batch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn await_control_close<E>(stream: &mut impl Read) -> Result<(), ClientError<E>> {
|
||||||
|
let mut trailing = [0_u8; 1];
|
||||||
|
match stream.read(&mut trailing) {
|
||||||
|
Ok(0) => Ok(()),
|
||||||
|
Ok(_) => Err(ClientError::Protocol("control data followed Session exit")),
|
||||||
|
Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => Ok(()),
|
||||||
|
Err(_) => Err(ClientError::LostConnection),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_control_connection(error: ControlClientError) -> ClientError<std::convert::Infallible> {
|
fn map_control_connection(error: ControlClientError) -> ClientError<std::convert::Infallible> {
|
||||||
|
|
@ -623,6 +645,33 @@ impl<E> From<io::Error> for ClientError<E> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct UnexpectedEof;
|
||||||
|
|
||||||
|
impl Read for UnexpectedEof {
|
||||||
|
fn read(&mut self, _buffer: &mut [u8]) -> io::Result<usize> {
|
||||||
|
Err(io::Error::from(io::ErrorKind::UnexpectedEof))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clean_control_close_is_required_after_session_exit() {
|
||||||
|
let mut closed = std::io::Cursor::new(Vec::<u8>::new());
|
||||||
|
assert!(await_control_close::<std::convert::Infallible>(&mut closed).is_ok());
|
||||||
|
|
||||||
|
assert!(await_control_close::<std::convert::Infallible>(&mut UnexpectedEof).is_ok());
|
||||||
|
|
||||||
|
let mut trailing = std::io::Cursor::new(vec![1_u8]);
|
||||||
|
assert!(matches!(
|
||||||
|
await_control_close::<std::convert::Infallible>(&mut trailing),
|
||||||
|
Err(ClientError::Protocol("control data followed Session exit"))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<E> From<ControlClientError> for ClientError<E> {
|
impl<E> From<ControlClientError> for ClientError<E> {
|
||||||
fn from(error: ControlClientError) -> Self {
|
fn from(error: ControlClientError) -> Self {
|
||||||
Self::Control(error)
|
Self::Control(error)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue