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