use zenoh::handlers::FifoChannelHandler;
use zenoh::key_expr::OwnedKeyExpr;
use zenoh::query::{Query as ZenohQuery, Queryable};
use crate::bus::abi::{CodecId, EncodingMetadata};
use crate::bus::error::{BusError, MetadataProblem, Result};
use crate::bus::metadata::BusMetadata;
use crate::bus::query::QueryFailure;
use crate::bus::session::BusHandle;
pub struct ServerQueryable {
inner: Queryable<FifoChannelHandler<ZenohQuery>>,
topic_key: String,
}
impl ServerQueryable {
pub async fn recv(&self) -> Result<IncomingQuery> {
let query = self
.inner
.recv_async()
.await
.map_err(|_| BusError::Closed)?;
Ok(IncomingQuery {
query,
topic_key: self.topic_key.clone(),
})
}
pub fn topic_key(&self) -> &str {
&self.topic_key
}
}
pub struct IncomingQuery {
query: ZenohQuery,
topic_key: String,
}
impl IncomingQuery {
pub fn topic_key(&self) -> &str {
&self.topic_key
}
pub fn request_bytes(&self) -> Result<Vec<u8>> {
let payload = self
.query
.payload()
.ok_or_else(|| self.malformed(MetadataProblem::MissingPayload))?;
Ok(payload.to_bytes().to_vec())
}
fn malformed(&self, problem: MetadataProblem) -> BusError {
BusError::metadata(&self.topic_key, problem)
}
pub fn request_metadata(&self) -> Result<BusMetadata> {
let encoding = self
.query
.encoding()
.ok_or_else(|| self.malformed(MetadataProblem::MissingEncoding))?;
let encoding: EncodingMetadata = encoding
.to_string()
.parse()
.map_err(|e: crate::bus::abi::EncodingError| self.malformed(e.into()))?;
if encoding.codec_id() != Some(CodecId::MessagePack) {
return Err(BusError::UnsupportedCodec {
codec: encoding.codec,
topic: self.topic_key.clone(),
});
}
let attachment = self
.query
.attachment()
.ok_or_else(|| self.malformed(MetadataProblem::MissingAttachment))?;
let metadata = BusMetadata::decode(attachment.to_bytes().as_ref())
.map_err(|e| self.malformed(e.into()))?;
if metadata.codec != encoding.codec {
return Err(self.malformed(MetadataProblem::CodecMismatch {
encoding: encoding.codec,
attachment: metadata.codec,
}));
}
Ok(metadata)
}
pub async fn reply(&self, bus: &BusHandle, payload: Vec<u8>) -> Result<()> {
let metadata = bus.metadata(None)?;
let attachment = metadata
.encode()
.map_err(|e| self.malformed(MetadataProblem::Encode(e)))?;
self.query
.reply(self.query.key_expr(), payload)
.encoding(CodecId::MessagePack.encoding_string())
.attachment(attachment)
.await
.map_err(|e| BusError::Transport(e.to_string()))
}
pub async fn reply_err(&self, failure: &QueryFailure) -> Result<()> {
let payload = failure
.encode()
.map_err(|e| BusError::Transport(format!("failed to encode a query failure: {e}")))?;
self.query
.reply_err(payload)
.await
.map_err(|e| BusError::Transport(e.to_string()))
}
}
impl BusHandle {
pub async fn declare_server(&self, topic_key: &str) -> Result<ServerQueryable> {
let full_key = self.full_key(topic_key);
let key = OwnedKeyExpr::new(full_key.clone())
.map_err(|e| BusError::not_a_key_expression(&full_key, e))?;
let inner = self
.session()?
.declare_queryable(key)
.complete(true)
.await
.map_err(|e| BusError::Transport(e.to_string()))?;
Ok(ServerQueryable {
inner,
topic_key: topic_key.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use serial_test::serial;
use zenoh::bytes::Encoding;
use crate::bus::session::BusOwner;
use crate::bus::test_support::{GetRequest, metadata, participant_config};
#[serial]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn incoming_query_rejects_encoding_attachment_codec_mismatch() {
let (owner, bus) = BusOwner::open(participant_config("q-mismatch"))
.await
.unwrap();
let server = bus.declare_server("yTEST/asset/get").await.unwrap();
let request = GetRequest {
path: "asset.bin".to_string(),
};
let payload = rmp_serde::to_vec_named(&request).unwrap();
let mut meta = metadata();
meta.codec = CodecId::MessagePack.as_u8();
let key = OwnedKeyExpr::new(bus.full_key("yTEST/asset/get")).unwrap();
let _replies = bus
.session()
.unwrap()
.get(key)
.payload(payload)
.encoding(Encoding::from("phoxal/v0;codec=99".to_string()))
.attachment(meta.encode().expect("test metadata encodes"))
.target(zenoh::query::QueryTarget::All)
.consolidation(zenoh::query::ConsolidationMode::None)
.await
.unwrap();
let incoming = tokio::time::timeout(Duration::from_secs(5), server.recv())
.await
.expect("the query must reach the server")
.unwrap();
let error = incoming.request_metadata().unwrap_err();
match error {
BusError::UnsupportedCodec { codec: 99, .. } => {}
other => panic!("expected unsupported codec 99, got {other:?}"),
}
owner.close().await;
}
}