1use bytes::Bytes;
21use prost::Message;
22
23use crate::error::{Error, Result, YtError};
24use crate::guid::Guid;
25use crate::proto;
26
27pub const API_SERVICE: &str = "ApiService";
29
30pub const DISCOVERY_SERVICE: &str = "DiscoveryService";
32
33pub const PROTOCOL_VERSION_MAJOR: i32 = 1;
36
37pub const DEFAULT_PROTOCOL_VERSION_MAJOR: i32 = 0;
47
48pub fn protocol_version_major(service: &str) -> i32 {
50 match service {
51 API_SERVICE => PROTOCOL_VERSION_MAJOR,
52 _ => DEFAULT_PROTOCOL_VERSION_MAJOR,
53 }
54}
55
56pub const CODEC_NONE: i32 = 0;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[repr(u32)]
69pub enum MessageType {
70 Request = 0x6963_7072,
71 RequestCancelation = 0x6363_7072,
72 Response = 0x6f63_7072,
73}
74
75impl MessageType {
76 fn from_wire(value: u32) -> Option<Self> {
77 match value {
78 0x6963_7072 => Some(Self::Request),
79 0x6363_7072 => Some(Self::RequestCancelation),
80 0x6f63_7072 => Some(Self::Response),
81 _ => None,
82 }
83 }
84}
85
86const CREDENTIALS_EXT_FIELD: u32 = 110;
93
94#[derive(Debug, Clone)]
96pub struct RequestHeaderBuilder {
97 pub request_id: Guid,
98 pub service: String,
99 pub method: String,
100 pub timeout: Option<std::time::Duration>,
101 pub mutation_id: Option<Guid>,
102 pub retry: bool,
103 pub user: Option<String>,
104 pub token: Option<String>,
105}
106
107impl RequestHeaderBuilder {
108 pub fn new(service: impl Into<String>, method: impl Into<String>) -> Self {
109 Self {
110 request_id: Guid::random(),
111 service: service.into(),
112 method: method.into(),
113 timeout: None,
114 mutation_id: None,
115 retry: false,
116 user: None,
117 token: None,
118 }
119 }
120
121 pub fn build(&self) -> proto::rpc::TRequestHeader {
125 proto::rpc::TRequestHeader {
126 request_id: Some(self.request_id.to_proto()),
127 service: self.service.clone(),
128 method: self.method.clone(),
129 protocol_version_major: Some(protocol_version_major(&self.service)),
130 timeout: self.timeout.map(|timeout| timeout.as_micros() as i64),
133 mutation_id: self.mutation_id.map(Guid::to_proto),
134 retry: Some(self.retry),
135 user: self.user.clone(),
136 request_codec: Some(CODEC_NONE),
137 response_codec: Some(CODEC_NONE),
138 ..Default::default()
139 }
140 }
141}
142
143fn append_length_delimited_field(buffer: &mut Vec<u8>, field_number: u32, payload: &[u8]) {
147 prost::encoding::encode_key(
148 field_number,
149 prost::encoding::WireType::LengthDelimited,
150 buffer,
151 );
152 prost::encoding::encode_varint(payload.len() as u64, buffer);
153 buffer.extend_from_slice(payload);
154}
155
156fn encode_header_part(
158 message_type: MessageType,
159 header: &impl Message,
160 token: Option<&str>,
161) -> Bytes {
162 let mut buffer = Vec::with_capacity(4 + header.encoded_len());
163 buffer.extend_from_slice(&(message_type as u32).to_le_bytes());
164 header
165 .encode(&mut buffer)
166 .expect("a Vec never runs out of room");
167
168 if let Some(token) = token {
169 let credentials = proto::rpc::TCredentialsExt {
170 token: Some(token.to_owned()),
171 ..Default::default()
172 };
173 append_length_delimited_field(
174 &mut buffer,
175 CREDENTIALS_EXT_FIELD,
176 &credentials.encode_to_vec(),
177 );
178 }
179
180 Bytes::from(buffer)
181}
182
183pub fn encode_request(
189 header: &proto::rpc::TRequestHeader,
190 token: Option<&str>,
191 body: &impl Message,
192 attachments: Vec<Bytes>,
193) -> Vec<Option<Bytes>> {
194 let mut parts = Vec::with_capacity(2 + attachments.len());
195 parts.push(Some(encode_header_part(
196 MessageType::Request,
197 header,
198 token,
199 )));
200 parts.push(Some(Bytes::from(body.encode_to_vec())));
201 parts.extend(attachments.into_iter().map(Some));
202 parts
203}
204
205pub fn encode_cancelation(request_id: Guid, service: &str, method: &str) -> Vec<Option<Bytes>> {
211 let header = proto::rpc::TRequestCancelationHeader {
212 request_id: request_id.to_proto(),
213 service: service.to_owned(),
214 method: method.to_owned(),
215 realm_id: None,
216 };
217 vec![Some(encode_header_part(
218 MessageType::RequestCancelation,
219 &header,
220 None,
221 ))]
222}
223
224#[derive(Debug, Clone)]
226pub struct ResponseMessage {
227 pub header: proto::rpc::TResponseHeader,
228 pub body: Option<Bytes>,
229 pub attachments: Vec<Bytes>,
230}
231
232impl ResponseMessage {
233 pub fn request_id(&self) -> Option<Guid> {
235 self.header.request_id.as_ref().map(Guid::from_proto)
236 }
237
238 pub fn error(&self) -> Option<YtError> {
245 let error = self.header.error.as_ref()?;
246 let converted = YtError::from_proto(error);
247 (converted.code != crate::error::codes::OK).then_some(converted)
248 }
249
250 pub fn decode_body<T: Message + Default>(&self, message_name: &'static str) -> Result<T> {
252 let body = self.body.as_ref().ok_or_else(|| {
253 Error::Protocol(format!("response to {message_name} has no body part"))
254 })?;
255 T::decode(body.clone()).map_err(|source| Error::Decode {
256 message: message_name,
257 source,
258 })
259 }
260}
261
262pub fn decode_response(parts: Vec<Option<Bytes>>) -> Result<ResponseMessage> {
268 let mut parts = parts.into_iter();
269 let header_part = parts
270 .next()
271 .flatten()
272 .ok_or_else(|| Error::Protocol("message has no header part".to_owned()))?;
273
274 if header_part.len() < 4 {
275 return Err(Error::Protocol(format!(
276 "message header part is {} bytes, too short for a message type",
277 header_part.len()
278 )));
279 }
280 let raw_type = u32::from_le_bytes(header_part[0..4].try_into().unwrap());
281 match MessageType::from_wire(raw_type) {
282 Some(MessageType::Response) => {}
283 Some(other) => {
284 return Err(Error::Protocol(format!(
285 "expected a response message, got {other:?}"
286 )));
287 }
288 None => {
289 return Err(Error::Protocol(format!(
290 "unknown RPC message type {raw_type:#010x}"
291 )));
292 }
293 }
294
295 let header =
296 proto::rpc::TResponseHeader::decode(&header_part[4..]).map_err(|source| Error::Decode {
297 message: "TResponseHeader",
298 source,
299 })?;
300
301 let body = parts.next().flatten();
304 let attachments = parts.flatten().collect();
305
306 Ok(ResponseMessage {
307 header,
308 body,
309 attachments,
310 })
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 #[test]
322 fn the_wire_constants_are_the_documented_ones() {
323 assert_eq!(
324 CREDENTIALS_EXT_FIELD, 110,
325 "TCredentialsExt is extension 110"
326 );
327 assert_eq!(CODEC_NONE, 0, "ECodec::None is 0");
328 assert_eq!(PROTOCOL_VERSION_MAJOR, 1);
329 assert_eq!(DEFAULT_PROTOCOL_VERSION_MAJOR, 0);
330 assert_eq!(API_SERVICE, "ApiService");
331 assert_eq!(DISCOVERY_SERVICE, "DiscoveryService");
332 }
333
334 #[test]
335 fn the_protocol_version_is_per_service() {
336 assert_eq!(protocol_version_major(API_SERVICE), 1);
337 assert_eq!(protocol_version_major(DISCOVERY_SERVICE), 0);
338
339 let header = RequestHeaderBuilder::new(DISCOVERY_SERVICE, "DiscoverProxies").build();
340 assert_eq!(header.protocol_version_major, Some(0));
341 let header = RequestHeaderBuilder::new(API_SERVICE, "LookupRows").build();
342 assert_eq!(header.protocol_version_major, Some(1));
343 }
344
345 #[test]
346 fn message_type_words_spell_rpci_rpcc_and_rpco() {
347 assert_eq!(&(MessageType::Request as u32).to_le_bytes(), b"rpci");
350 assert_eq!(
351 &(MessageType::RequestCancelation as u32).to_le_bytes(),
352 b"rpcc"
353 );
354 assert_eq!(&(MessageType::Response as u32).to_le_bytes(), b"rpco");
355 }
356
357 #[test]
358 fn a_request_has_a_header_part_a_body_part_and_then_attachments() {
359 let header = RequestHeaderBuilder::new(API_SERVICE, "LookupRows").build();
360 let body = proto::api::TReqLookupRows::default();
361 let parts = encode_request(
362 &header,
363 None,
364 &body,
365 vec![Bytes::from_static(b"rowset"), Bytes::from_static(b"more")],
366 );
367
368 assert_eq!(parts.len(), 4);
369 let header_part = parts[0].as_ref().unwrap();
370 assert_eq!(&header_part[0..4], b"rpci");
371 assert_eq!(parts[2].as_ref().unwrap(), &Bytes::from_static(b"rowset"));
372 assert_eq!(parts[3].as_ref().unwrap(), &Bytes::from_static(b"more"));
373 }
374
375 #[test]
376 fn the_header_part_parses_back_as_a_request_header() {
377 let built = RequestHeaderBuilder {
378 timeout: Some(std::time::Duration::from_secs(30)),
379 ..RequestHeaderBuilder::new(API_SERVICE, "StartTransaction")
380 };
381 let header = built.build();
382 let parts = encode_request(
383 &header,
384 None,
385 &proto::api::TReqStartTransaction::default(),
386 vec![],
387 );
388 let header_part = parts[0].as_ref().unwrap();
389
390 let parsed = proto::rpc::TRequestHeader::decode(&header_part[4..]).unwrap();
391 assert_eq!(parsed.service, API_SERVICE);
392 assert_eq!(parsed.method, "StartTransaction");
393 assert_eq!(parsed.protocol_version_major, Some(PROTOCOL_VERSION_MAJOR));
394 assert_eq!(parsed.request_codec, Some(CODEC_NONE));
395 assert_eq!(parsed.response_codec, Some(CODEC_NONE));
396 assert_eq!(parsed.timeout, Some(30_000_000));
398 assert_eq!(
399 Guid::from_proto(&parsed.request_id.unwrap()),
400 built.request_id
401 );
402 }
403
404 #[test]
409 fn the_token_is_appended_as_extension_field_110() {
410 let header = RequestHeaderBuilder::new(API_SERVICE, "LookupRows").build();
411 let parts = encode_request(
412 &header,
413 Some("secret-token"),
414 &proto::api::TReqLookupRows::default(),
415 vec![],
416 );
417 let header_part = parts[0].as_ref().unwrap();
418 let body = &header_part[4..];
419
420 let mut expected_key = Vec::new();
422 prost::encoding::encode_key(
423 CREDENTIALS_EXT_FIELD,
424 prost::encoding::WireType::LengthDelimited,
425 &mut expected_key,
426 );
427 let key_at = body
428 .windows(expected_key.len())
429 .position(|window| window == expected_key)
430 .expect("the credentials key must be in the header bytes");
431
432 let payload = &body[key_at + expected_key.len()..];
433 let (length, rest) = {
434 let mut cursor = payload;
435 let length = prost::encoding::decode_varint(&mut cursor).unwrap();
436 (length as usize, cursor)
437 };
438 let credentials = proto::rpc::TCredentialsExt::decode(&rest[..length]).unwrap();
439 assert_eq!(credentials.token.as_deref(), Some("secret-token"));
440 }
441
442 #[test]
443 fn no_token_means_no_extension() {
444 let header = RequestHeaderBuilder::new(API_SERVICE, "LookupRows").build();
445 let with = encode_request(
446 &header,
447 Some("t"),
448 &proto::api::TReqLookupRows::default(),
449 vec![],
450 );
451 let without = encode_request(
452 &header,
453 None,
454 &proto::api::TReqLookupRows::default(),
455 vec![],
456 );
457 assert!(without[0].as_ref().unwrap().len() < with[0].as_ref().unwrap().len());
458 }
459
460 fn response_parts(
461 header: proto::rpc::TResponseHeader,
462 body: Option<&[u8]>,
463 ) -> Vec<Option<Bytes>> {
464 let mut header_part = Vec::new();
465 header_part.extend_from_slice(&(MessageType::Response as u32).to_le_bytes());
466 header.encode(&mut header_part).unwrap();
467 let mut parts = vec![Some(Bytes::from(header_part))];
468 if let Some(body) = body {
469 parts.push(Some(Bytes::copy_from_slice(body)));
470 }
471 parts
472 }
473
474 #[test]
475 fn a_successful_response_decodes_with_its_body() {
476 let request_id = Guid::random();
477 let body = proto::api::TRspStartTransaction::default();
478 let parts = response_parts(
479 proto::rpc::TResponseHeader {
480 request_id: Some(request_id.to_proto()),
481 ..Default::default()
482 },
483 Some(&body.encode_to_vec()),
484 );
485
486 let response = decode_response(parts).unwrap();
487 assert_eq!(response.request_id(), Some(request_id));
488 assert!(response.error().is_none());
489 response
490 .decode_body::<proto::api::TRspStartTransaction>("TRspStartTransaction")
491 .unwrap();
492 }
493
494 #[test]
495 fn an_error_response_surfaces_the_error_and_has_no_body() {
496 let parts = response_parts(
497 proto::rpc::TResponseHeader {
498 request_id: Some(Guid::random().to_proto()),
499 error: Some(proto::misc::TError {
500 code: crate::error::codes::RESOLVE_ERROR,
501 message: Some("no such table".to_owned()),
502 attributes: None,
503 inner_errors: vec![],
504 }),
505 ..Default::default()
506 },
507 None,
508 );
509
510 let response = decode_response(parts).unwrap();
511 let error = response.error().expect("the header carries an error");
512 assert_eq!(error.code, crate::error::codes::RESOLVE_ERROR);
513 assert!(
514 response
515 .decode_body::<proto::api::TRspLookupRows>("TRspLookupRows")
516 .is_err()
517 );
518 }
519
520 #[test]
524 fn an_error_with_code_zero_is_success() {
525 let parts = response_parts(
526 proto::rpc::TResponseHeader {
527 request_id: Some(Guid::random().to_proto()),
528 error: Some(proto::misc::TError {
529 code: 0,
530 message: Some(String::new()),
531 attributes: None,
532 inner_errors: vec![],
533 }),
534 ..Default::default()
535 },
536 Some(&[]),
537 );
538 assert!(decode_response(parts).unwrap().error().is_none());
539 }
540
541 #[test]
542 fn a_message_that_is_not_a_response_is_rejected() {
543 let mut header_part = Vec::new();
544 header_part.extend_from_slice(&(MessageType::Request as u32).to_le_bytes());
545 proto::rpc::TRequestHeader {
546 service: API_SERVICE.to_owned(),
547 method: "LookupRows".to_owned(),
548 ..Default::default()
549 }
550 .encode(&mut header_part)
551 .unwrap();
552
553 let error = decode_response(vec![Some(Bytes::from(header_part))]).unwrap_err();
554 assert!(error.to_string().contains("expected a response message"));
555 }
556
557 #[test]
558 fn a_short_or_missing_header_part_is_rejected_not_panicked_on() {
559 assert!(decode_response(vec![]).is_err());
560 assert!(decode_response(vec![None]).is_err());
561 assert!(decode_response(vec![Some(Bytes::from_static(b"rp"))]).is_err());
562 assert!(decode_response(vec![Some(Bytes::from_static(b"nope"))]).is_err());
563 }
564
565 #[test]
566 fn cancelation_is_one_part_naming_the_request() {
567 let request_id = Guid::random();
568 let parts = encode_cancelation(request_id, API_SERVICE, "SelectRows");
569 assert_eq!(parts.len(), 1, "the C++ builds a single-part message");
570
571 let part = parts[0].as_ref().unwrap();
572 assert_eq!(&part[0..4], b"rpcc");
573 let header = proto::rpc::TRequestCancelationHeader::decode(&part[4..]).unwrap();
574 assert_eq!(Guid::from_proto(&header.request_id), request_id);
575 assert_eq!(header.service, API_SERVICE);
576 assert_eq!(header.method, "SelectRows");
577 }
578
579 #[test]
580 fn attachments_survive_the_round_trip() {
581 let parts = response_parts(proto::rpc::TResponseHeader::default(), Some(b"body"));
582 let mut parts = parts;
583 parts.push(Some(Bytes::from_static(b"attachment one")));
584 parts.push(Some(Bytes::from_static(b"attachment two")));
585
586 let response = decode_response(parts).unwrap();
587 assert_eq!(response.body.as_deref(), Some(&b"body"[..]));
588 assert_eq!(response.attachments.len(), 2);
589 assert_eq!(
590 response.attachments[1],
591 Bytes::from_static(b"attachment two")
592 );
593 }
594}