zerodds_discovery/type_lookup/
mod.rs1pub mod client;
26pub mod endpoints;
27pub mod server;
28
29pub use client::{
30 ClientCallback, RequestId, TypeLookupClient, TypeLookupReply, hashes_to_minimal_ids,
31 request_dependencies_payload, request_types_payload,
32};
33pub use endpoints::{
34 TYPELOOKUP_TOPIC_PREFIX, TypeLookupEndpoints, format_service_instance_name,
35 format_service_instance_name_short,
36};
37pub use server::TypeLookupServer;
38
39use alloc::vec::Vec;
40
41use zerodds_cdr::{BufferReader, BufferWriter, EncodeError, Endianness};
42use zerodds_rtps::wire_types::{EntityId, Guid, GuidPrefix};
43use zerodds_types::error::TypeCodecError;
44use zerodds_types::resolve::TypeRegistry;
45use zerodds_types::type_lookup::{
46 ContinuationPoint, GetTypeDependenciesReply, GetTypeDependenciesRequest, GetTypesReply,
47 GetTypesRequest, ReplyTypeObject,
48};
49use zerodds_types::type_object::TypeObject;
50use zerodds_types::{EquivalenceHash, TypeIdentifier};
51
52#[derive(Debug)]
54pub struct TypeLookupStack {
55 pub local_prefix: GuidPrefix,
57 pub registry: TypeRegistry,
59 next_request_seq: u64,
61}
62
63impl TypeLookupStack {
64 #[must_use]
66 pub fn new(local_prefix: GuidPrefix) -> Self {
67 Self {
68 local_prefix,
69 registry: TypeRegistry::new(),
70 next_request_seq: 1,
71 }
72 }
73
74 #[must_use]
76 pub fn request_writer_guid(&self) -> Guid {
77 Guid::new(self.local_prefix, EntityId::TL_SVC_REQ_WRITER)
78 }
79
80 #[must_use]
82 pub fn reply_reader_guid(&self) -> Guid {
83 Guid::new(self.local_prefix, EntityId::TL_SVC_REPLY_READER)
84 }
85
86 pub fn make_get_types_request(
92 &mut self,
93 hashes: &[EquivalenceHash],
94 minimal: bool,
95 ) -> Result<(Vec<u8>, u64), EncodeError> {
96 let seq = self.next_request_seq;
97 self.next_request_seq = self.next_request_seq.saturating_add(1);
98
99 let type_ids: Vec<TypeIdentifier> = hashes
100 .iter()
101 .map(|h| {
102 if minimal {
103 TypeIdentifier::EquivalenceHashMinimal(*h)
104 } else {
105 TypeIdentifier::EquivalenceHashComplete(*h)
106 }
107 })
108 .collect();
109 let req = GetTypesRequest { type_ids };
110 let mut w = BufferWriter::new(Endianness::Little);
111 req.encode_into(&mut w)?;
112 Ok((w.into_bytes(), seq))
113 }
114
115 pub fn handle_get_types_reply(&mut self, bytes: &[u8]) -> Result<usize, TypeCodecError> {
121 let mut r = BufferReader::new(bytes, Endianness::Little);
122 let reply = GetTypesReply::decode_from(&mut r)?;
123 let mut count = 0;
124 for item in reply.types {
125 match item {
126 ReplyTypeObject::Minimal(m) => {
127 let hash = zerodds_types::compute_hash(&TypeObject::Minimal(m.clone()))?;
128 self.registry.insert_minimal(hash, m);
129 count += 1;
130 }
131 ReplyTypeObject::Complete(c) => {
132 let hash = zerodds_types::compute_hash(&TypeObject::Complete(c.clone()))?;
133 self.registry.insert_complete(hash, c);
134 count += 1;
135 }
136 }
137 }
138 Ok(count)
139 }
140
141 pub fn make_get_dependencies_request(
146 &self,
147 hashes: &[EquivalenceHash],
148 cont: ContinuationPoint,
149 ) -> Result<Vec<u8>, EncodeError> {
150 let req = GetTypeDependenciesRequest {
151 type_ids: hashes
152 .iter()
153 .map(|h| TypeIdentifier::EquivalenceHashMinimal(*h))
154 .collect(),
155 continuation_point: cont,
156 };
157 let mut w = BufferWriter::new(Endianness::Little);
158 req.encode_into(&mut w)?;
159 Ok(w.into_bytes())
160 }
161
162 pub fn parse_dependencies_reply(
167 &self,
168 bytes: &[u8],
169 ) -> Result<GetTypeDependenciesReply, TypeCodecError> {
170 let mut r = BufferReader::new(bytes, Endianness::Little);
171 GetTypeDependenciesReply::decode_from(&mut r)
172 }
173
174 pub fn build_get_types_reply(
180 &self,
181 hashes: &[EquivalenceHash],
182 minimal: bool,
183 ) -> Result<Vec<u8>, EncodeError> {
184 let mut types = Vec::new();
185 for h in hashes {
186 if minimal {
187 if let Some(m) = self.registry.get_minimal(h) {
188 types.push(ReplyTypeObject::Minimal(m.clone()));
189 }
190 } else if let Some(c) = self.registry.get_complete(h) {
191 types.push(ReplyTypeObject::Complete(c.clone()));
192 }
193 }
194 let reply = GetTypesReply { types };
195 let mut w = BufferWriter::new(Endianness::Little);
196 reply.encode_into(&mut w)?;
197 Ok(w.into_bytes())
198 }
199}
200
201#[cfg(test)]
202#[allow(clippy::unwrap_used)]
203mod tests {
204 use super::*;
205 use zerodds_types::builder::TypeObjectBuilder;
206 use zerodds_types::{MinimalTypeObject, PrimitiveKind};
207
208 fn sample_struct() -> MinimalTypeObject {
209 MinimalTypeObject::Struct(
210 TypeObjectBuilder::struct_type("::X")
211 .member("a", TypeIdentifier::Primitive(PrimitiveKind::Int64), |m| m)
212 .build_minimal(),
213 )
214 }
215
216 #[test]
217 fn make_and_parse_get_types_roundtrip() {
218 let mut stack = TypeLookupStack::new(GuidPrefix::from_bytes([1; 12]));
219 let hash = zerodds_types::compute_minimal_hash(&sample_struct()).unwrap();
220 let (bytes, seq) = stack.make_get_types_request(&[hash], true).unwrap();
221 assert_eq!(seq, 1);
222 let mut r = BufferReader::new(&bytes, Endianness::Little);
223 let decoded = GetTypesRequest::decode_from(&mut r).unwrap();
224 assert_eq!(decoded.type_ids.len(), 1);
225 }
226
227 #[test]
228 fn responder_round_trip_via_registry() {
229 let mut responder = TypeLookupStack::new(GuidPrefix::from_bytes([2; 12]));
230 let m = sample_struct();
231 let hash = zerodds_types::compute_minimal_hash(&m).unwrap();
232 responder.registry.insert_minimal(hash, m);
233
234 let reply_bytes = responder.build_get_types_reply(&[hash], true).unwrap();
235
236 let mut requester = TypeLookupStack::new(GuidPrefix::from_bytes([3; 12]));
237 let n = requester.handle_get_types_reply(&reply_bytes).unwrap();
238 assert_eq!(n, 1);
239 assert!(requester.registry.get_minimal(&hash).is_some());
240 }
241
242 #[test]
243 fn request_seq_increments() {
244 let mut s = TypeLookupStack::new(GuidPrefix::from_bytes([0; 12]));
245 let (_, s1) = s.make_get_types_request(&[], true).unwrap();
246 let (_, s2) = s.make_get_types_request(&[], true).unwrap();
247 assert_eq!(s1, 1);
248 assert_eq!(s2, 2);
249 }
250}