1use crate::protocol::peer::Peer;
26use crate::tools::buckets::BucketLocation;
27use crate::tools::time::TimeMillis;
28use crate::tools::types::{Hash, Id, Signature, SignatureKey, VerificationKey, ID_BYTES};
29use crate::tools::{hashing, json, signing, tools, BytesGatherer};
30use bytes::{Buf, BufMut, Bytes, BytesMut};
31use serde::{Deserialize, Serialize};
32use strum_macros::{Display, FromRepr};
33use crate::anyhow_assert_ge;
34use crate::protocol::posting::encoded_post_bundle::EncodedPostBundleHeaderV1;
35use crate::protocol::posting::encoded_post_feedback::EncodedPostFeedbackV1;
36
37#[derive(Debug, Display, PartialEq, Clone, FromRepr)]
49#[repr(u16)]
50pub enum PayloadRequestKind {
51 ErrorV1, PingV1,
53 BootstrapV1,
54 RetiredAnnounceV1, GetPostBundleV1,
56 GetPostBundleFeedbackV1,
57 SubmitPostClaimV1,
58 SubmitPostCommitV1,
59 SubmitPostFeedbackV1,
60 HealPostBundleClaimV1,
61 HealPostBundleCommitV1,
62 HealPostBundleFeedbackV1,
63 CachePostBundleV1,
64 CachePostBundleFeedbackV1,
65 FetchUrlPreviewV1,
66 TrendingHashtagsFetchV1,
67 PeerStatsRequestV1,
68 AnnounceV2,
69}
70
71pub const PAYLOAD_REQUEST_KIND_COUNT: usize = 18;
75
76impl PayloadRequestKind {
77 pub fn from_u16(value: u16) -> anyhow::Result<Self> {
78 Self::from_repr(value).ok_or_else(|| anyhow::anyhow!("unknown PayloadRequestKind: {}", value))
79 }
80}
81
82#[derive(Debug, Display, PartialEq, Clone, FromRepr)]
91#[repr(u16)]
92pub enum PayloadResponseKind {
93 ErrorResponseV1, PingResponseV1,
95 BootstrapResponseV1,
96 RetiredAnnounceResponseV1, GetPostBundleResponseV1,
98 GetPostBundleFeedbackResponseV1,
99 SubmitPostClaimResponseV1,
100 SubmitPostCommitResponseV1,
101 SubmitPostFeedbackResponseV1,
102 HealPostBundleClaimResponseV1,
103 HealPostBundleCommitResponseV1,
104 HealPostBundleFeedbackResponseV1,
105 CachePostBundleResponseV1,
106 CachePostBundleFeedbackResponseV1,
107 FetchUrlPreviewResponseV1,
108 TrendingHashtagsFetchResponseV1,
109 PeerStatsResponseV1,
110 AnnounceResponseV2,
111}
112
113impl PayloadResponseKind {
114 pub fn from_u16(value: u16) -> anyhow::Result<Self> {
115 Self::from_repr(value).ok_or_else(|| anyhow::anyhow!("unknown PayloadResponseKind: {}", value))
116 }
117}
118
119#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
126pub struct ErrorResponseV1 {
127 pub code: u16,
128 pub message: String,
129}
130
131#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
132pub struct PingV1 {}
133
134#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
135pub struct PingResponseV1 {
136 pub peer: Peer,
137}
138
139#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
140pub struct BootstrapV1 {}
141
142#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
143pub struct BootstrapResponseV1 {
144 pub peers_random: Vec<Peer>,
145}
146
147#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
160pub struct AnnounceV2 {
161 pub peer_self: Peer,
162 pub proof_payload: Vec<u8>,
163}
164
165#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
169pub struct AnnounceResponseV2 {
170 pub peer_self: Peer,
171 pub peers_nearest: Vec<Peer>,
172}
173
174#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
175pub struct GetPostBundleV1 {
176 pub bucket_location: BucketLocation,
177 pub peers_visited: Vec<Peer>,
178 pub already_retrieved_peer_ids: Vec<Id>,
179}
180
181#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
182pub struct GetPostBundleFeedbackV1 {
183 pub bucket_location: BucketLocation,
184 pub peers_visited: Vec<Peer>,
185 pub already_retrieved_peer_ids: Vec<Id>,
186}
187
188#[derive(Debug, PartialEq, Clone)]
189pub struct GetPostBundleResponseV1 {
190 pub peers_nearer: Vec<Peer>,
191 pub cache_request_token: Option<CacheRequestTokenV1>,
192 pub post_bundles_cached: Vec<Bytes>, pub post_bundle: Option<Bytes>, }
195
196impl GetPostBundleResponseV1 {
197 pub fn to_bytes_gatherer(&self) -> anyhow::Result<BytesGatherer> {
198 let mut bytes_gatherer = BytesGatherer::default();
199
200 tools::write_length_prefixed_json::<Vec<Peer>>(&mut bytes_gatherer, &self.peers_nearer)?;
201
202 match &self.cache_request_token {
203 Some(token) => {
204 bytes_gatherer.put_u8(1);
205 tools::write_length_prefixed_json(&mut bytes_gatherer, token)?;
206 }
207 None => bytes_gatherer.put_u8(0),
208 }
209
210 bytes_gatherer.put_u16(self.post_bundles_cached.len() as u16);
211 for bundle in &self.post_bundles_cached {
212 bytes_gatherer.put_u32(bundle.len() as u32);
213 bytes_gatherer.put_bytes(bundle.clone());
214 }
215
216 match &self.post_bundle {
217 Some(post_bundle) => { bytes_gatherer.put_u8(1); bytes_gatherer.put_bytes(post_bundle.clone()); }
218 None => { bytes_gatherer.put_u8(0); }
219 }
220
221 Ok(bytes_gatherer)
222 }
223
224 pub fn from_bytes(mut bytes: Bytes) -> anyhow::Result<Self> {
225 use bytes::Buf;
226
227 let peers_nearer = tools::read_length_prefixed_json::<Vec<Peer>>(&mut bytes)?;
228
229 if bytes.remaining() < 1 {
230 anyhow::bail!("Invalid buffer: missing cache_request_token presence flag");
231 }
232 let cache_request_token = match bytes.get_u8() {
233 0 => None,
234 1 => Some(tools::read_length_prefixed_json::<CacheRequestTokenV1>(&mut bytes)?),
235 _ => anyhow::bail!("Invalid buffer: unknown cache_request_token presence flag"),
236 };
237
238 anyhow_assert_ge!(bytes.remaining(), 2, "Missing post_bundles_cached count");
239 let cached_count = bytes.get_u16() as usize;
240 let mut post_bundles_cached = Vec::with_capacity(cached_count);
241 for _ in 0..cached_count {
242 anyhow_assert_ge!(bytes.remaining(), 4, "Missing post_bundles_cached entry length");
243 let len = bytes.get_u32() as usize;
244 anyhow_assert_ge!(bytes.remaining(), len, "Truncated post_bundles_cached entry");
245 post_bundles_cached.push(bytes.split_to(len));
246 }
247
248 if bytes.remaining() < 1 {
249 anyhow::bail!("Invalid buffer: missing post_bundle presence flag");
250 }
251 let post_bundle = match bytes.get_u8() {
252 0 => None,
253 1 => Some(bytes.copy_to_bytes(bytes.remaining())),
254 _ => anyhow::bail!("Invalid buffer: unknown post_bundle presence flag"),
255 };
256
257 Ok(Self { peers_nearer, cache_request_token, post_bundles_cached, post_bundle })
258 }
259}
260#[derive(Debug, PartialEq, Clone)]
261pub struct GetPostBundleFeedbackResponseV1 {
262 pub peers_nearer: Vec<Peer>,
263 pub cache_request_token: Option<CacheRequestTokenV1>,
264 pub post_bundle_feedbacks_cached: Vec<Bytes>, pub encoded_post_bundle_feedback: Option<Bytes>, }
267
268impl GetPostBundleFeedbackResponseV1 {
269 pub fn to_bytes_gatherer(&self) -> anyhow::Result<BytesGatherer> {
270 let mut bytes_gatherer = BytesGatherer::default();
271
272 tools::write_length_prefixed_json::<Vec<Peer>>(&mut bytes_gatherer, &self.peers_nearer)?;
273
274 match &self.cache_request_token {
275 Some(token) => {
276 bytes_gatherer.put_u8(1u8);
277 tools::write_length_prefixed_json(&mut bytes_gatherer, token)?;
278 }
279 None => bytes_gatherer.put_u8(0u8),
280 }
281
282 bytes_gatherer.put_u16(self.post_bundle_feedbacks_cached.len() as u16);
283 for feedback in &self.post_bundle_feedbacks_cached {
284 bytes_gatherer.put_u32(feedback.len() as u32);
285 bytes_gatherer.put_bytes(feedback.clone());
286 }
287
288 match &self.encoded_post_bundle_feedback {
289 Some(post_bundle_feedback) => {
290 bytes_gatherer.put_u8(1u8);
291 bytes_gatherer.put_bytes(post_bundle_feedback.clone());
292 }
293 None => bytes_gatherer.put_u8(0u8),
294 }
295
296 Ok(bytes_gatherer)
297 }
298
299 pub fn from_bytes(mut bytes: Bytes) -> anyhow::Result<Self> {
300 use bytes::Buf;
301
302 let peers_nearer = tools::read_length_prefixed_json::<Vec<Peer>>(&mut bytes)?;
303
304 if bytes.remaining() < 1 {
305 anyhow::bail!("Invalid buffer: missing cache_request_token presence flag");
306 }
307 let cache_request_token = match bytes.get_u8() {
308 0 => None,
309 1 => Some(tools::read_length_prefixed_json::<CacheRequestTokenV1>(&mut bytes)?),
310 _ => anyhow::bail!("Invalid buffer: unknown cache_request_token presence flag"),
311 };
312
313 anyhow_assert_ge!(bytes.remaining(), 2, "Missing post_bundle_feedbacks_cached count");
314 let cached_count = bytes.get_u16() as usize;
315 let mut post_bundle_feedbacks_cached = Vec::with_capacity(cached_count);
316 for _ in 0..cached_count {
317 anyhow_assert_ge!(bytes.remaining(), 4, "Missing post_bundle_feedbacks_cached entry length");
318 let len = bytes.get_u32() as usize;
319 anyhow_assert_ge!(bytes.remaining(), len, "Truncated post_bundle_feedbacks_cached entry");
320 post_bundle_feedbacks_cached.push(bytes.split_to(len));
321 }
322
323 if bytes.remaining() < 1 {
324 anyhow::bail!("Invalid buffer: missing post_bundle_feedback presence flag");
325 }
326 let post_bundle_feedback = match bytes.get_u8() {
327 0 => None,
328 1 => Some(bytes.copy_to_bytes(bytes.remaining())),
329 _ => anyhow::bail!("Invalid buffer: unknown post_bundle_feedback presence flag"),
330 };
331
332 Ok(Self { peers_nearer, cache_request_token, post_bundle_feedbacks_cached, encoded_post_bundle_feedback: post_bundle_feedback })
333 }
334}
335
336#[derive(Debug, PartialEq, Clone)]
337pub struct SubmitPostClaimV1 {
338 pub bucket_location: BucketLocation,
339 pub referenced_post_header_bytes: Option<Bytes>, pub referenced_hashtags: Vec<String>, pub encoded_post_bytes: Bytes, }
343
344impl SubmitPostClaimV1 {
345 pub fn new_to_bytes(bucket_location: &BucketLocation, referenced_post_header_bytes: Option<&[u8]>, referenced_hashtags: &[String], encoded_post_bytes_without_body: &[u8]) -> anyhow::Result<Bytes> {
346 let mut bytes_gatherer = BytesGatherer::default();
347 tools::write_length_prefixed_json(&mut bytes_gatherer, bucket_location)?;
348 match referenced_post_header_bytes {
349 Some(header_bytes) => {
350 bytes_gatherer.put_u32(header_bytes.len() as u32);
351 bytes_gatherer.put_slice(header_bytes);
352 }
353 None => {
354 bytes_gatherer.put_u32(0);
355 }
356 }
357 tools::write_length_prefixed_json(&mut bytes_gatherer, &referenced_hashtags)?;
358 bytes_gatherer.put_slice(encoded_post_bytes_without_body);
359 Ok(bytes_gatherer.to_bytes())
360 }
361
362 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
363 use bytes::Buf;
364 let bucket_location = tools::read_length_prefixed_json::<BucketLocation>(bytes)?;
365
366 anyhow::ensure!(bytes.remaining() >= 4, "SubmitPostClaimV1: missing referenced_post_header_bytes length");
367 let referenced_post_header_bytes_len = bytes.get_u32() as usize;
368 let referenced_post_header_bytes = if referenced_post_header_bytes_len > 0 {
369 anyhow::ensure!(bytes.remaining() >= referenced_post_header_bytes_len, "SubmitPostClaimV1: referenced_post_header_bytes length {} exceeds remaining {}", referenced_post_header_bytes_len, bytes.remaining());
370 Some(bytes.split_to(referenced_post_header_bytes_len))
371 } else {
372 None
373 };
374
375 let referenced_hashtags = tools::read_length_prefixed_json::<Vec<String>>(bytes)?;
376
377 anyhow::ensure!(bytes.has_remaining(), "SubmitPostClaimV1: missing encoded_post_bytes");
378 let encoded_post_bytes = bytes.split_to(bytes.len());
379
380 Ok(Self { bucket_location, referenced_post_header_bytes, referenced_hashtags, encoded_post_bytes })
381 }
382}
383
384#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
385pub struct SubmitPostClaimResponseV1 {
386 pub peers_nearer: Vec<Peer>,
387 pub submit_post_claim_token: Option<SubmitPostClaimTokenV1>,
388}
389
390#[derive(Debug, PartialEq, Clone)]
391pub struct SubmitPostCommitV1 {
392 pub bucket_location: BucketLocation,
393 pub submit_post_claim_token: SubmitPostClaimTokenV1,
394 pub encoded_post_bytes: Bytes,
395}
396
397impl SubmitPostCommitV1 {
398 pub fn new_to_bytes(bucket_location: &BucketLocation, submit_post_claim_token: &SubmitPostClaimTokenV1, encoded_post_bytes: &[u8]) -> anyhow::Result<Bytes> {
399 let mut bytes_gatherer = BytesGatherer::default();
400 tools::write_length_prefixed_json(&mut bytes_gatherer, bucket_location)?;
401 tools::write_length_prefixed_json(&mut bytes_gatherer, submit_post_claim_token)?;
402 bytes_gatherer.put_slice(encoded_post_bytes);
403 Ok(bytes_gatherer.to_bytes())
404 }
405
406 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
407 let bucket_location = tools::read_length_prefixed_json::<BucketLocation>(bytes)?;
408 let submit_post_claim_token = tools::read_length_prefixed_json::<SubmitPostClaimTokenV1>(bytes)?;
409 let encoded_post_bytes = bytes.split_to(bytes.len());
410
411 Ok(Self {
412 bucket_location,
413 submit_post_claim_token,
414 encoded_post_bytes,
415 })
416 }
417}
418
419#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
420pub struct SubmitPostCommitResponseV1 {
421 pub submit_post_commit_token: SubmitPostCommitTokenV1,
422}
423
424#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
425pub struct SubmitPostClaimTokenV1 {
426 pub peer: Peer,
427 pub bucket_location: BucketLocation,
428 pub post_id: Id,
429 pub token_signature: Signature,
430}
431
432impl SubmitPostClaimTokenV1 {
433 fn get_hash_for_signing(peer: &Peer, bucket_location: &BucketLocation, post_id: &Id) -> Hash {
434 hashing::hash_multiple(&[peer.id.as_ref(), bucket_location.get_hash_for_signing().as_ref(), post_id.as_ref(), "SubmitPostClaimTokenV1".as_bytes()])
435 }
436
437 pub fn new(peer: Peer, bucket_location: BucketLocation, post_id: Id, signature_key: &SignatureKey) -> Self {
438 let token_signature = signing::sign(signature_key, Self::get_hash_for_signing(&peer, &bucket_location, &post_id).as_ref());
439 Self { peer, bucket_location, post_id, token_signature }
440 }
441
442 pub fn verify(&self) -> anyhow::Result<()> {
443 self.peer.verify()?;
444 let verification_key = VerificationKey::from_bytes(&self.peer.verification_key_bytes)?;
445 signing::verify(&verification_key, &self.token_signature, Self::get_hash_for_signing(&self.peer, &self.bucket_location, &self.post_id).as_ref())
446 }
447}
448
449#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
450pub struct SubmitPostCommitTokenV1 {
451 pub peer: Peer,
452 pub bucket_location: BucketLocation,
453 pub post_id: Id,
454 pub token_signature: Signature,
455}
456
457impl SubmitPostCommitTokenV1 {
458 fn get_hash_for_signing(peer: &Peer, bucket_location: &BucketLocation, post_id: &Id) -> Hash {
459 hashing::hash_multiple(&[peer.id.as_ref(), bucket_location.get_hash_for_signing().as_ref(), post_id.as_ref(), "SubmitPostCommitTokenV1".as_bytes()])
460 }
461
462 pub fn new(peer: Peer, bucket_location: BucketLocation, post_id: Id, signature_key: &SignatureKey) -> Self {
463 let token_signature = signing::sign(signature_key, Self::get_hash_for_signing(&peer, &bucket_location, &post_id).as_ref());
464 Self { peer, bucket_location, post_id, token_signature }
465 }
466
467 pub fn verify(&self) -> anyhow::Result<()> {
468 self.peer.verify()?;
469 let verification_key = VerificationKey::from_bytes(&self.peer.verification_key_bytes)?;
470 signing::verify(&verification_key, &self.token_signature, Self::get_hash_for_signing(&self.peer, &self.bucket_location, &self.post_id).as_ref())
471 }
472}
473
474
475#[derive(Debug, PartialEq, Clone)]
476pub struct SubmitPostFeedbackV1 {
477 pub bucket_location: BucketLocation,
478 pub encoded_post_feedback: EncodedPostFeedbackV1,
479}
480
481impl SubmitPostFeedbackV1 {
482 pub fn new_to_bytes(bucket_location: &BucketLocation, encoded_post_feedback: &EncodedPostFeedbackV1) -> anyhow::Result<Bytes> {
483 let mut bytes_gatherer = BytesGatherer::default();
484 tools::write_length_prefixed_json(&mut bytes_gatherer, bucket_location)?;
485 let mut feedback_bytes = BytesMut::new();
486 encoded_post_feedback.append_encode_to_bytes(&mut feedback_bytes)?;
487 bytes_gatherer.put_bytes(feedback_bytes.freeze());
488 Ok(bytes_gatherer.to_bytes())
489 }
490
491 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
492 let bucket_location = tools::read_length_prefixed_json::<BucketLocation>(bytes)?;
493 let encoded_post_feedback = EncodedPostFeedbackV1::decode_from_bytes(bytes)?;
494 Ok(Self { bucket_location, encoded_post_feedback })
495 }
496
497}
498
499#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
500pub struct SubmitPostFeedbackResponseV1 {
501 pub peers_nearer: Vec<Peer>,
502 pub accepted: bool,
503}
504
505
506
507#[derive(Debug, PartialEq, Clone)]
510pub struct HealPostBundleClaimV1 {
511 pub bucket_location: BucketLocation,
512 pub donor_header: EncodedPostBundleHeaderV1,
513}
514
515impl HealPostBundleClaimV1 {
516 pub fn new_to_bytes(donor_header: &EncodedPostBundleHeaderV1, bucket_location: &BucketLocation) -> anyhow::Result<Bytes> {
517 let mut g = BytesGatherer::default();
518 tools::write_length_prefixed_json(&mut g, bucket_location)?;
519 tools::write_length_prefixed_json(&mut g, donor_header)?;
520 Ok(g.to_bytes())
521 }
522
523 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
524 let bucket_location = tools::read_length_prefixed_json::<BucketLocation>(bytes)?;
525 let donor_header = tools::read_length_prefixed_json::<EncodedPostBundleHeaderV1>(bytes)?;
526 Ok(Self { donor_header, bucket_location })
527 }
528}
529
530#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
531pub struct HealPostBundleClaimResponseV1 {
532 pub needed_post_ids: Vec<Id>,
533 pub token: Option<HealPostBundleClaimTokenV1>,
534}
535
536#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
537pub struct HealPostBundleClaimTokenV1 {
538 pub peer: Peer,
539 pub bucket_location: BucketLocation,
540 pub donor_header_signature: Signature,
541 pub needed_post_ids: Vec<Id>,
542 pub token_signature: Signature,
543}
544
545impl HealPostBundleClaimTokenV1 {
546 fn get_hash_for_signing(peer_id: &Id, bucket_location: &BucketLocation, donor_header_signature: &Signature, needed_post_ids: &[Id]) -> Hash {
547 let bucket_location_hash = bucket_location.get_hash_for_signing();
548 let mut hash_input: Vec<&[u8]> = vec![
549 peer_id.as_ref(),
550 bucket_location_hash.as_ref(),
551 donor_header_signature.as_ref(),
552 "HealPostBundleClaimTokenV1".as_bytes(),
553 ];
554 for id in needed_post_ids {
555 hash_input.push(id.as_ref());
556 }
557 hashing::hash_multiple(&hash_input)
558 }
559
560 pub fn new(peer: Peer, bucket_location: BucketLocation, needed_post_ids: Vec<Id>, donor_header_signature: Signature, signature_key: &SignatureKey) -> Self {
561 let hash = Self::get_hash_for_signing(&peer.id, &bucket_location, &donor_header_signature, &needed_post_ids);
562 let token_signature = signing::sign(signature_key, hash.as_ref());
563 Self { peer, bucket_location, donor_header_signature, needed_post_ids, token_signature }
564 }
565
566 pub fn verify(&self) -> anyhow::Result<()> {
567 self.peer.verify()?;
568 self.bucket_location.validate()?;
569 let hash = Self::get_hash_for_signing(&self.peer.id, &self.bucket_location, &self.donor_header_signature, &self.needed_post_ids);
570 let verification_key = VerificationKey::from_bytes(&self.peer.verification_key_bytes)?;
571 signing::verify(&verification_key, &self.token_signature, hash.as_ref())
572 }
573}
574
575#[derive(Debug, PartialEq, Clone)]
576pub struct HealPostBundleCommitV1 {
577 pub token: HealPostBundleClaimTokenV1,
578 pub donor_header: EncodedPostBundleHeaderV1,
579 pub encoded_posts_bytes: Bytes,
580}
581
582impl HealPostBundleCommitV1 {
583 pub fn new_to_bytes(token: &HealPostBundleClaimTokenV1, donor_header: &EncodedPostBundleHeaderV1, encoded_posts_bytes: &[u8]) -> anyhow::Result<Bytes> {
584 let mut bytes_gatherer = BytesGatherer::default();
585 tools::write_length_prefixed_json(&mut bytes_gatherer, token)?;
586 tools::write_length_prefixed_json(&mut bytes_gatherer, donor_header)?;
587 bytes_gatherer.put_slice(encoded_posts_bytes);
588 Ok(bytes_gatherer.to_bytes())
589 }
590
591 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
592 let token = tools::read_length_prefixed_json::<HealPostBundleClaimTokenV1>(bytes)?;
593 let donor_header = tools::read_length_prefixed_json::<EncodedPostBundleHeaderV1>(bytes)?;
594 let encoded_posts_bytes = bytes.split_to(bytes.len());
595 Ok(Self { token, donor_header, encoded_posts_bytes })
596 }
597}
598
599#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
600pub struct HealPostBundleCommitResponseV1 {
601 pub accepted: bool,
602}
603
604#[derive(Debug, PartialEq, Clone)]
607pub struct HealPostBundleFeedbackV1 {
608 pub location_id: Id,
609 pub encoded_post_feedbacks: Vec<EncodedPostFeedbackV1>,
610}
611
612impl HealPostBundleFeedbackV1 {
613 pub fn new_to_bytes(location_id: &Id, feedbacks: &[EncodedPostFeedbackV1]) -> anyhow::Result<Bytes> {
614 let mut bytes = BytesMut::new();
615 bytes.put_slice(location_id.as_ref());
616 for f in feedbacks {
617 f.append_encode_to_bytes(&mut bytes)?;
618 }
619 Ok(bytes.freeze())
620 }
621
622 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
623 use bytes::Buf;
624 anyhow_assert_ge!(bytes.remaining(), ID_BYTES, "Missing location_id in HealPostBundleFeedbackV1");
625 let location_id = Id::from_slice(&bytes.split_to(ID_BYTES))?;
626 let mut encoded_post_feedbacks = Vec::new();
627 while bytes.has_remaining() {
628 encoded_post_feedbacks.push(EncodedPostFeedbackV1::decode_from_bytes(&mut *bytes)?);
629 }
630 Ok(Self { location_id, encoded_post_feedbacks })
631 }
632}
633
634#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
635pub struct HealPostBundleFeedbackResponseV1 {
636 pub accepted_count: u32,
637}
638
639
640#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
643pub struct CacheRequestTokenV1 {
644 pub peer: Peer,
645 pub bucket_location: BucketLocation,
646 pub expires_at: TimeMillis,
647 pub already_cached_peer_ids: Vec<Id>,
650 pub token_signature: Signature,
651}
652
653impl CacheRequestTokenV1 {
654 fn get_hash_for_signing(peer_id: &Id, bucket_location: &BucketLocation, expires_at: &TimeMillis, already_cached_peer_ids: &[Id]) -> Hash {
655 let expires_at_be = expires_at.encode_be();
656 let bucket_location_hash = bucket_location.get_hash_for_signing();
657 let mut inputs: Vec<&[u8]> = vec![peer_id.as_ref(), bucket_location_hash.as_ref(), expires_at_be.as_ref(), "CacheRequestToken".as_bytes()];
658 for id in already_cached_peer_ids {
659 inputs.push(id.as_ref());
660 }
661 hashing::hash_multiple(&inputs)
662 }
663
664 pub fn new(peer: Peer, bucket_location: BucketLocation, expires_at: TimeMillis, already_cached_peer_ids: Vec<Id>, signature_key: &SignatureKey) -> Self {
665 let token_signature = signing::sign(signature_key, Self::get_hash_for_signing(&peer.id, &bucket_location, &expires_at, &already_cached_peer_ids).as_ref());
666 Self { peer, bucket_location, expires_at, already_cached_peer_ids, token_signature }
667 }
668
669 pub fn verify(&self) -> anyhow::Result<()> {
670 self.peer.verify()?;
671 self.bucket_location.validate()?;
672 let verification_key = VerificationKey::from_bytes(&self.peer.verification_key_bytes)?;
673 signing::verify(&verification_key, &self.token_signature, Self::get_hash_for_signing(&self.peer.id, &self.bucket_location, &self.expires_at, &self.already_cached_peer_ids).as_ref())
674 }
675
676 pub fn is_expired(&self, current_time: TimeMillis) -> bool {
677 current_time > self.expires_at
678 }
679}
680
681#[derive(Debug, PartialEq, Clone)]
682pub struct CachePostBundleV1 {
683 pub token: CacheRequestTokenV1,
684 pub encoded_post_bundles: Vec<Bytes>,
686}
687
688impl CachePostBundleV1 {
689 pub fn new_to_bytes(token: &CacheRequestTokenV1, encoded_post_bundles: &[&[u8]]) -> anyhow::Result<Bytes> {
690 let mut bytes_gatherer = BytesGatherer::default();
691 tools::write_length_prefixed_json(&mut bytes_gatherer, token)?;
692 bytes_gatherer.put_u16(encoded_post_bundles.len() as u16);
693 for bundle in encoded_post_bundles {
694 bytes_gatherer.put_u32(bundle.len() as u32);
695 bytes_gatherer.put_slice(bundle);
696 }
697 Ok(bytes_gatherer.to_bytes())
698 }
699
700 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
701 let token = tools::read_length_prefixed_json::<CacheRequestTokenV1>(bytes)?;
702 anyhow_assert_ge!(bytes.remaining(), 2, "Missing encoded_post_bundles count");
703 let count = bytes.get_u16() as usize;
704 let mut encoded_post_bundles = Vec::with_capacity(count);
705 for _ in 0..count {
706 anyhow_assert_ge!(bytes.remaining(), 4, "Missing encoded_post_bundle entry length");
707 let len = bytes.get_u32() as usize;
708 anyhow_assert_ge!(bytes.remaining(), len, "Truncated encoded_post_bundle entry");
709 encoded_post_bundles.push(bytes.split_to(len));
710 }
711 Ok(Self { token, encoded_post_bundles })
712 }
713}
714
715#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
716pub struct CachePostBundleResponseV1 {
717 pub accepted: bool,
718}
719
720#[derive(Debug, PartialEq, Clone)]
721pub struct CachePostBundleFeedbackV1 {
722 pub token: CacheRequestTokenV1,
723 pub encoded_post_bundle_feedback_bytes: Bytes,
724}
725
726impl CachePostBundleFeedbackV1 {
727 pub fn new_to_bytes(token: &CacheRequestTokenV1, encoded_post_bundle_feedback_bytes: &[u8]) -> anyhow::Result<Bytes> {
728 let mut bytes_gatherer = BytesGatherer::default();
729 tools::write_length_prefixed_json(&mut bytes_gatherer, token)?;
730 bytes_gatherer.put_slice(encoded_post_bundle_feedback_bytes);
731 Ok(bytes_gatherer.to_bytes())
732 }
733
734 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
735 let token = tools::read_length_prefixed_json::<CacheRequestTokenV1>(bytes)?;
736 let encoded_post_bundle_feedback_bytes = bytes.split_to(bytes.len());
737 Ok(Self { token, encoded_post_bundle_feedback_bytes })
738 }
739}
740
741#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
742pub struct CachePostBundleFeedbackResponseV1 {
743 pub accepted: bool,
744}
745
746#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
748pub struct FetchUrlPreviewV1 {
749 pub url: String,
750}
751
752impl FetchUrlPreviewV1 {
753 pub fn new_to_bytes(url: &str) -> anyhow::Result<Bytes> {
754 let mut bytes_gatherer = BytesGatherer::default();
755 tools::write_length_prefixed_json(&mut bytes_gatherer, &FetchUrlPreviewV1 { url: url.to_string() })?;
756 Ok(bytes_gatherer.to_bytes())
757 }
758
759 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
760 tools::read_length_prefixed_json(bytes)
761 }
762}
763
764#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
766pub struct FetchUrlPreviewResponseV1 {
767 pub url: String,
768 pub title: String,
769 pub description: String,
770 pub image_url: String,
771}
772
773impl FetchUrlPreviewResponseV1 {
774 pub fn to_bytes(&self) -> anyhow::Result<Bytes> {
775 json::struct_to_bytes(self)
776 }
777
778 pub fn from_bytes(bytes: &Bytes) -> anyhow::Result<Self> {
779 json::bytes_to_struct(bytes)
780 }
781}
782
783#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
785pub struct TrendingHashtagsFetchV1 {
786 pub limit: u16,
787}
788
789impl TrendingHashtagsFetchV1 {
790 pub fn new_to_bytes(limit: u16) -> anyhow::Result<Bytes> {
791 let mut bytes_gatherer = BytesGatherer::default();
792 tools::write_length_prefixed_json(&mut bytes_gatherer, &TrendingHashtagsFetchV1 { limit })?;
793 Ok(bytes_gatherer.to_bytes())
794 }
795
796 pub fn from_bytes(bytes: &mut Bytes) -> anyhow::Result<Self> {
797 tools::read_length_prefixed_json(bytes)
798 }
799}
800
801#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
802pub struct TrendingHashtagV1 {
803 pub hashtag: String,
804 pub count: u64,
805}
806
807#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
809pub struct TrendingHashtagsFetchResponseV1 {
810 pub trending_hashtags: Vec<TrendingHashtagV1>,
811}
812
813impl TrendingHashtagsFetchResponseV1 {
814 pub fn to_bytes(&self) -> anyhow::Result<Bytes> {
815 json::struct_to_bytes(self)
816 }
817
818 pub fn from_bytes(bytes: &Bytes) -> anyhow::Result<Self> {
819 json::bytes_to_struct(bytes)
820 }
821}
822
823#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
830pub struct PeerStatsRequestV1 {}
831
832impl PeerStatsRequestV1 {
833 pub fn to_bytes(&self) -> anyhow::Result<Bytes> {
834 json::struct_to_bytes(self)
835 }
836
837 pub fn from_bytes(bytes: &Bytes) -> anyhow::Result<Self> {
838 json::bytes_to_struct(bytes)
839 }
840}
841
842#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
850pub struct PeerStatsResponseV1 {
851 pub peer: Peer,
852 pub timestamp: TimeMillis,
853 pub json_compressed: Bytes,
854 pub signature: Signature,
855}
856
857impl PeerStatsResponseV1 {
858 pub fn signing_input(timestamp: TimeMillis, json_compressed: &[u8]) -> Vec<u8> {
860 let mut buf = Vec::with_capacity(crate::tools::time::TIME_MILLIS_BYTES + json_compressed.len());
861 buf.extend_from_slice(timestamp.encode_be().as_ref());
862 buf.extend_from_slice(json_compressed);
863 buf
864 }
865
866 pub fn to_bytes(&self) -> anyhow::Result<Bytes> {
867 json::struct_to_bytes(self)
868 }
869
870 pub fn from_bytes(bytes: &Bytes) -> anyhow::Result<Self> {
871 json::bytes_to_struct(bytes)
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use crate::protocol::payload::payload::{GetPostBundleResponseV1, HealPostBundleClaimResponseV1, HealPostBundleClaimTokenV1, HealPostBundleClaimV1, HealPostBundleCommitResponseV1, HealPostBundleCommitV1, SubmitPostClaimTokenV1, SubmitPostCommitTokenV1};
878 use crate::protocol::posting::encoded_post_bundle::{EncodedPostBundleHeaderV1, EncodedPostBundleV1};
879 use crate::tools::server_id::ServerId;
880 use crate::tools::buckets::{BucketLocation, BucketType};
881 use crate::tools::time::{TimeMillis, MILLIS_IN_HOUR};
882 use crate::tools::time_provider::time_provider::{RealTimeProvider, TimeProvider};
883 use crate::tools::{config, json, tools};
884 use crate::tools::types::{Id, Pow, Signature, ID_BYTES};
885 use std::collections::HashSet;
886 use crate::tools::pow_generator::single_threaded_pow_generator::SingleThreadedPowGenerator;
887 use bytes::Bytes;
888
889 #[tokio::test]
890 #[allow(non_snake_case)]
891 async fn test_to_from_GetPostBundleResponseV1() -> anyhow::Result<()> {
892 let time_provider = RealTimeProvider;
893 let pow_generator = SingleThreadedPowGenerator::new();
894 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
895 let peer = server_id.to_peer(&time_provider)?;
896
897 let mut encoded_posts_ids = Vec::new();
898 let mut encoded_posts_lengths = Vec::new();
899 let mut encoded_posts_bytes = Vec::new();
900
901 for i in 0..16 {
902 let len = (i + 1) * 128;
903 let mut encoded_post_bytes = tools::random_bytes(len);
904 let post_id = Id::from_slice(&encoded_post_bytes[0..ID_BYTES])?;
905 encoded_posts_ids.push(post_id);
906 encoded_posts_lengths.push(encoded_post_bytes.len());
907 encoded_posts_bytes.append(&mut encoded_post_bytes);
908 }
909
910 let header = EncodedPostBundleHeaderV1 {
911 time_millis: time_provider.current_time_millis(),
912 location_id: Id::random(),
913 overflowed: false,
914 sealed: false,
915 num_posts: 0,
916 encoded_post_ids: encoded_posts_ids,
917 encoded_post_lengths: encoded_posts_lengths,
918 encoded_post_healed: HashSet::new(),
919 peer,
920 signature: Signature::zero(),
921 };
922
923 let peers_nearer = {
924 vec![
925 ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?.to_peer(&time_provider)?,
926 ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?.to_peer(&time_provider)?,
927 ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?.to_peer(&time_provider)?,
928 ]
929 };
930
931 let response = GetPostBundleResponseV1 {
932 peers_nearer,
933 cache_request_token: None,
934 post_bundles_cached: vec![],
935 post_bundle: Some((EncodedPostBundleV1 { header, encoded_posts_bytes: Bytes::from(encoded_posts_bytes) }).to_bytes()?),
936 };
937
938 let encoded = response.to_bytes_gatherer()?.to_bytes();
939 let response_decoded = GetPostBundleResponseV1::from_bytes(encoded)?;
940
941 assert_eq!(response_decoded, response);
942
943 Ok(())
944 }
945
946 async fn make_signed_header(time_provider: &RealTimeProvider) -> anyhow::Result<(EncodedPostBundleHeaderV1, ServerId)> {
947 let pow_generator = SingleThreadedPowGenerator::new();
948 let server_id = ServerId::new("own_pow", time_provider, config::SERVER_KEY_POW_MIN, true, &pow_generator).await?;
949 let peer = server_id.to_peer(time_provider)?;
950 let num_posts: u8 = 4;
951 let mut header = EncodedPostBundleHeaderV1 {
952 time_millis: time_provider.current_time_millis(),
953 location_id: Id::random(),
954 overflowed: false,
955 sealed: false,
956 num_posts,
957 encoded_post_ids: (0..num_posts).map(|_| Id::random()).collect(),
958 encoded_post_lengths: (0..num_posts).map(|i| (i as usize + 1) * 64).collect(),
959 encoded_post_healed: HashSet::new(),
960 peer,
961 signature: Signature::zero(),
962 };
963 header.signature_generate(&server_id.keys.signature_key)?;
964 Ok((header, server_id))
965 }
966
967 fn make_bucket_location() -> anyhow::Result<BucketLocation> {
968 BucketLocation::new(BucketType::User, Id::random(), MILLIS_IN_HOUR, TimeMillis(1_700_000_000_000))
969 }
970
971 #[tokio::test]
972 #[allow(non_snake_case)]
973 async fn test_SubmitPostClaimTokenV1_verify() -> anyhow::Result<()> {
974 let time_provider = RealTimeProvider;
975 let pow_generator = SingleThreadedPowGenerator::new();
976 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
977 let peer = server_id.to_peer(&time_provider)?;
978
979 let token = SubmitPostClaimTokenV1::new(peer, make_bucket_location()?, Id::random(), &server_id.keys.signature_key);
980 token.verify()?;
981 Ok(())
982 }
983
984 #[tokio::test]
985 #[allow(non_snake_case)]
986 async fn test_SubmitPostCommitTokenV1_verify() -> anyhow::Result<()> {
987 let time_provider = RealTimeProvider;
988 let pow_generator = SingleThreadedPowGenerator::new();
989 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
990 let peer = server_id.to_peer(&time_provider)?;
991
992 let token = SubmitPostCommitTokenV1::new(peer, make_bucket_location()?, Id::random(), &server_id.keys.signature_key);
993 token.verify()?;
994 Ok(())
995 }
996
997 #[tokio::test]
998 #[allow(non_snake_case)]
999 async fn test_to_from_HealPostBundleClaimV1() -> anyhow::Result<()> {
1000 let time_provider = RealTimeProvider;
1001 let (header, _) = make_signed_header(&time_provider).await?;
1002
1003 let bucket_location = make_bucket_location()?;
1004 let encoded = HealPostBundleClaimV1::new_to_bytes(&header, &bucket_location)?;
1005 let mut bytes = encoded;
1006 let decoded = HealPostBundleClaimV1::from_bytes(&mut bytes)?;
1007
1008 assert_eq!(decoded.donor_header, header);
1009 assert_eq!(decoded.bucket_location, bucket_location);
1010 Ok(())
1011 }
1012
1013 #[tokio::test]
1014 #[allow(non_snake_case)]
1015 async fn test_to_from_HealPostBundleClaimResponseV1() -> anyhow::Result<()> {
1016 let time_provider = RealTimeProvider;
1017 let (header, server_id) = make_signed_header(&time_provider).await?;
1018 let peer = server_id.to_peer(&time_provider)?;
1019
1020 let needed_post_ids = header.encoded_post_ids[0..2].to_vec();
1021 let token = HealPostBundleClaimTokenV1::new(
1022 peer,
1023 make_bucket_location()?,
1024 needed_post_ids.clone(),
1025 header.signature,
1026 &server_id.keys.signature_key,
1027 );
1028
1029 let response = HealPostBundleClaimResponseV1 { needed_post_ids, token: Some(token) };
1030
1031 let encoded = json::struct_to_bytes(&response)?;
1032 let decoded = json::bytes_to_struct::<HealPostBundleClaimResponseV1>(&encoded)?;
1033
1034 assert_eq!(decoded, response);
1035 Ok(())
1036 }
1037
1038 #[tokio::test]
1039 #[allow(non_snake_case)]
1040 async fn test_HealPostBundleClaimTokenV1_verify() -> anyhow::Result<()> {
1041 let time_provider = RealTimeProvider;
1042 let (header, server_id) = make_signed_header(&time_provider).await?;
1043 let peer = server_id.to_peer(&time_provider)?;
1044
1045 let token = HealPostBundleClaimTokenV1::new(
1046 peer,
1047 make_bucket_location()?,
1048 header.encoded_post_ids.clone(),
1049 header.signature,
1050 &server_id.keys.signature_key,
1051 );
1052
1053 token.verify()?;
1054 Ok(())
1055 }
1056
1057 #[tokio::test]
1058 #[allow(non_snake_case)]
1059 async fn test_to_from_HealPostBundleCommitV1() -> anyhow::Result<()> {
1060 let time_provider = RealTimeProvider;
1061 let (header, server_id) = make_signed_header(&time_provider).await?;
1062 let peer = server_id.to_peer(&time_provider)?;
1063
1064 let token = HealPostBundleClaimTokenV1::new(
1065 peer,
1066 make_bucket_location()?,
1067 header.encoded_post_ids.clone(),
1068 header.signature,
1069 &server_id.keys.signature_key,
1070 );
1071
1072 let post_bytes = tools::random_bytes(512);
1073 let encoded = HealPostBundleCommitV1::new_to_bytes(&token, &header, &post_bytes)?;
1074 let mut bytes = encoded;
1075 let decoded = HealPostBundleCommitV1::from_bytes(&mut bytes)?;
1076
1077 assert_eq!(decoded.token, token);
1078 assert_eq!(decoded.donor_header, header);
1079 assert_eq!(decoded.encoded_posts_bytes.as_ref(), post_bytes.as_slice());
1080 Ok(())
1081 }
1082
1083 #[tokio::test]
1084 #[allow(non_snake_case)]
1085 async fn test_to_from_HealPostBundleCommitResponseV1() -> anyhow::Result<()> {
1086 for accepted in [true, false] {
1087 let response = HealPostBundleCommitResponseV1 { accepted };
1088 let encoded = json::struct_to_bytes(&response)?;
1089 let decoded = json::bytes_to_struct::<HealPostBundleCommitResponseV1>(&encoded)?;
1090 assert_eq!(decoded, response);
1091 }
1092 Ok(())
1093 }
1094
1095 #[tokio::test]
1096 #[allow(non_snake_case)]
1097 async fn test_CacheRequestTokenV1_verify() -> anyhow::Result<()> {
1098 use crate::protocol::payload::payload::CacheRequestTokenV1;
1099 use crate::tools::time::MILLIS_IN_MINUTE;
1100
1101 let time_provider = RealTimeProvider;
1102 let pow_generator = SingleThreadedPowGenerator::new();
1103 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
1104 let peer = server_id.to_peer(&time_provider)?;
1105 let bucket_location = make_bucket_location()?;
1106 let expires_at = time_provider.current_time_millis() + MILLIS_IN_MINUTE;
1107 let already_cached_peer_ids = vec![Id::random(), Id::random()];
1108
1109 let token = CacheRequestTokenV1::new(peer, bucket_location, expires_at, already_cached_peer_ids, &server_id.keys.signature_key);
1110 token.verify()?;
1111 Ok(())
1112 }
1113
1114 #[tokio::test]
1115 #[allow(non_snake_case)]
1116 async fn test_to_from_CachePostBundleV1() -> anyhow::Result<()> {
1117 use crate::protocol::payload::payload::{CachePostBundleV1, CacheRequestTokenV1};
1118 use crate::tools::time::MILLIS_IN_MINUTE;
1119
1120 let time_provider = RealTimeProvider;
1121 let pow_generator = SingleThreadedPowGenerator::new();
1122 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
1123 let peer = server_id.to_peer(&time_provider)?;
1124 let bucket_location = make_bucket_location()?;
1125 let expires_at = time_provider.current_time_millis() + MILLIS_IN_MINUTE;
1126 let token = CacheRequestTokenV1::new(peer, bucket_location, expires_at, vec![], &server_id.keys.signature_key);
1127
1128 let bundle_a = tools::random_bytes(512);
1130 let bundle_b = tools::random_bytes(256);
1131 let bundle_c = tools::random_bytes(1024);
1132 let bundles: &[&[u8]] = &[&bundle_a, &bundle_b, &bundle_c];
1133
1134 let encoded = CachePostBundleV1::new_to_bytes(&token, bundles)?;
1135 let mut bytes = encoded;
1136 let decoded = CachePostBundleV1::from_bytes(&mut bytes)?;
1137
1138 assert_eq!(decoded.token, token);
1139 assert_eq!(decoded.encoded_post_bundles.len(), 3);
1140 assert_eq!(decoded.encoded_post_bundles[0].as_ref(), bundle_a.as_slice());
1141 assert_eq!(decoded.encoded_post_bundles[1].as_ref(), bundle_b.as_slice());
1142 assert_eq!(decoded.encoded_post_bundles[2].as_ref(), bundle_c.as_slice());
1143 Ok(())
1144 }
1145
1146 #[tokio::test]
1147 #[allow(non_snake_case)]
1148 async fn test_to_from_CachePostBundleV1_empty() -> anyhow::Result<()> {
1149 use crate::protocol::payload::payload::{CachePostBundleV1, CacheRequestTokenV1};
1150 use crate::tools::time::MILLIS_IN_MINUTE;
1151
1152 let time_provider = RealTimeProvider;
1153 let pow_generator = SingleThreadedPowGenerator::new();
1154 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
1155 let peer = server_id.to_peer(&time_provider)?;
1156 let bucket_location = make_bucket_location()?;
1157 let expires_at = time_provider.current_time_millis() + MILLIS_IN_MINUTE;
1158 let token = CacheRequestTokenV1::new(peer, bucket_location, expires_at, vec![], &server_id.keys.signature_key);
1159
1160 let encoded = CachePostBundleV1::new_to_bytes(&token, &[])?;
1161 let mut bytes = encoded;
1162 let decoded = CachePostBundleV1::from_bytes(&mut bytes)?;
1163
1164 assert_eq!(decoded.token, token);
1165 assert!(decoded.encoded_post_bundles.is_empty());
1166 Ok(())
1167 }
1168
1169 #[tokio::test]
1170 #[allow(non_snake_case)]
1171 async fn test_to_from_CachePostBundleFeedbackV1() -> anyhow::Result<()> {
1172 use crate::protocol::payload::payload::{CachePostBundleFeedbackV1, CacheRequestTokenV1};
1173 use crate::tools::time::MILLIS_IN_MINUTE;
1174
1175 let time_provider = RealTimeProvider;
1176 let pow_generator = SingleThreadedPowGenerator::new();
1177 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
1178 let peer = server_id.to_peer(&time_provider)?;
1179 let bucket_location = make_bucket_location()?;
1180 let expires_at = time_provider.current_time_millis() + MILLIS_IN_MINUTE;
1181 let already_cached_peer_ids = vec![Id::random()];
1182 let token = CacheRequestTokenV1::new(peer, bucket_location, expires_at, already_cached_peer_ids, &server_id.keys.signature_key);
1183
1184 let feedback_bytes = tools::random_bytes(256);
1185 let encoded = CachePostBundleFeedbackV1::new_to_bytes(&token, &feedback_bytes)?;
1186 let mut bytes = encoded;
1187 let decoded = CachePostBundleFeedbackV1::from_bytes(&mut bytes)?;
1188
1189 assert_eq!(decoded.token, token);
1190 assert_eq!(decoded.encoded_post_bundle_feedback_bytes.as_ref(), feedback_bytes.as_slice());
1191 Ok(())
1192 }
1193
1194 #[tokio::test]
1195 #[allow(non_snake_case)]
1196 async fn test_to_from_FetchUrlPreviewV1() -> anyhow::Result<()> {
1197 use crate::protocol::payload::payload::FetchUrlPreviewV1;
1198
1199 let url = "https://example.com/article?q=1#section";
1200 let encoded = FetchUrlPreviewV1::new_to_bytes(url)?;
1201 let mut bytes = encoded;
1202 let decoded = FetchUrlPreviewV1::from_bytes(&mut bytes)?;
1203
1204 assert_eq!(decoded.url, url);
1205 Ok(())
1206 }
1207
1208 #[tokio::test]
1209 #[allow(non_snake_case)]
1210 async fn test_to_from_FetchUrlPreviewResponseV1() -> anyhow::Result<()> {
1211 use crate::protocol::payload::payload::FetchUrlPreviewResponseV1;
1212
1213 let response = FetchUrlPreviewResponseV1 {
1214 url: "https://example.com/canonical".to_string(),
1215 title: "Example Title".to_string(),
1216 description: "A short description.".to_string(),
1217 image_url: "https://example.com/image.png".to_string(),
1218 };
1219
1220 let encoded = response.to_bytes()?;
1221 let decoded = FetchUrlPreviewResponseV1::from_bytes(&encoded)?;
1222
1223 assert_eq!(decoded, response);
1224 Ok(())
1225 }
1226
1227 #[test]
1230 #[allow(non_snake_case)]
1231 fn test_CachePostBundleV1_from_bytes_empty_input() {
1232 use crate::protocol::payload::payload::CachePostBundleV1;
1233 let mut bytes = Bytes::new();
1234 assert!(CachePostBundleV1::from_bytes(&mut bytes).is_err());
1235 }
1236
1237 #[test]
1238 #[allow(non_snake_case)]
1239 fn test_CachePostBundleV1_from_bytes_garbage_input() {
1240 use crate::protocol::payload::payload::CachePostBundleV1;
1241 let mut bytes = Bytes::from_static(&[0xff; 64]);
1242 assert!(CachePostBundleV1::from_bytes(&mut bytes).is_err());
1243 }
1244
1245 #[test]
1246 #[allow(non_snake_case)]
1247 fn test_CachePostBundleV1_from_bytes_truncated_count() {
1248 use crate::protocol::payload::payload::CachePostBundleV1;
1249 let mut bytes = Bytes::from_static(&[0x01]);
1253 assert!(CachePostBundleV1::from_bytes(&mut bytes).is_err());
1254 }
1255
1256 #[test]
1259 #[allow(non_snake_case)]
1260 fn test_GetPostBundleResponseV1_from_bytes_empty_input() {
1261 let bytes = Bytes::new();
1262 assert!(GetPostBundleResponseV1::from_bytes(bytes).is_err());
1263 }
1264
1265 #[test]
1266 #[allow(non_snake_case)]
1267 fn test_GetPostBundleResponseV1_from_bytes_garbage_input() {
1268 let bytes = Bytes::from_static(&[0xff; 128]);
1269 assert!(GetPostBundleResponseV1::from_bytes(bytes).is_err());
1270 }
1271
1272 #[test]
1275 #[allow(non_snake_case)]
1276 fn test_HealPostBundleClaimV1_from_bytes_empty_input() {
1277 let mut bytes = Bytes::new();
1278 assert!(HealPostBundleClaimV1::from_bytes(&mut bytes).is_err());
1279 }
1280
1281 #[test]
1282 #[allow(non_snake_case)]
1283 fn test_HealPostBundleClaimV1_from_bytes_garbage_input() {
1284 let mut bytes = Bytes::from_static(&[0xff; 64]);
1285 assert!(HealPostBundleClaimV1::from_bytes(&mut bytes).is_err());
1286 }
1287
1288 #[test]
1291 #[allow(non_snake_case)]
1292 fn test_HealPostBundleCommitV1_from_bytes_empty_input() {
1293 let mut bytes = Bytes::new();
1294 assert!(HealPostBundleCommitV1::from_bytes(&mut bytes).is_err());
1295 }
1296
1297 #[test]
1298 #[allow(non_snake_case)]
1299 fn test_HealPostBundleCommitV1_from_bytes_garbage_input() {
1300 let mut bytes = Bytes::from_static(&[0xff; 64]);
1301 assert!(HealPostBundleCommitV1::from_bytes(&mut bytes).is_err());
1302 }
1303
1304 #[test]
1307 #[allow(non_snake_case)]
1308 fn test_FetchUrlPreviewV1_from_bytes_empty_input() {
1309 use crate::protocol::payload::payload::FetchUrlPreviewV1;
1310 let mut bytes = Bytes::new();
1311 assert!(FetchUrlPreviewV1::from_bytes(&mut bytes).is_err());
1312 }
1313
1314 #[test]
1317 #[allow(non_snake_case)]
1318 fn test_FetchUrlPreviewResponseV1_from_bytes_empty_input() {
1319 use crate::protocol::payload::payload::FetchUrlPreviewResponseV1;
1320 let bytes = Bytes::new();
1321 assert!(FetchUrlPreviewResponseV1::from_bytes(&bytes).is_err());
1322 }
1323
1324 #[test]
1325 #[allow(non_snake_case)]
1326 fn test_FetchUrlPreviewResponseV1_from_bytes_garbage_input() {
1327 use crate::protocol::payload::payload::FetchUrlPreviewResponseV1;
1328 let bytes = Bytes::from_static(&[0xff; 64]);
1329 assert!(FetchUrlPreviewResponseV1::from_bytes(&bytes).is_err());
1330 }
1331
1332 #[test]
1335 #[allow(non_snake_case)]
1336 fn test_SubmitPostClaimV1_from_bytes_empty_input() {
1337 use crate::protocol::payload::payload::SubmitPostClaimV1;
1338 let mut bytes = Bytes::new();
1339 assert!(SubmitPostClaimV1::from_bytes(&mut bytes).is_err());
1340 }
1341
1342 #[test]
1343 #[allow(non_snake_case)]
1344 fn test_SubmitPostClaimV1_from_bytes_garbage_input() {
1345 use crate::protocol::payload::payload::SubmitPostClaimV1;
1346 let mut bytes = Bytes::from_static(&[0xff; 64]);
1347 assert!(SubmitPostClaimV1::from_bytes(&mut bytes).is_err());
1348 }
1349
1350 #[test]
1353 #[allow(non_snake_case)]
1354 fn test_SubmitPostCommitV1_from_bytes_empty_input() {
1355 use crate::protocol::payload::payload::SubmitPostCommitV1;
1356 let mut bytes = Bytes::new();
1357 assert!(SubmitPostCommitV1::from_bytes(&mut bytes).is_err());
1358 }
1359
1360 #[test]
1363 #[allow(non_snake_case)]
1364 fn test_SubmitPostFeedbackV1_from_bytes_empty_input() {
1365 use crate::protocol::payload::payload::SubmitPostFeedbackV1;
1366 let mut bytes = Bytes::new();
1367 assert!(SubmitPostFeedbackV1::from_bytes(&mut bytes).is_err());
1368 }
1369
1370 #[test]
1373 #[allow(non_snake_case)]
1374 fn test_GetPostBundleFeedbackResponseV1_from_bytes_empty_input() {
1375 use crate::protocol::payload::payload::GetPostBundleFeedbackResponseV1;
1376 let bytes = Bytes::new();
1377 assert!(GetPostBundleFeedbackResponseV1::from_bytes(bytes).is_err());
1378 }
1379
1380 #[test]
1383 #[allow(non_snake_case)]
1384 fn test_CachePostBundleFeedbackV1_from_bytes_empty_input() {
1385 use crate::protocol::payload::payload::CachePostBundleFeedbackV1;
1386 let mut bytes = Bytes::new();
1387 assert!(CachePostBundleFeedbackV1::from_bytes(&mut bytes).is_err());
1388 }
1389
1390 #[cfg(not(target_arch = "wasm32"))]
1391 mod bolero_fuzz {
1392 use bytes::Bytes;
1393
1394 #[test]
1395 #[allow(non_snake_case)]
1396 fn fuzz_CachePostBundleV1_from_bytes() {
1397 use crate::protocol::payload::payload::CachePostBundleV1;
1398 bolero::check!().for_each(|data: &[u8]| {
1399 let mut bytes = Bytes::copy_from_slice(data);
1400 let _ = CachePostBundleV1::from_bytes(&mut bytes);
1401 });
1402 }
1403
1404 #[test]
1405 #[allow(non_snake_case)]
1406 fn fuzz_GetPostBundleResponseV1_from_bytes() {
1407 use crate::protocol::payload::payload::GetPostBundleResponseV1;
1408 bolero::check!().for_each(|data: &[u8]| {
1409 let _ = GetPostBundleResponseV1::from_bytes(Bytes::copy_from_slice(data));
1410 });
1411 }
1412
1413 #[test]
1414 #[allow(non_snake_case)]
1415 fn fuzz_GetPostBundleFeedbackResponseV1_from_bytes() {
1416 use crate::protocol::payload::payload::GetPostBundleFeedbackResponseV1;
1417 bolero::check!().for_each(|data: &[u8]| {
1418 let _ = GetPostBundleFeedbackResponseV1::from_bytes(Bytes::copy_from_slice(data));
1419 });
1420 }
1421
1422 #[test]
1423 #[allow(non_snake_case)]
1424 fn fuzz_SubmitPostClaimV1_from_bytes() {
1425 use crate::protocol::payload::payload::SubmitPostClaimV1;
1426 bolero::check!().for_each(|data: &[u8]| {
1427 let mut bytes = Bytes::copy_from_slice(data);
1428 let _ = SubmitPostClaimV1::from_bytes(&mut bytes);
1429 });
1430 }
1431
1432 #[test]
1433 #[allow(non_snake_case)]
1434 fn fuzz_HealPostBundleClaimV1_from_bytes() {
1435 use crate::protocol::payload::payload::HealPostBundleClaimV1;
1436 bolero::check!().for_each(|data: &[u8]| {
1437 let mut bytes = Bytes::copy_from_slice(data);
1438 let _ = HealPostBundleClaimV1::from_bytes(&mut bytes);
1439 });
1440 }
1441
1442 #[test]
1443 #[allow(non_snake_case)]
1444 fn fuzz_HealPostBundleCommitV1_from_bytes() {
1445 use crate::protocol::payload::payload::HealPostBundleCommitV1;
1446 bolero::check!().for_each(|data: &[u8]| {
1447 let mut bytes = Bytes::copy_from_slice(data);
1448 let _ = HealPostBundleCommitV1::from_bytes(&mut bytes);
1449 });
1450 }
1451 }
1452
1453 #[test]
1454 fn test_payload_kind_wire_values_and_display_are_stable() {
1455 use crate::protocol::payload::payload::{PayloadRequestKind, PayloadResponseKind};
1457 assert_eq!(PayloadRequestKind::from_u16(0).unwrap(), PayloadRequestKind::ErrorV1);
1458 assert_eq!(PayloadRequestKind::from_u16(3).unwrap(), PayloadRequestKind::RetiredAnnounceV1, "retired slot must stay reserved so later kinds keep their wire values");
1459 assert_eq!(PayloadResponseKind::from_u16(3).unwrap(), PayloadResponseKind::RetiredAnnounceResponseV1);
1460 assert_eq!(PayloadRequestKind::from_u16(4).unwrap(), PayloadRequestKind::GetPostBundleV1);
1461 assert_eq!(PayloadResponseKind::from_u16(4).unwrap(), PayloadResponseKind::GetPostBundleResponseV1);
1462 assert_eq!(PayloadRequestKind::from_u16(17).unwrap(), PayloadRequestKind::AnnounceV2);
1463 assert_eq!(PayloadResponseKind::from_u16(0).unwrap(), PayloadResponseKind::ErrorResponseV1);
1464 assert_eq!(PayloadResponseKind::from_u16(17).unwrap(), PayloadResponseKind::AnnounceResponseV2);
1465 assert!(PayloadResponseKind::from_u16(18).is_err());
1466 assert_eq!(PayloadRequestKind::GetPostBundleV1.to_string(), "GetPostBundleV1");
1467 assert_eq!(PayloadResponseKind::AnnounceResponseV2.to_string(), "AnnounceResponseV2");
1468 }
1469
1470 #[test]
1471 #[allow(non_snake_case)]
1472 fn test_PAYLOAD_REQUEST_KIND_COUNT_matches_variants() {
1473 use crate::protocol::payload::payload::{PayloadRequestKind, PAYLOAD_REQUEST_KIND_COUNT};
1474 let last_variant = PayloadRequestKind::from_u16((PAYLOAD_REQUEST_KIND_COUNT - 1) as u16)
1475 .expect("PAYLOAD_REQUEST_KIND_COUNT - 1 must decode to a valid variant");
1476 assert_eq!(last_variant, PayloadRequestKind::AnnounceV2, "last variant changed; bump PAYLOAD_REQUEST_KIND_COUNT");
1477 assert!(PayloadRequestKind::from_u16(PAYLOAD_REQUEST_KIND_COUNT as u16).is_err(), "PAYLOAD_REQUEST_KIND_COUNT must equal the variant count");
1478 }
1479
1480 #[test]
1481 #[allow(non_snake_case)]
1482 fn test_to_from_PeerStatsRequestV1() -> anyhow::Result<()> {
1483 use crate::protocol::payload::payload::PeerStatsRequestV1;
1484 let request = PeerStatsRequestV1 {};
1485 let encoded = request.to_bytes()?;
1486 let decoded = PeerStatsRequestV1::from_bytes(&encoded)?;
1487 assert_eq!(decoded, request);
1488 Ok(())
1489 }
1490
1491 #[tokio::test]
1492 #[allow(non_snake_case)]
1493 async fn test_to_from_PeerStatsResponseV1() -> anyhow::Result<()> {
1494 use crate::protocol::payload::payload::PeerStatsResponseV1;
1495 use crate::tools::compression;
1496
1497 let time_provider = RealTimeProvider;
1498 let pow_generator = SingleThreadedPowGenerator::new();
1499 let server_id = ServerId::new("own_pow", &time_provider, Pow(4), true, &pow_generator).await?;
1500 let peer = server_id.to_peer(&time_provider)?;
1501
1502 let doc = serde_json::json!({ "requests": { "PingV1": 7 }, "system": { "memory_total_bytes": 1234 } });
1503 let json_bytes = serde_json::to_vec(&doc)?;
1504 let json_compressed = compression::compress_for_speed(&json_bytes)?.to_bytes();
1505
1506 let timestamp = time_provider.current_time_millis();
1507 let signing_input = PeerStatsResponseV1::signing_input(timestamp, &json_compressed);
1508 let signature = crate::tools::signing::sign(&server_id.keys.signature_key, &signing_input);
1509
1510 let response = PeerStatsResponseV1 { peer, timestamp, json_compressed, signature };
1511 let encoded = response.to_bytes()?;
1512 let decoded = PeerStatsResponseV1::from_bytes(&encoded)?;
1513 assert_eq!(decoded, response);
1514 Ok(())
1515 }
1516}