1use std::{
3 cell::RefCell,
4 ops::{Deref, DerefMut},
5 sync::{
6 atomic::{AtomicU8, Ordering},
7 Arc, Mutex, PoisonError,
8 },
9};
10
11use kmip_ttlv::{error::ErrorKind, Config, PrettyPrinter};
12use log::trace;
13
14use crate::{
15 auth::{self, CredentialType},
16 request::to_vec,
17 tag_map,
18 types::{common::*, request, request::*, response::*, traits::*},
19};
20
21#[non_exhaustive]
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum Error {
25 ConfigurationError(String),
26 SerializeError(String),
27 RequestWriteError(String),
28 ResponseReadError(String),
29 DeserializeError(String),
30 ServerError(String),
31 InternalError(String),
32 ItemNotFound(String),
33 Unknown(String),
34}
35
36impl Error {
37 pub fn is_connection_error(&self) -> bool {
39 use Error::*;
40 matches!(self, RequestWriteError(_) | ResponseReadError(_))
41 }
42}
43
44impl std::error::Error for Error {}
45
46impl From<std::io::Error> for Error {
47 fn from(err: std::io::Error) -> Self {
48 Error::ServerError(format!("I/O error: {}", err))
49 }
50}
51
52impl std::fmt::Display for Error {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Error::ConfigurationError(e) => f.write_fmt(format_args!("Configuration error: {}", e)),
56 Error::SerializeError(e) => f.write_fmt(format_args!("Serialize error: {}", e)),
57 Error::RequestWriteError(e) => f.write_fmt(format_args!("Request send error: {}", e)),
58 Error::ResponseReadError(e) => f.write_fmt(format_args!("Response read error: {}", e)),
59 Error::DeserializeError(e) => f.write_fmt(format_args!("Deserialize error: {}", e)),
60 Error::ServerError(e) => f.write_fmt(format_args!("Server error: {}", e)),
61 Error::InternalError(e) => f.write_fmt(format_args!("Internal error: {}", e)),
62 Error::ItemNotFound(e) => f.write_fmt(format_args!("Item not found: {}", e)),
63 Error::Unknown(e) => f.write_fmt(format_args!("Unknown error: {}", e)),
64 }
65 }
66}
67
68pub type Result<T> = std::result::Result<T, Error>;
70
71impl<T> From<PoisonError<T>> for Error {
72 fn from(err: PoisonError<T>) -> Self {
73 Error::InternalError(err.to_string())
74 }
75}
76
77#[derive(Debug)]
79pub struct ClientBuilder<T> {
80 username: Option<String>,
81 password: Option<String>,
82 stream: T,
83 reader_config: Config,
84}
85
86impl<T> ClientBuilder<T> {
87 pub fn new(stream: T) -> Self {
97 Self {
98 username: None,
99 password: None,
100 stream,
101 reader_config: Config::default(),
102 }
103 }
104
105 pub fn with_credentials(mut self, username: String, password: Option<String>) -> Self {
107 self.username = Some(username);
108 self.password = password;
109 self
110 }
111
112 pub fn with_reader_config(mut self, reader_config: Config) -> Self {
114 self.reader_config = reader_config;
115 self
116 }
117
118 pub fn build(self) -> Client<T> {
120 let mut pretty_printer = PrettyPrinter::new();
121 pretty_printer.with_tag_prefix("4200".into());
122 pretty_printer.with_tag_map(tag_map::make_kmip_tag_map());
123
124 Client {
125 username: self.username,
126 password: self.password,
127 stream: Arc::new(Mutex::new(self.stream)),
128 reader_config: self.reader_config,
129 connection_error_count: AtomicU8::new(0),
130 last_req_diag_str: RefCell::new(None),
131 last_res_diag_str: RefCell::new(None),
132 pretty_printer,
133 }
134 }
135}
136
137macro_rules! get_response_payload_for_type {
139 ($response:expr, $response_type:path) => {{
140 if let $response_type(payload) = $response {
142 Ok(payload)
143 } else {
144 Err(Error::InternalError(format!(
145 "Expected {} response payload but got: {:?}",
146 stringify!($response_type),
147 $response
148 )))
149 }
150 }};
151}
152
153#[derive(Debug)]
157pub struct Client<T> {
158 username: Option<String>,
159 password: Option<String>,
160 stream: Arc<Mutex<T>>,
161 reader_config: Config,
162 connection_error_count: AtomicU8,
163 last_req_diag_str: RefCell<Option<String>>,
164 last_res_diag_str: RefCell<Option<String>>,
165 pretty_printer: PrettyPrinter,
166}
167
168impl<T: ReadWrite> Client<T> {
169 pub fn inner(&self) -> Arc<Mutex<T>> {
170 self.stream.clone()
171 }
172
173 #[maybe_async::maybe_async]
175 async fn send_and_receive(
176 &self,
177 operation: Operation,
178 reader_config: &Config,
179 req_bytes: &[u8],
180 stream: Arc<Mutex<T>>,
181 ) -> Result<ResponsePayload> {
182 let mut lock = stream.lock()?;
183 let stream = lock.deref_mut();
184
185 stream
186 .write_all(req_bytes)
187 .await
188 .map_err(|e| Error::RequestWriteError(e.to_string()))?;
189
190 let mut res: ResponseMessage = kmip_ttlv::from_reader(stream, reader_config)
192 .await
193 .map_err(|err| match err.kind() {
194 ErrorKind::IoError(e) => Error::ResponseReadError(e.to_string()),
195 ErrorKind::ResponseSizeExceedsLimit(_) | ErrorKind::MalformedTtlv(_) => {
196 Error::DeserializeError(err.to_string())
197 }
198 _ => Error::InternalError(err.to_string()),
199 })?;
200
201 if res.header.batch_count == 1 && res.batch_items.len() == 1 {
202 let item = &mut res.batch_items[0];
203
204 match item.result_status {
205 ResultStatus::OperationFailed => {
206 if matches!(item.result_reason, Some(ResultReason::ItemNotFound)) {
207 Err(Error::ItemNotFound(format!(
208 "Operation {:?} failed: {}",
209 operation,
210 item.result_message.as_ref().unwrap_or(&String::new()).clone()
211 )))
212 } else {
213 Err(Error::ServerError(format!(
214 "Operation {:?} failed: {}",
215 operation,
216 item.result_message.as_ref().unwrap_or(&String::new()).clone()
217 )))
218 }
219 }
220 ResultStatus::OperationPending => Err(Error::InternalError(
221 "Result status 'operation pending' is not supported".into(),
222 )),
223 ResultStatus::OperationUndone => Err(Error::InternalError(
224 "Result status 'operation undone' is not supported".into(),
225 )),
226 ResultStatus::Success => {
227 if item.operation == Some(operation) {
228 if let Some(payload) = item.payload.take() {
229 Ok(payload)
230 } else {
231 Err(Error::InternalError(
232 "Unable to process response payload due to wrong deserialized type!".into(),
233 ))
234 }
235 } else {
236 Err(Error::InternalError(format!(
237 "Response operation {:?} does not match request operation {}",
238 item.operation, operation
239 )))
240 }
241 }
242 }
243 } else {
244 Err(Error::ServerError(format!(
245 "Expected one batch item in response but received {}",
246 res.batch_items.len()
247 )))
248 }
249 }
250
251 #[maybe_async::maybe_async]
267 pub async fn do_request(&self, payload: RequestPayload) -> Result<ResponsePayload> {
268 *self.last_req_diag_str.borrow_mut() = None;
270 *self.last_res_diag_str.borrow_mut() = None;
271
272 let operation = payload.operation();
275
276 let req_bytes = to_vec(payload, self.auth()).map_err(|err| match err.kind() {
278 ErrorKind::IoError(e) => Error::SerializeError(e.to_string()),
279 _ => Error::InternalError(err.to_string()),
280 })?;
281
282 if self.reader_config.has_buf() {
285 let diag_str = self.pretty_printer.to_diag_string(&req_bytes);
286 trace!("KMIP TTLV request: {}", &diag_str);
287 self.last_req_diag_str.borrow_mut().replace(diag_str);
288 }
289
290 let incr_err_count = |err: Error| {
292 if err.is_connection_error() {
293 let _ = self.connection_error_count.fetch_add(1, Ordering::SeqCst);
294 }
295 Err(err)
296 };
297
298 let res = self
300 .send_and_receive(operation, &self.reader_config, &req_bytes, self.stream.clone())
301 .await
302 .or_else(incr_err_count);
303
304 if let Some(buf) = self.reader_config.read_buf() {
307 let diag_str = self.pretty_printer.to_diag_string(&buf);
308 trace!("KMIP TTLV response: {}", &diag_str);
309 self.last_res_diag_str.borrow_mut().replace(diag_str);
310 }
311
312 res
313 }
314
315 #[maybe_async::maybe_async]
319 pub async fn query(&self) -> Result<QueryResponsePayload> {
320 let wanted_info = vec![
322 QueryFunction::QueryOperations,
323 QueryFunction::QueryObjects,
324 QueryFunction::QueryServerInformation,
325 ];
326 let request = RequestPayload::Query(wanted_info);
327
328 let response = self.do_request(request).await?;
330
331 get_response_payload_for_type!(response, ResponsePayload::Query)
333 }
334
335 #[maybe_async::maybe_async]
344 pub async fn create_rsa_key_pair(
345 &self,
346 key_length: i32,
347 private_key_name: String,
348 public_key_name: String,
349 ) -> Result<(String, String)> {
350 let request = RequestPayload::CreateKeyPair(
352 Some(CommonTemplateAttribute::unnamed(vec![
353 request::Attribute::CryptographicAlgorithm(CryptographicAlgorithm::RSA),
354 request::Attribute::CryptographicLength(key_length),
355 ])),
356 Some(PrivateKeyTemplateAttribute::unnamed(vec![
357 request::Attribute::Name(private_key_name),
358 request::Attribute::CryptographicUsageMask(CryptographicUsageMask::Sign),
359 ])),
360 Some(PublicKeyTemplateAttribute::unnamed(vec![
361 request::Attribute::Name(public_key_name),
362 request::Attribute::CryptographicUsageMask(CryptographicUsageMask::Verify),
363 ])),
364 );
365
366 let response = self.do_request(request).await?;
368
369 get_response_payload_for_type!(response, ResponsePayload::CreateKeyPair).map(|payload| {
371 (
372 payload.private_key_unique_identifier.deref().clone(),
373 payload.public_key_unique_identifier.deref().clone(),
374 )
375 })
376 }
377
378 #[maybe_async::maybe_async]
384 pub async fn rng_retrieve(&self, num_bytes: i32) -> Result<RNGRetrieveResponsePayload> {
385 let request = RequestPayload::RNGRetrieve(DataLength(num_bytes));
386
387 let response = self.do_request(request).await?;
389
390 get_response_payload_for_type!(response, ResponsePayload::RNGRetrieve)
392 }
393
394 #[maybe_async::maybe_async]
400 pub async fn sign(&self, private_key_id: &str, in_bytes: &[u8]) -> Result<SignResponsePayload> {
401 let request = RequestPayload::Sign(
402 Some(UniqueIdentifier(private_key_id.to_owned())),
403 Some(
404 CryptographicParameters::default()
405 .with_padding_method(PaddingMethod::PKCS1_v1_5)
406 .with_hashing_algorithm(HashingAlgorithm::SHA256)
407 .with_cryptographic_algorithm(CryptographicAlgorithm::RSA),
408 ),
409 Data(in_bytes.to_vec()),
410 );
411
412 let response = self.do_request(request).await?;
414
415 get_response_payload_for_type!(response, ResponsePayload::Sign)
416 }
417
418 #[maybe_async::maybe_async]
426 pub async fn activate_key(&self, private_key_id: &str) -> Result<()> {
427 let request = RequestPayload::Activate(Some(UniqueIdentifier(private_key_id.to_owned())));
428
429 let response = self.do_request(request).await?;
431
432 get_response_payload_for_type!(response, ResponsePayload::Activate).map(|_| ())
434 }
435
436 #[maybe_async::maybe_async]
444 pub async fn revoke_key(&self, private_key_id: &str) -> Result<()> {
445 let request = RequestPayload::Revoke(
446 Some(UniqueIdentifier(private_key_id.to_owned())),
447 RevocationReason(
448 RevocationReasonCode::CessationOfOperation,
449 Option::<RevocationMessage>::None,
450 ),
451 Option::<CompromiseOccurrenceDate>::None,
452 );
453
454 let response = self.do_request(request).await?;
456
457 get_response_payload_for_type!(response, ResponsePayload::Revoke).map(|_| ())
459 }
460
461 #[maybe_async::maybe_async]
469 pub async fn destroy_key(&self, key_id: &str) -> Result<()> {
470 let request = RequestPayload::Destroy(Some(UniqueIdentifier(key_id.to_owned())));
471
472 let response = self.do_request(request).await?;
474
475 get_response_payload_for_type!(response, ResponsePayload::Destroy).map(|_| ())
477 }
478
479 #[maybe_async::maybe_async]
487 pub async fn rename_key(&self, key_id: &str, new_name: String) -> Result<ModifyAttributeResponsePayload> {
488 let request = RequestPayload::ModifyAttribute(
490 Some(UniqueIdentifier(key_id.to_string())),
491 request::Attribute::Name(new_name),
492 );
493
494 let response = self.do_request(request).await?;
496
497 get_response_payload_for_type!(response, ResponsePayload::ModifyAttribute)
499 }
500
501 #[maybe_async::maybe_async]
506 pub async fn get_key(&self, key_id: &str) -> Result<GetResponsePayload> {
507 let request = RequestPayload::Get(
509 Some(UniqueIdentifier(key_id.to_string())),
510 Option::<KeyFormatType>::None,
511 Option::<KeyCompressionType>::None,
512 Option::<KeyWrappingSpecification>::None,
513 );
514
515 let response = self.do_request(request).await?;
517
518 get_response_payload_for_type!(response, ResponsePayload::Get)
520 }
521}
522
523impl<T> Clone for Client<T> {
524 fn clone(&self) -> Self {
525 Self {
526 username: self.username.clone(),
527 password: self.password.clone(),
528 stream: self.stream.clone(),
529 reader_config: self.reader_config.clone(),
530 connection_error_count: AtomicU8::new(self.connection_error_count()),
531 last_req_diag_str: self.last_req_diag_str.clone(),
532 last_res_diag_str: self.last_res_diag_str.clone(),
533 pretty_printer: self.pretty_printer.clone(),
534 }
535 }
536}
537
538impl<T> Client<T> {
539 fn auth(&self) -> Option<CredentialType> {
540 if self.username.is_some() && self.password.is_some() {
541 Some(CredentialType::UsernameAndPassword(
542 auth::UsernameAndPasswordCredential::new(self.username.clone().unwrap(), self.password.clone()),
543 ))
544 } else {
545 None
546 }
547 }
548
549 pub fn last_req_diag_str(&self) -> Option<String> {
551 self.last_req_diag_str.borrow().to_owned()
552 }
553
554 pub fn last_res_diag_str(&self) -> Option<String> {
556 self.last_res_diag_str.borrow().to_owned()
557 }
558
559 pub fn connection_error_count(&self) -> u8 {
561 self.connection_error_count.load(Ordering::SeqCst)
562 }
563}
564
565#[cfg(test)]
566mod test {
567 use std::{
568 io::{Cursor, Read, Write},
569 net::TcpStream,
570 };
571
572 use kmip_ttlv::Config;
573
574 #[cfg(feature = "tls-with-openssl")]
575 use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
576
577 use crate::{
578 client::ClientBuilder,
579 types::{
580 request::{QueryFunction, RequestPayload},
581 response::ResponsePayload,
582 },
583 };
584
585 struct MockStream {
586 pub response: Cursor<Vec<u8>>,
587 }
588
589 impl Write for MockStream {
590 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
591 std::io::sink().write(buf)
592 }
593
594 fn flush(&mut self) -> std::io::Result<()> {
595 Ok(())
596 }
597 }
598
599 impl Read for MockStream {
600 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
601 self.response.read(buf)
602 }
603 }
604
605 #[test]
606 fn test_query() {
607 let response_hex = concat!(
608 "42007B010000023042007A0100000048420069010000002042006A0200000004000000010000000042006B02000000040",
609 "0000000000000004200920900000008000000004B7918AA42000D0200000004000000010000000042000F01000001D842",
610 "005C0500000004000000180000000042007F0500000004000000000000000042007C01000001B042005C0500000004000",
611 "000010000000042005C0500000004000000020000000042005C0500000004000000030000000042005C05000000040000",
612 "00040000000042005C0500000004000000080000000042005C0500000004000000090000000042005C050000000400000",
613 "00A0000000042005C05000000040000000B0000000042005C05000000040000000C0000000042005C0500000004000000",
614 "0D0000000042005C05000000040000000E0000000042005C05000000040000000F0000000042005C05000000040000001",
615 "00000000042005C0500000004000000110000000042005C0500000004000000120000000042005C050000000400000013",
616 "0000000042005C0500000004000000140000000042005C0500000004000000150000000042005C0500000004000000160",
617 "000000042005C0500000004000000180000000042005C0500000004000000190000000042005C05000000040000001A00",
618 "0000004200570500000004000000010000000042005705000000040000000200000000420057050000000400000003000",
619 "000004200570500000004000000040000000042005705000000040000000600000000"
620 );
621 let response_bytes = hex::decode(response_hex).unwrap();
622
623 let mut stream = MockStream {
624 response: Cursor::new(response_bytes),
625 };
626
627 let client = ClientBuilder::new(&mut stream).build();
628
629 let response_payload = client.query().unwrap();
630
631 dbg!(response_payload);
632 }
633
634 #[test]
635 fn test_create_rsa_key_pair() {
636 let response_hex = concat!(
637 "42007B01000000E042007A0100000048420069010000002042006A0200000004000000010000000042006B02000000040",
638 "0000000000000004200920900000008000000004B73C13A42000D0200000004000000010000000042000F010000008842",
639 "005C0500000004000000020000000042007F0500000004000000000000000042007C01000000604200940700000024383",
640 "93566373263322D623230612D343964382D393530342D3664633231313563633034320000000042009407000000246132",
641 "3432666361342D656266302D343339382D616336352D38373962616234393032353900000000"
642 );
643 let response_bytes = hex::decode(response_hex).unwrap();
644
645 let mut stream = MockStream {
646 response: Cursor::new(response_bytes),
647 };
648
649 let client = ClientBuilder::new(&mut stream).build();
650
651 let response_payload = client
652 .create_rsa_key_pair(1024, "My Private Key".into(), "My Public Key".into())
653 .unwrap();
654
655 dbg!(response_payload);
656 }
657
658 #[cfg(feature = "tls-with-openssl")]
659 #[test]
660 #[ignore = "Requires a running PyKMIP instance"]
661 fn test_pykmip_query_against_server_with_openssl() {
662 let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
663 connector.set_verify(SslVerifyMode::NONE);
664 connector
665 .set_certificate_file("/etc/pykmip/server.crt", SslFiletype::PEM)
666 .unwrap();
667 connector
668 .set_private_key_file("/etc/pykmip/server.key", SslFiletype::PEM)
669 .unwrap();
670 let connector = connector.build();
671 let stream = TcpStream::connect("localhost:5696").unwrap();
672 let mut tls = connector.connect("localhost", stream).unwrap();
673
674 let client = ClientBuilder::new(&mut tls)
675 .with_reader_config(Config::default().with_max_bytes(64 * 1024))
676 .build();
677
678 let response_payload = client.query().unwrap();
679
680 dbg!(response_payload);
681 }
682
683 #[cfg(feature = "tls-with-rustls")]
684 #[test]
685 #[ignore = "Requires a running PyKMIP instance"]
686 fn test_pykmip_query_against_server_with_rustls() {
687 fn load_binary_file(path: &'static str) -> std::io::Result<Vec<u8>> {
766 let mut buf = Vec::new();
767 std::fs::File::open(path)?.read_to_end(&mut buf)?;
768 Ok(buf)
769 }
770
771 fn bytes_to_cert_chain(bytes: &[u8]) -> std::io::Result<Vec<rustls::Certificate>> {
772 let cert_chain = rustls_pemfile::read_all(&mut BufReader::new(bytes))?
773 .iter()
774 .map(|i: &rustls_pemfile::Item| match i {
775 rustls_pemfile::Item::X509Certificate(bytes) => rustls::Certificate(bytes.clone()),
776 rustls_pemfile::Item::RSAKey(_) => panic!("Expected an X509 certificate, got an RSA key"),
777 rustls_pemfile::Item::PKCS8Key(_) => panic!("Expected an X509 certificate, got a PKCS8 key"),
778 })
779 .collect();
780 Ok(cert_chain)
781 }
782
783 fn bytes_to_private_key(bytes: &[u8]) -> std::io::Result<rustls::PrivateKey> {
784 let private_key = rustls_pemfile::read_one(&mut BufReader::new(bytes))?
785 .map(|i: rustls_pemfile::Item| match i {
786 rustls_pemfile::Item::X509Certificate(_) => panic!("Expected a PKCS8 key, got an X509 certificate"),
787 rustls_pemfile::Item::RSAKey(_) => panic!("Expected a PKCS8 key, got an RSA key"),
788 rustls_pemfile::Item::PKCS8Key(bytes) => rustls::PrivateKey(bytes.clone()),
789 })
790 .unwrap();
791 Ok(private_key)
792 }
793
794 let ca_cert_pem = load_binary_file("/etc/pykmip/ca.crt").unwrap();
796 let server_cert_pem = load_binary_file("/etc/pykmip/server.crt").unwrap();
797 let server_key_pem = load_binary_file("/etc/pykmip/server.key").unwrap();
798
799 let mut config = rustls::ClientConfig::new();
800 config
801 .root_store
802 .add_pem_file(&mut BufReader::new(ca_cert_pem.as_slice()))
803 .unwrap();
804 config
805 .root_store
806 .add_pem_file(&mut BufReader::new(server_cert_pem.as_slice()))
807 .unwrap();
808
809 let cert_chain = bytes_to_cert_chain(&server_cert_pem).unwrap();
810 let key_der = bytes_to_private_key(&server_key_pem).unwrap();
811 config.set_single_client_cert(cert_chain, key_der).unwrap();
812
813 let rc_config = Arc::new(config);
814 let localhost = webpki::DNSNameRef::try_from_ascii_str("localhost").unwrap();
815 let mut sess = rustls::ClientSession::new(&rc_config, localhost);
816 let mut stream = TcpStream::connect("localhost:5696").unwrap();
817 let mut tls = rustls::Stream::new(&mut sess, &mut stream);
818
819 let client = ClientBuilder::new(&mut tls)
820 .with_reader_config(Config::default().with_max_bytes(64 * 1024))
821 .build();
822
823 let response_payload = client.query().unwrap();
824
825 dbg!(response_payload);
826 }
827
828 #[test]
829 #[ignore = "Requires a running Kryptus instance"]
830 fn test_kryptus_query_against_server() {
831 let mut connector = SslConnector::builder(SslMethod::tls()).unwrap();
832 connector.set_verify(SslVerifyMode::NONE);
833 let connector = connector.build();
834 let host = std::env::var("KRYPTUS_HOST").unwrap();
835 let port = std::env::var("KRYPTUS_PORT").unwrap();
836 let stream = TcpStream::connect(format!("{}:{}", host, port)).unwrap();
837 let mut tls = connector.connect(&host, stream).unwrap();
838
839 let client = ClientBuilder::new(&mut tls)
840 .with_credentials(
841 std::env::var("KRYPTUS_USER").unwrap(),
842 Some(std::env::var("KRYPTUS_PASS").unwrap()),
843 )
844 .with_reader_config(Config::default().with_max_bytes(64 * 1024))
845 .build();
846
847 let response_payload = client.query().unwrap();
848
849 dbg!(response_payload);
850 }
851
852 #[test]
853 fn test_pykmip_query_response() {
854 let response_hex = concat!(
855 "42007b010000014042007a0100000048420069010000002042006a0200000004000000010000000042006b02000000040",
856 "00000000000000042009209000000080000000060ff457142000d0200000004000000010000000042000f01000000e842",
857 "005c0500000004000000180000000042007f0500000004000000000000000042007c01000000c042005c0500000004000",
858 "000010000000042005c0500000004000000020000000042005c0500000004000000030000000042005c05000000040000",
859 "00050000000042005c0500000004000000080000000042005c05000000040000000a0000000042005c050000000400000",
860 "00b0000000042005c05000000040000000c0000000042005c0500000004000000120000000042005c0500000004000000",
861 "130000000042005c0500000004000000140000000042005c05000000040000001800000000"
862 );
863 let response_bytes = hex::decode(response_hex).unwrap();
864
865 let mut stream = MockStream {
866 response: Cursor::new(response_bytes),
867 };
868
869 let client = ClientBuilder::new(&mut stream).build();
870
871 let result = client
872 .do_request(RequestPayload::Query(vec![QueryFunction::QueryOperations]))
873 .unwrap();
874
875 if let ResponsePayload::Query(payload) = result {
876 dbg!(payload);
877 } else {
878 panic!("Expected query response!");
879 }
880 }
881}