use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
use std::collections::{HashMap, hash_map};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use std::sync::{Arc, Mutex, Weak};
use std::{io, mem, slice, task};
use std::net::SocketAddr;
use std::future::Future;
use std::borrow::Cow;
use std::pin::Pin;
use mio::{Events, Interest, Poll, Registry, Token, Waker};
use tracing::{debug, error, trace, warn};
use mio::net::TcpStream;
use crate::error::{Error, ErrorKind, Result};
use crate::buf::{Buf, BufMut};
use crate::future::resolve;
use super::cdr::{CdrReader, CdrWrite, CdrWriter, CdrSender};
use super::ior::{Ior, ProfileKind, ComponentKind};
use super::{MAX_MESSAGE_LEN, EVENTS_CAPACITY};
use super::io::{ReadExt as _, WriteExt as _};
use super::codeset::{self, Codesets};
use super::endian::Endian;
use super::giop::{
CancelRequest, Message, MessageLen, ReplyStatus, Request,
MessageWhole, ServiceContext, ServiceContextKind, SystemException, Target, Version};
const VERSION: Version = Version::V12;
const SWEEP_INTERVAL: Duration = Duration::from_secs(30);
const WAKER_TOKEN: Token = Token(0);
const FIRST_CONNECTION_TOKEN: usize = 1;
const REQUEST_MIN_LEN: usize = 512;
#[derive(Debug)]
pub struct Client {
shared: Arc<Shared>,
waker: Waker,
thread: Option<JoinHandle<()>>,
}
impl Client {
pub fn new() -> Result<Self> {
let poll = Poll::new()
.map_err(|err| Error::new(ErrorKind::Internal)
.with_description("Failed to create the poll of a CORBA client")
.with_source(Box::new(err)))?;
let poll_registry = poll.registry().try_clone()
.map_err(|err| Error::new(ErrorKind::Internal)
.with_description("Failed to clone the poll registry of a CORBA client")
.with_source(Box::new(err)))?;
let waker = Waker::new(poll.registry(), WAKER_TOKEN)
.map_err(|err| Error::new(ErrorKind::Internal)
.with_description("Failed to create the waker of a CORBA client")
.with_source(Box::new(err)))?;
let shared = Arc::new(Shared {
connections: Mutex::new(Connections::default()),
poll_registry,
next_token: AtomicUsize::new(FIRST_CONNECTION_TOKEN),
running: AtomicBool::new(true),
});
let thread = Thread {
shared: Arc::clone(&shared),
connections: HashMap::new(),
next_sweep: Instant::now() + SWEEP_INTERVAL,
poll,
events: Events::with_capacity(EVENTS_CAPACITY),
};
let handle = thread::Builder::new()
.name("corba-client".to_string())
.spawn(move || thread.main())
.map_err(|err| Error::new(ErrorKind::Internal)
.with_description("Failed to spawn the polling thread of a CORBA client")
.with_source(Box::new(err)))?;
Ok(Self {
shared,
waker,
thread: Some(handle),
})
}
pub async fn connect_object_ior(&self, ior: &str, type_id_filter: &[&str]) -> Result<ClientObject> {
let profiles = parse_ior_profiles(ior, type_id_filter)
.map_err(|err| Error::new(ErrorKind::Corba)
.with_append_description("Cannot parse IOR profiles to connect to CORBA object")
.with_parent(err))?;
let mut last_err = None::<Error>;
for profile in profiles {
for (host, port) in profile.addrs {
let err = match self.connect_object_iiop(&host, port, profile.codesets, Some(profile.version), Arc::clone(&profile.object)).await {
Ok(object) => return Ok(object),
Err(err) => err,
};
debug!(host, port, %err, "Failed to connect to the CORBA object, trying the next profile of the IOR");
last_err = Some(err);
}
}
match last_err {
Some(err) => Err(Error::new(ErrorKind::Corba)
.with_append_description(format_args!("No profile of IOR accepted a connection"))
.with_parent(err)),
None => Err(Error::new(ErrorKind::Corba)
.with_append_description(format_args!("The IOR resolves to no profile at all"))),
}
}
pub async fn connect_object_iiop(&self, host: &str, port: u16, codesets: Option<Codesets>, version: Option<Version>, object: Arc<[u8]>) -> Result<ClientObject> {
let connection = self.connect_iiop(host, port, codesets, version).await?;
Ok(ClientObject {
connection,
object,
})
}
async fn connect_iiop(&self, host: &str, port: u16, codesets: Option<Codesets>, version: Option<Version>) -> Result<Arc<Connection>> {
let candidates = resolve(host, port).await?;
let mut last_err = None::<(SocketAddr, Error)>;
for addr in candidates {
let err = match self.shared.connect(addr, codesets, version) {
Ok(connection) => {
let init = connection.wait_init().await;
match init {
Ok(()) => return Ok(connection),
Err(err) => err,
}
}
Err(err) => err,
};
debug!(host, port, %addr, %err, "Failed to connect to the CORBA server, trying the next address");
last_err = Some((addr, err));
}
match last_err {
Some((addr, err)) => Err(Error::new(ErrorKind::Corba)
.with_append_description(format_args!("No address of {host}:{port} accepted a connection, last tried {addr}"))
.with_parent(err)),
None => Err(Error::new(ErrorKind::Corba)
.with_append_description(format_args!("The host {host}:{port} resolves to no address at all"))),
}
}
}
impl Drop for Client {
fn drop(&mut self) {
self.shared.running.store(false, Ordering::Relaxed);
if let Err(err) = self.waker.wake() {
warn!(%err, "Failed to wake the polling thread of the CORBA client being dropped");
}
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
#[derive(Debug)]
pub struct ClientObject {
connection: Arc<Connection>,
object: Arc<[u8]>,
}
impl ClientObject {
#[inline]
pub fn object(&self) -> &[u8] {
&self.object
}
#[inline]
pub fn request(&self, operation: &[u8], data: &dyn CdrWrite) -> ClientRequest {
self.connection.request(self.object(), operation, data)
}
}
struct Thread {
shared: Arc<Shared>,
connections: HashMap<Token, RecvConnection>,
next_sweep: Instant,
poll: Poll,
events: Events,
}
impl Thread {
fn main(mut self) {
while self.shared.running.load(Ordering::Relaxed) {
match self.run() {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
Err(err) => {
error!(%err, "Failed to poll the connections of the CORBA client, giving up on them");
break;
}
}
}
for (_, held) in self.connections.drain() {
held.connection.fail();
}
let mut connections = self.shared.connections.lock().unwrap();
for (_, connection) in connections.by_token.drain() {
if let Some(connection) = connection.upgrade() {
connection.fail();
}
}
connections.by_identity.clear();
}
fn run(&mut self) -> io::Result<()> {
let timeout = self.next_sweep.saturating_duration_since(Instant::now());
self.poll.poll(&mut self.events, Some(timeout))?;
self.handle_events();
if Instant::now() >= self.next_sweep {
self.sweep();
self.next_sweep = Instant::now() + SWEEP_INTERVAL;
}
Ok(())
}
fn forget(&mut self, token: Token, identity: Identity) {
self.connections.remove(&token);
let mut connections = self.shared.connections.lock().unwrap();
let Some(gone) = connections.by_token.remove(&token) else {
return;
};
if connections.by_identity.get(&identity).is_some_and(|held| Weak::ptr_eq(held, &gone)) {
connections.by_identity.remove(&identity);
}
}
fn sweep(&mut self) {
let mut connections = self.shared.connections.lock().unwrap();
self.connections.retain(|_, held| Arc::strong_count(&held.connection) > 1);
connections.by_token.retain(|_, connection| connection.strong_count() > 0);
connections.by_identity.retain(|_, connection| connection.strong_count() > 0);
}
fn handle_events(&mut self) {
let events = mem::replace(&mut self.events, Events::with_capacity(0));
for event in &events {
if event.token() == WAKER_TOKEN {
continue;
}
let recv = match self.connections.entry(event.token()) {
hash_map::Entry::Occupied(entry) => entry.into_mut(),
hash_map::Entry::Vacant(entry) => {
let connection = self.shared.connections.lock().unwrap()
.by_token.get(&event.token())
.and_then(Weak::upgrade);
let Some(connection) = connection else {
warn!(?event, "Received a poll event for no known connection, ignoring");
continue;
};
entry.insert(RecvConnection {
connection,
recv: RecvState {
buf: Buf::new(),
fragments: HashMap::new(),
adjacent: None,
},
})
}
};
if recv.connection.is_init() {
let failed = recv.connection.complete_init();
let identity = recv.connection.identity;
if failed {
self.forget(event.token(), identity);
}
continue;
}
let mut failure = None;
if event.is_writable() {
let mut send_state = recv.connection.send_state.lock().unwrap();
failure = recv.connection.flush(&mut send_state.writer).err();
}
if failure.is_none() && event.is_readable() {
failure = recv.connection.receive(&mut recv.recv).err();
}
if failure.is_none() && event.is_read_closed() {
failure = Some(Error::new(ErrorKind::Corba)
.with_description("The server closed the connection"));
}
if let Some(err) = failure {
let identity = recv.connection.identity;
debug!(addr = %identity.addr, %err, "Lost the connection to the CORBA server");
recv.connection.fail();
self.forget(event.token(), identity);
}
}
self.events = events;
}
}
#[derive(Debug)]
struct Shared {
connections: Mutex<Connections>,
poll_registry: Registry,
next_token: AtomicUsize,
running: AtomicBool,
}
impl Shared {
fn connect(&self, addr: SocketAddr, codesets: Option<Codesets>, version: Option<Version>) -> Result<Arc<Connection>> {
let identity = Identity {
addr,
codesets,
version,
};
let mut connections = self.connections.lock().unwrap();
if let Some(connection) = connections.by_identity.get(&identity).and_then(Weak::upgrade) {
trace!(%addr, "Reusing the connection to the CORBA server");
return Ok(connection);
}
trace!(%addr, "Connecting to the CORBA server");
let mut stream = TcpStream::connect(addr)
.map_err(|err| Error::new(ErrorKind::Corba)
.with_append_description(format_args!("Could not open a socket to {}", addr))
.with_source(Box::new(err)))?;
if let Err(err) = stream.set_nodelay(true) {
debug!(%addr, %err, "Failed to disable Nagle's algorithm on the stream");
}
let token = Token(self.next_token.fetch_add(1, Ordering::Relaxed));
self.poll_registry.register(&mut stream, token, Interest::WRITABLE | Interest::READABLE)
.map_err(|err| Error::new(ErrorKind::Internal)
.with_description("Failed to register the socket of a CORBA connection with the poll")
.with_source(Box::new(err)))?;
let connection = Arc::new(Connection {
stream,
identity,
_token: token,
init: Init {
done: AtomicBool::new(false),
state: Mutex::new(InitState::Connecting(Vec::new())),
},
next_request_id: AtomicU32::new(0),
request_state: Mutex::new(RequestState {
slots: HashMap::new(),
lost: false,
}),
send_state: Mutex::new(SendState {
writer: CdrWriter::new().with_codesets(identity.codesets).into_sender(),
version: identity.version.unwrap_or(VERSION).min(VERSION),
version_settled: false,
codesets_sent: false,
}),
});
connections.by_token.insert(token, Arc::downgrade(&connection));
connections.by_identity.insert(identity, Arc::downgrade(&connection));
Ok(connection)
}
}
#[derive(Default, Debug)]
struct Connections {
by_token: HashMap<Token, Weak<Connection>>,
by_identity: HashMap<Identity, Weak<Connection>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Identity {
addr: SocketAddr,
codesets: Option<Codesets>,
version: Option<Version>,
}
#[derive(Debug)]
struct Connection {
stream: TcpStream,
identity: Identity,
_token: Token,
init: Init,
next_request_id: AtomicU32,
request_state: Mutex<RequestState>,
send_state: Mutex<SendState>,
}
impl Connection {
fn wait_init(&self) -> WaitInit<'_> {
WaitInit(self)
}
fn is_init(&self) -> bool {
!self.init.is_done()
}
fn complete_init(&self) -> bool {
let err = match self.stream.take_error() {
Ok(Some(err)) | Err(err) => err,
Ok(None) => match self.stream.peer_addr() {
Ok(_) => {
debug!(addr = %self.identity.addr, "Connected to the CORBA server");
self.init.set(InitState::Established);
return false;
}
Err(err) if err.kind() == io::ErrorKind::NotConnected => return false,
Err(err) => err,
}
};
debug!(addr = %self.identity.addr, %err, "Failed to connect to the CORBA server");
self.init.set(InitState::Failed(Some(err)));
true
}
fn request(self: &Arc<Self>, object: &[u8], operation: &[u8], data: &dyn CdrWrite) -> ClientRequest {
ClientRequest {
state: match self.try_request(object, operation, data) {
Ok(state) => state,
Err(err) => ClientRequestState::Failed(Some(err)),
},
}
}
fn try_request(self: &Arc<Self>, object: &[u8], operation: &[u8], data: &dyn CdrWrite) -> Result<ClientRequestState> {
let request_id = self.next_request_id.fetch_add(1, Ordering::Relaxed);
let mut send_state = self.send_state.lock().unwrap();
let version = send_state.version;
let announce = match self.identity.codesets {
Some(codesets) if !send_state.codesets_sent => Some(ServiceContextKind::Codesets(codesets).build()?),
_ => None,
};
let mut alternates = Vec::new();
if !send_state.version_settled {
for minor in 0..version.minor() {
let alternate = Version::from_minor(minor).expect("every minor under a version is one");
let mut writer = CdrWriter::with_capacity(REQUEST_MIN_LEN).with_codesets(self.identity.codesets);
self.write_request(&mut writer, alternate, request_id, object, operation, data, announce.as_ref())?;
alternates.push((alternate, writer.into_buf().freeze()));
}
}
{
let mut request_state = self.request_state.lock().unwrap();
if request_state.lost {
return Err(Error::new(ErrorKind::Corba)
.with_description("The connection to the server is over"));
}
request_state.slots.insert(request_id, RequestSlot {
wakers: Vec::new(),
reply: None,
alternates,
});
}
trace!(request_id, %version, "Sending a request");
self.write_request(&mut send_state.writer, version, request_id, object, operation, data, announce.as_ref())?;
self.flush(&mut send_state.writer)?;
send_state.codesets_sent = true;
Ok(ClientRequestState::Sent {
connection: Arc::clone(self),
request_id,
})
}
fn cancel(&self, request_id: u32) -> Result<()> {
let mut send_state = self.send_state.lock().unwrap();
let version = send_state.version;
trace!(request_id, %version, "Cancelling a request");
let message = Message::CancelRequest(CancelRequest::new(version, request_id));
self.write_message(&mut send_state.writer, &message, None)?;
self.flush(&mut send_state.writer)
}
fn write_message(&self, writer: &mut CdrWriter, message: &Message, body: Option<&dyn CdrWrite>) -> Result<()> {
let origin = writer.origin();
let at = writer.position();
writer.set_origin(at);
let res = writer.write(message)
.and_then(|()| body.map_or(Ok(()), |body| writer.write(body)))
.and_then(|()| writer.write(&MessageLen));
writer.set_origin(origin);
if res.is_err() {
writer.set_position(at);
}
res
}
fn write_request(&self, writer: &mut CdrWriter, version: Version, request_id: u32, object: &[u8], operation: &[u8], data: &dyn CdrWrite, announce: Option<&ServiceContext>) -> Result<()> {
let mut request = Request::new(version, request_id, Target::new_key(object), operation)
.with_response_expected(true);
if let Some(announce) = announce {
request = request.with_service_contexts(slice::from_ref(announce));
}
self.write_message(writer, &Message::Request(request), Some(data))
}
fn flush(&self, sender: &mut CdrSender) -> Result<()> {
let mut stream = &self.stream;
stream.write_nb_from_cdr(sender)
.map_err(|err| Error::new(ErrorKind::Corba)
.with_description("The connection to the server failed while it was being written to")
.with_source(Box::new(err)))
}
fn receive(&self, recv: &mut RecvState) -> Result<()> {
let mut stream = &self.stream;
let (len, eof) = stream.read_nb_to_cdr(&mut recv.buf)
.map_err(|err| Error::new(ErrorKind::Corba)
.with_description("The connection to the server failed while it was being read")
.with_source(Box::new(err)))?;
trace!(addr = %self.identity.addr, len, eof, "Read from the CORBA server");
while self.dispatch(recv)? { }
if eof {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server closed the connection"));
}
Ok(())
}
fn dispatch(&self, recv: &mut RecvState) -> Result<bool> {
let mut reader = CdrReader::new(&recv.buf);
let message = reader.read::<Message>()
.map_err(|err| Error::new(ErrorKind::Corba)
.with_description("The server sent a GIOP message that could not be read")
.with_parent(err))?;
let body_at = reader.position();
let message_len = body_at + message.body_len();
let (request_id, status, body_at, endian, message) = match message {
Message::Incomplete(total_len) => {
if total_len > MAX_MESSAGE_LEN {
return Err(Error::new(ErrorKind::Corba)
.with_description(format!("The server announced a message of {total_len} octets, more than the {MAX_MESSAGE_LEN} allowed")));
}
let buf = recv.buf.make_mut();
buf.try_reserve(total_len.saturating_sub(buf.len()))?;
return Ok(false);
}
Message::Reply(reply) if !reply.fragment_follows() => {
if recv.adjacent.is_some() {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server sent a whole GIOP message in the middle of one it was sending in fragments"));
}
let (request_id, status, body_at, endian) =
(reply.request_id(), reply.status(), body_at, reply.endian());
(request_id, status, body_at, endian, recv.buf.split_to(message_len))
}
Message::Reply(reply) => {
if recv.adjacent.is_some() {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server sent a whole GIOP message in the middle of one it was sending in fragments"));
}
let request_id = reply.request_id();
let version = reply.version();
if version < Version::V12 {
recv.adjacent = Some(request_id);
}
let part = recv.buf.split_to(message_len).into_mut();
let _ = recv.fragments.insert(request_id, part);
return Ok(true);
}
Message::Fragment(frag) => {
let Some(request_id) = frag.request_id().or(recv.adjacent) else {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server sent a GIOP fragment of no message at all"));
};
let fragment_follows = frag.fragment_follows();
let data = reader.read_octet_slice(frag.body_len())
.expect("a fragment carries the octets it says it carries");
let Some(part) = recv.fragments.get_mut(&request_id) else {
return Err(Error::new(ErrorKind::Corba)
.with_description(format!("The server sent a GIOP fragment of the request {request_id}, which no message was announced for")));
};
if part.len() + data.len() > MAX_MESSAGE_LEN {
return Err(Error::new(ErrorKind::Corba)
.with_description(format!("The server sent a GIOP message of more than {MAX_MESSAGE_LEN} octets in fragments")));
}
part.extend_from_slice(&data);
let _ = recv.buf.split_to(message_len);
if fragment_follows {
return Ok(true);
}
let part = recv.fragments.remove(&request_id)
.expect("the message being put back together was just written into");
recv.adjacent = None;
let mut writer = CdrWriter::from_buf(part);
let res = writer.write(&MessageWhole);
let part = writer.into_buf().freeze();
res.map_err(|err| Error::new(ErrorKind::Corba)
.with_description("A GIOP message could not be put back together")
.with_parent(err))?;
let (request_id, status, body_at, endian) = {
let message = CdrReader::new(&part).read::<Message<'_>>()
.map_err(|err| Error::new(ErrorKind::Corba)
.with_description("The server sent a GIOP message that could not be read")
.with_parent(err))?;
let Message::Reply(reply) = message else {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server sent the fragments of a GIOP message that is not a reply"));
};
(reply.request_id(), reply.status(), part.len() - reply.body_len(), reply.endian())
};
(request_id, status, body_at, endian, part)
}
Message::MessageError(refused) => {
let _ = recv.buf.split_to(message_len);
self.downgrade(refused)?;
return Ok(true);
}
_ => {
return Err(Error::new(ErrorKind::Corba)
.with_description("The server sent a GIOP message that is not a reply"));
}
};
self.send_state.lock().unwrap().version_settled = true;
let mut request_state = self.request_state.lock().unwrap();
let Some(state) = request_state.slots.get_mut(&request_id) else {
trace!(request_id, "Dropping the reply of a request nothing is waiting for");
return Ok(true);
};
if state.reply.is_some() {
trace!(request_id, "Dropping a second reply for a request");
return Ok(true);
}
state.reply = Some(ClientReply {
body: message,
codesets: self.identity.codesets,
status,
body_at,
endian,
});
let wakers = mem::take(&mut state.wakers);
drop(request_state);
for waker in wakers {
waker.wake();
}
Ok(true)
}
fn downgrade(&self, refused: Version) -> Result<()> {
let mut send_state = self.send_state.lock().unwrap();
if refused >= send_state.version {
debug!(%refused, "Received a GIOP message error for a version there is no falling back from, ignoring");
return Ok(());
}
debug!(%refused, "The CORBA server refused the GIOP version, falling back to the one it answered in");
send_state.version = refused;
send_state.codesets_sent = false;
let mut request_state = self.request_state.lock().unwrap();
let mut void = Vec::new();
for (request_id, state) in request_state.slots.iter_mut() {
let alternate = state.alternates.iter()
.position(|(version, _)| *version == refused)
.map(|at| state.alternates.swap_remove(at).1);
state.alternates.retain(|(version, _)| *version < refused);
let Some(alternate) = alternate else {
void.push(*request_id);
continue;
};
trace!(request_id, version = %refused, "Sending a request again");
send_state.writer.interleave(alternate);
}
for request_id in void {
let Some(state) = request_state.slots.remove(&request_id) else {
continue;
};
for waker in state.wakers {
waker.wake();
}
}
drop(request_state);
self.flush(&mut send_state.writer)
}
fn fail(&self) {
self.init.set(InitState::Failed(Some(io::Error::new(io::ErrorKind::ConnectionAborted,
"the connection was given up on before its connect resolved"))));
let mut wakers = Vec::new();
{ let mut request_state = self.request_state.lock().unwrap();
request_state.lost = true;
request_state.slots.retain(|_, slot| {
if slot.reply.is_some() {
return true;
}
wakers.append(&mut slot.wakers);
false
});
}
for waker in wakers {
waker.wake();
}
}
}
#[derive(Debug)]
struct Init {
done: AtomicBool,
state: Mutex<InitState>,
}
impl Init {
fn is_done(&self) -> bool {
self.done.load(Ordering::Acquire)
}
fn set(&self, state: InitState) {
let mut guard = self.state.lock().unwrap();
let InitState::Connecting(wakers) = &mut *guard else {
return;
};
let wakers = mem::take(wakers);
*guard = state;
self.done.store(true, Ordering::Release);
drop(guard);
for waker in wakers {
waker.wake();
}
}
}
#[derive(Debug)]
enum InitState {
Connecting(Vec<task::Waker>),
Established,
Failed(Option<io::Error>),
}
struct WaitInit<'a>(&'a Connection);
impl Future for WaitInit<'_> {
type Output = Result<()>;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let addr = self.0.identity.addr;
let mut state = self.0.init.state.lock().unwrap();
let wakers = match &mut *state {
InitState::Connecting(wakers) => wakers,
InitState::Established => return task::Poll::Ready(Ok(())),
InitState::Failed(err) => return task::Poll::Ready(Err(match err.take() {
Some(err) => Error::new(ErrorKind::Corba)
.with_append_description(format_args!("Could not connect to {addr}"))
.with_source(Box::new(err)),
None => Error::new(ErrorKind::Corba)
.with_append_description(format_args!("Could not connect to {addr}, what it \
failed with having gone to the caller that asked first")),
})),
};
if !wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
wakers.push(cx.waker().clone());
}
task::Poll::Pending
}
}
struct RecvConnection {
connection: Arc<Connection>,
recv: RecvState,
}
#[derive(Debug)]
struct RecvState {
buf: Buf<u8, 1>,
fragments: HashMap<u32, BufMut<u8, 1>>,
adjacent: Option<u32>,
}
#[derive(Debug)]
struct RequestState {
slots: HashMap<u32, RequestSlot>,
lost: bool,
}
#[derive(Debug)]
struct RequestSlot {
wakers: Vec<task::Waker>,
reply: Option<ClientReply>,
alternates: Vec<(Version, Buf<u8, 1>)>,
}
#[derive(Debug)]
struct SendState {
writer: CdrSender,
version: Version,
version_settled: bool,
codesets_sent: bool,
}
pub struct ClientRequest {
state: ClientRequestState,
}
enum ClientRequestState {
Sent {
connection: Arc<Connection>,
request_id: u32,
},
Failed(Option<Error>),
}
impl Future for ClientRequest {
type Output = Result<ClientReply>;
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<Self::Output> {
let this = self.get_mut();
let (connection, request_id) = match &mut this.state {
ClientRequestState::Sent { connection, request_id } => (connection, *request_id),
ClientRequestState::Failed(err) => {
let err = err.take().unwrap_or_else(|| Error::new(ErrorKind::Corba)
.with_description("This request's failure was already given to a previous poll"));
return task::Poll::Ready(Err(err));
}
};
let mut request_state = connection.request_state.lock().unwrap();
let Some(slot) = request_state.slots.get_mut(&request_id) else {
return task::Poll::Ready(Err(Error::new(ErrorKind::Corba)
.with_description("The connection to the server failed while its reply was being waited for")));
};
let Some(reply) = slot.reply.take() else {
if !slot.wakers.iter().any(|waker| waker.will_wake(cx.waker())) {
slot.wakers.push(cx.waker().clone());
}
return task::Poll::Pending;
};
let _ = request_state.slots.remove(&request_id);
task::Poll::Ready(Ok(reply))
}
}
impl Drop for ClientRequest {
fn drop(&mut self) {
let ClientRequestState::Sent { connection, request_id } = &self.state else {
return;
};
let Some(state) = connection.request_state.lock().unwrap().slots.remove(request_id) else {
return;
};
if state.reply.is_some() {
return;
}
if let Err(err) = connection.cancel(*request_id) {
debug!(request_id, %err, "Failed to cancel the request");
}
}
}
#[derive(Debug)]
pub struct ClientReply {
body: Buf<u8, 1>,
codesets: Option<Codesets>,
status: ReplyStatus,
body_at: usize,
endian: Endian,
}
impl ClientReply {
pub fn read_at(&self, position: usize) -> CdrReader<'_> {
let mut reader = CdrReader::new(&self.body).with_endian(self.endian).with_codesets(self.codesets);
reader.set_position(position);
reader
}
pub fn read(&self) -> Result<(CdrReader<'_>, Option<Cow<'_, str>>)> {
let mut reader = self.read_at(self.body_at);
match self.status {
ReplyStatus::NoException => Ok((reader, None)),
ReplyStatus::UserException => {
let exception_id = reader.read_string()?;
Ok((reader, Some(exception_id)))
}
ReplyStatus::SystemException => {
let exception = reader.read::<SystemException<'_>>()?;
Err(Error::new(ErrorKind::Corba)
.with_description(exception.to_string()))
}
status => Err(Error::new(ErrorKind::NotHandled)
.with_description(format!("The server answered with a {status:?}, which this implementation does not follow"))),
}
}
}
#[derive(Debug)]
struct IorProfile {
object: Arc<[u8]>,
codesets: Option<Codesets>,
version: Version,
addrs: Vec<(String, u16)>,
}
fn parse_ior_profiles(ior: &str, type_id_filter: &[&str]) -> Result<Vec<IorProfile>> {
let raw_ior = ior.strip_prefix("IOR:").unwrap_or(ior);
let buf = BufMut::from_hex(raw_ior)
.ok_or_else(|| Error::new(ErrorKind::InvalidData)
.with_description("The IOR of the device is not valid hexadecimal")
.with_append_description(raw_ior))?
.freeze();
let mut reader = CdrReader::new(&buf);
reader.read_endian()?;
let ior = reader.read::<Ior>()?;
if !type_id_filter.contains(&&*ior.type_id) {
return Err(Error::new(ErrorKind::InvalidData)
.with_description(format!("Invalid type id of the IOR, got {}, expected one of {type_id_filter:?}", ior.type_id)));
}
let mut profiles = Vec::with_capacity(ior.profiles.len());
for profile in &*ior.profiles {
match profile.parse()? {
Some(ProfileKind::Iiop(iiop_service)) => {
let mut codesets = None;
let mut addrs = Vec::with_capacity(1);
addrs.push((iiop_service.host.to_string(), iiop_service.port));
for component in &*iiop_service.components {
match component.parse()? {
Some(ComponentKind::Codesets(codesets_comp)) => {
if let Some(native_codeset) = codesets_comp.parse_native_codeset()
&& let Some(native_wide_codeset) = codesets_comp.parse_native_wide_codeset() {
let conversion_codesets = codesets_comp.parse_conversion_codesets().collect::<Vec<_>>();
let conversion_wide_codesets = codesets_comp.parse_conversion_wide_codesets().collect::<Vec<_>>();
if let Some(codeset) = codeset::negociate_with_server(native_codeset, &conversion_codesets)
&& let Some(wide_codeset) = codeset::negociate_with_server(native_wide_codeset, &conversion_wide_codesets) {
trace!(?codeset, ?wide_codeset, "Negotiated the codesets of the IOR");
codesets = Some(Codesets {
codeset,
wide_codeset,
});
} else {
debug!(?native_codeset, ?native_wide_codeset, "Failed to negotiate the codesets of the IOR, using the defaults");
}
} else {
debug!("Failed to parse the codesets of the IOR, using the defaults");
}
}
Some(ComponentKind::AltIiopAddr(alt_addr_comp)) => {
let host_and_port = (alt_addr_comp.host.to_string(), alt_addr_comp.port);
if !addrs.contains(&host_and_port) {
addrs.push(host_and_port);
}
}
Some(_) | None => {}
}
}
profiles.push(IorProfile {
object: iiop_service.object_key.to_vec().into(),
codesets,
version: iiop_service.version,
addrs,
});
}
Some(_) | None => {}
}
}
if profiles.is_empty() {
return Err(Error::new(ErrorKind::InvalidData)
.with_description("Could not find an IIOP profile to connect to in the given IOR"));
}
Ok(profiles)
}
#[cfg(test)]
mod tests {
use std::net::TcpListener;
use crate::future::block_on_timeout;
use super::*;
const TIMEOUT: Duration = Duration::from_secs(5);
#[test]
fn connection_lost() {
let client = Client::new().unwrap();
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let object = block_on_timeout(client.connect_object_iiop("127.0.0.1", port, None, None, Arc::from(&b"object"[..])), TIMEOUT)
.unwrap()
.unwrap();
drop(listener.accept().unwrap());
let err = block_on_timeout(object.request(b"_non_existent", &()), TIMEOUT).unwrap().unwrap_err();
assert_eq!(err.kind(), ErrorKind::Corba);
let err = block_on_timeout(object.request(b"_non_existent", &()), TIMEOUT).unwrap().unwrap_err();
assert_eq!(err.kind(), ErrorKind::Corba);
}
}