vortex_protocol/tlv/
persistent_piece_content.rs

1/*
2 *     Copyright 2025 The Dragonfly Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::error::{Error, Result};
18use bytes::{BufMut, Bytes, BytesMut};
19use chrono::{DateTime, NaiveDateTime};
20use std::convert::TryFrom;
21use std::time::Duration;
22
23/// METADATA_LENGTH_SIZE is the size of the metadata length in bytes.
24pub const METADATA_LENGTH_SIZE: usize = 4;
25
26/// NUMBER_SIZE is the size of the piece number in bytes.
27const NUMBER_SIZE: usize = 4;
28
29/// OFFSET_SIZE is the size of the offset in bytes.
30const OFFSET_SIZE: usize = 8;
31
32/// LENGTH_SIZE is the size of the length in bytes.
33const LENGTH_SIZE: usize = 8;
34
35/// DIGEST_LENGTH_SIZE is the size of the digest length in bytes.
36const DIGEST_LENGTH_SIZE: usize = 4;
37
38/// PARENT_ID_LENGTH_SIZE is the size of the parent ID length in bytes.
39const PARENT_ID_LENGTH_SIZE: usize = 4;
40
41/// TRAFFIC_TYPE_SIZE is the size of the traffic type in bytes.
42const TRAFFIC_TYPE_SIZE: usize = 1;
43
44/// COST_SIZE is the size of the cost in bytes.
45const COST_SIZE: usize = 8;
46
47/// CREATED_AT_SIZE is the size of the created at in bytes.
48const CREATED_AT_SIZE: usize = 8;
49
50/// PersistentPieceContent represents a persistent piece metadata and piece content request.
51///
52/// Value Format:
53///   - Metadata Length (4 bytes): Length of the metadata section.
54///   - Number (4 bytes): Piece number to download.
55///   - Offset (8 bytes): Byte offset in the file.
56///   - Length (8 bytes): Length of the piece in bytes.
57///   - Digest Length (4 bytes): Length of the digest field.
58///   - Digest (variable): CRC32 hash of the piece content.
59///   - Parent ID Length (4 bytes): Length of the parent task identifier.
60///   - Parent ID (variable): Parent task identifier.
61///   - Traffic Type (1 byte): Network traffic classification type.
62///   - Cost (8 bytes): Download cost in seconds.
63///   - Created At (8 bytes): Creation timestamp as Unix epoch seconds.
64///   - Content (variable): Piece content bytes.
65///
66/// ```text
67/// -------------------------------------------------------------------------------------------------------------------------------------
68/// | Metadata Length (4 bytes) | Number (4 bytes) |  Offset (8 bytes) |  Length (8 bytes) | Digest Length(8 bytes) | Digest (variable) |
69/// ------------------------------------------------------------------------------------------------------------------------------------------
70/// | Parent ID Length(4 bytes) | Parent ID (variable) | Traffic Type (1 byte) | Cost (8 bytes) | Created At (8 bytes) |  Content (variable) |
71/// ------------------------------------------------------------------------------------------------------------------------------------------
72/// ```
73#[derive(Debug, Clone)]
74pub struct PersistentPieceContent {
75    metadata_length: u32,
76    metadata: PersistentPieceMetadata,
77}
78
79/// PersistentPieceContent implements the PersistentPieceContent functions.
80impl PersistentPieceContent {
81    /// new creates a new PersistentPieceContent request.
82    #[allow(clippy::too_many_arguments)]
83    pub fn new(
84        number: u32,
85        offset: u64,
86        length: u64,
87        digest: String,
88        parent_id: String,
89        traffic_type: u8,
90        cost: Duration,
91        created_at: NaiveDateTime,
92    ) -> Self {
93        Self {
94            metadata_length: (NUMBER_SIZE
95                + OFFSET_SIZE
96                + LENGTH_SIZE
97                + DIGEST_LENGTH_SIZE
98                + digest.len()
99                + PARENT_ID_LENGTH_SIZE
100                + parent_id.len()
101                + TRAFFIC_TYPE_SIZE
102                + COST_SIZE
103                + CREATED_AT_SIZE) as u32,
104            metadata: PersistentPieceMetadata {
105                number,
106                offset,
107                length,
108                digest,
109                parent_id,
110                traffic_type,
111                cost,
112                created_at,
113            },
114        }
115    }
116
117    ///  metadata returns the persistent piece metadata.
118    pub fn metadata(&self) -> PersistentPieceMetadata {
119        self.metadata.clone()
120    }
121
122    /// metadata_len returns the length of the metadata section.
123    pub fn metadata_len(&self) -> u32 {
124        self.metadata_length
125    }
126
127    /// is_empty returns whether the persistent piece content request is empty.
128    pub fn is_empty(&self) -> bool {
129        self.metadata.length == 0
130    }
131}
132
133/// Implement TryFrom<Bytes> for PersistentPieceContent for conversion from a byte slice.
134impl TryFrom<Bytes> for PersistentPieceContent {
135    type Error = Error;
136
137    /// try_from decodes the persistent piece content request from the byte slice.
138    fn try_from(bytes: Bytes) -> Result<Self> {
139        let metadata_length = u32::from_be_bytes(
140            bytes
141                .get(..METADATA_LENGTH_SIZE)
142                .ok_or(Error::InvalidPacket(
143                    "insufficient bytes for metadata length".to_string(),
144                ))?
145                .try_into()?,
146        );
147
148        if bytes.len() != METADATA_LENGTH_SIZE + metadata_length as usize {
149            return Err(Error::InvalidPacket(format!(
150                "expected {} bytes for PersistentPieceContent, got {}",
151                METADATA_LENGTH_SIZE + metadata_length as usize,
152                bytes.len()
153            )));
154        }
155
156        let metadata = (
157            bytes.slice(METADATA_LENGTH_SIZE..METADATA_LENGTH_SIZE + metadata_length as usize),
158            metadata_length,
159        )
160            .try_into()?;
161
162        Ok(PersistentPieceContent {
163            metadata_length,
164            metadata,
165        })
166    }
167}
168
169/// Implement From<PersistentPieceContent> for Bytes for conversion to a byte slice.
170impl From<PersistentPieceContent> for Bytes {
171    /// from converts the persistent piece content request to a byte slice.
172    fn from(content: PersistentPieceContent) -> Bytes {
173        let (metadata_bytes, metadata_length) = content.metadata.into();
174        let mut bytes = BytesMut::with_capacity(METADATA_LENGTH_SIZE + metadata_length as usize);
175        bytes.put_u32(metadata_length);
176        bytes.extend_from_slice(&metadata_bytes);
177        bytes.freeze()
178    }
179}
180
181/// PersistentPieceMetadata holds the metadata information for a persistent piece.
182#[derive(Debug, Clone)]
183pub struct PersistentPieceMetadata {
184    pub number: u32,
185    pub offset: u64,
186    pub length: u64,
187    pub digest: String,
188    pub parent_id: String,
189    pub traffic_type: u8,
190    pub cost: Duration,
191    pub created_at: NaiveDateTime,
192}
193
194/// PersistentPieceMetadata implements the PersistentPieceMetadata functions.
195impl PersistentPieceMetadata {
196    /// new creates a new PersistentPieceMetadata.
197    #[allow(clippy::too_many_arguments)]
198    pub fn new(
199        number: u32,
200        offset: u64,
201        length: u64,
202        digest: String,
203        parent_id: String,
204        traffic_type: u8,
205        cost: Duration,
206        created_at: NaiveDateTime,
207    ) -> Self {
208        Self {
209            number,
210            offset,
211            length,
212            digest,
213            parent_id,
214            traffic_type,
215            cost,
216            created_at,
217        }
218    }
219}
220
221/// Implement TryFrom<Bytes> for PersistentPieceMetadata for conversion from a byte slice.
222impl TryFrom<(Bytes, u32)> for PersistentPieceMetadata {
223    type Error = Error;
224
225    /// try_from decodes the persistent piece metadata request from the byte slice.
226    fn try_from(input: (Bytes, u32)) -> Result<Self> {
227        let (bytes, length) = input;
228        if bytes.len() != length as usize {
229            return Err(Error::InvalidLength(format!(
230                "expected {} bytes for PersistentPieceMetadata, got {}",
231                length,
232                bytes.len()
233            )));
234        }
235
236        let mut bytes_offset = 0;
237        let number = u32::from_be_bytes(
238            bytes
239                .get(bytes_offset..bytes_offset + NUMBER_SIZE)
240                .ok_or(Error::InvalidPacket(
241                    "insufficient bytes for piece number".to_string(),
242                ))?
243                .try_into()?,
244        );
245        bytes_offset += NUMBER_SIZE;
246
247        let offset = u64::from_be_bytes(
248            bytes
249                .get(bytes_offset..bytes_offset + OFFSET_SIZE)
250                .ok_or(Error::InvalidPacket(
251                    "insufficient bytes for piece offset".to_string(),
252                ))?
253                .try_into()?,
254        );
255        bytes_offset += OFFSET_SIZE;
256
257        let length = u64::from_be_bytes(
258            bytes
259                .get(bytes_offset..bytes_offset + LENGTH_SIZE)
260                .ok_or(Error::InvalidPacket(
261                    "insufficient bytes for piece length".to_string(),
262                ))?
263                .try_into()?,
264        );
265
266        bytes_offset += LENGTH_SIZE;
267
268        let digest_length = u32::from_be_bytes(
269            bytes
270                .get(bytes_offset..bytes_offset + DIGEST_LENGTH_SIZE)
271                .ok_or(Error::InvalidPacket(
272                    "insufficient bytes for digest length".to_string(),
273                ))?
274                .try_into()?,
275        ) as usize;
276        bytes_offset += DIGEST_LENGTH_SIZE;
277
278        let digest = String::from_utf8(
279            bytes
280                .get(bytes_offset..bytes_offset + digest_length)
281                .ok_or(Error::InvalidPacket(
282                    "insufficient bytes for digest length".to_string(),
283                ))?
284                .to_vec(),
285        )?;
286        bytes_offset += digest_length;
287
288        let parent_id_length = u32::from_be_bytes(
289            bytes
290                .get(bytes_offset..bytes_offset + PARENT_ID_LENGTH_SIZE)
291                .ok_or(Error::InvalidPacket(
292                    "insufficient bytes for parent id length".to_string(),
293                ))?
294                .try_into()?,
295        ) as usize;
296        bytes_offset += PARENT_ID_LENGTH_SIZE;
297
298        let parent_id = String::from_utf8(
299            bytes
300                .get(bytes_offset..bytes_offset + parent_id_length)
301                .ok_or(Error::InvalidPacket(
302                    "insufficient bytes for parent id".to_string(),
303                ))?
304                .to_vec(),
305        )?;
306        bytes_offset += parent_id_length;
307
308        let traffic_type = bytes
309            .get(bytes_offset)
310            .ok_or(Error::InvalidPacket(
311                "insufficient bytes for traffic type".to_string(),
312            ))?
313            .to_owned();
314        bytes_offset += TRAFFIC_TYPE_SIZE;
315
316        let cost = Duration::from_secs(u64::from_be_bytes(
317            bytes
318                .get(bytes_offset..bytes_offset + COST_SIZE)
319                .ok_or(Error::InvalidPacket(
320                    "insufficient bytes for cost".to_string(),
321                ))?
322                .try_into()?,
323        ));
324        bytes_offset += COST_SIZE;
325
326        let created_at = DateTime::from_timestamp(
327            i64::from_be_bytes(
328                bytes
329                    .get(bytes_offset..bytes_offset + CREATED_AT_SIZE)
330                    .ok_or(Error::InvalidPacket(
331                        "insufficient bytes for created_at".to_string(),
332                    ))?
333                    .try_into()?,
334            ),
335            0,
336        )
337        .ok_or_else(|| Error::InvalidPacket("invalid timestamp for created_at".to_string()))?
338        .naive_utc();
339        Ok(PersistentPieceMetadata {
340            number,
341            offset,
342            length,
343            digest,
344            parent_id,
345            traffic_type,
346            cost,
347            created_at,
348        })
349    }
350}
351
352/// Implement From<PersistentPieceMetadata> for Bytes for conversion to a byte slice.
353impl From<PersistentPieceMetadata> for (Bytes, u32) {
354    /// from converts the persistent piece metadata request to a byte slice.
355    fn from(metadata: PersistentPieceMetadata) -> (Bytes, u32) {
356        let PersistentPieceMetadata {
357            number,
358            offset,
359            length,
360            digest,
361            parent_id,
362            traffic_type,
363            cost,
364            created_at,
365        } = metadata;
366
367        let parent_id = parent_id.as_bytes();
368        let bytes_length = NUMBER_SIZE
369            + OFFSET_SIZE
370            + LENGTH_SIZE
371            + DIGEST_LENGTH_SIZE
372            + digest.len()
373            + PARENT_ID_LENGTH_SIZE
374            + parent_id.len()
375            + TRAFFIC_TYPE_SIZE
376            + COST_SIZE
377            + CREATED_AT_SIZE;
378
379        let mut bytes = BytesMut::with_capacity(bytes_length);
380        bytes.put_u32(number);
381        bytes.put_u64(offset);
382        bytes.put_u64(length);
383        bytes.put_u32(digest.len() as u32);
384        bytes.extend_from_slice(digest.as_bytes());
385        bytes.put_u32(parent_id.len() as u32);
386        bytes.extend_from_slice(parent_id);
387        bytes.put_u8(traffic_type);
388        bytes.put_u64(cost.as_secs());
389        bytes.put_i64(created_at.and_utc().timestamp());
390        (bytes.freeze(), bytes_length as u32)
391    }
392}
393
394#[cfg(test)]
395mod tests {
396    use super::*;
397    use bytes::Bytes;
398    use std::time::Duration;
399
400    fn create_test_persistent_piece_content() -> PersistentPieceContent {
401        PersistentPieceContent::new(
402            42,
403            1024,
404            2048,
405            "a".repeat(32),
406            "test_parent_id".to_string(),
407            1,
408            Duration::from_secs(5),
409            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
410        )
411    }
412
413    fn create_test_metadata() -> PersistentPieceMetadata {
414        PersistentPieceMetadata {
415            number: 42,
416            offset: 1024,
417            length: 2048,
418            digest: "a".repeat(32),
419            parent_id: "test_parent_id".to_string(),
420            traffic_type: 1,
421            cost: Duration::from_secs(5),
422            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
423        }
424    }
425
426    #[test]
427    fn test_persistent_piece_content_conversion_roundtrip() {
428        let original = create_test_persistent_piece_content();
429        let bytes = Bytes::from(original.clone());
430        let result = PersistentPieceContent::try_from(bytes).unwrap();
431
432        assert_eq!(result.metadata().number, original.metadata().number);
433        assert_eq!(result.metadata().offset, original.metadata().offset);
434        assert_eq!(result.metadata().length, original.metadata().length);
435        assert_eq!(result.metadata().digest, original.metadata().digest);
436        assert_eq!(result.metadata().parent_id, original.metadata().parent_id);
437        assert_eq!(
438            result.metadata().traffic_type,
439            original.metadata().traffic_type
440        );
441        assert_eq!(result.metadata().cost, original.metadata().cost);
442        assert_eq!(result.metadata().created_at, original.metadata().created_at);
443        assert_eq!(result.metadata_len(), original.metadata_len());
444    }
445
446    #[test]
447    fn test_persistent_piece_content_try_from_insufficient_bytes_for_metadata_length() {
448        let short_bytes = Bytes::from(vec![0u8; 4]); // Less than METADATA_LENGTH_SIZE (8)
449        let result = PersistentPieceContent::try_from(short_bytes);
450
451        assert!(result.is_err());
452        assert!(matches!(result.unwrap_err(), Error::InvalidPacket(_)));
453    }
454
455    #[test]
456    fn test_persistent_piece_content_try_from_insufficient_metadata_bytes() {
457        let mut bytes = BytesMut::new();
458        bytes.put_u32(100); // metadata_length = 100
459        bytes.put(&vec![0u8; 50][..]); // But only provide 50 bytes of metadata
460
461        let result = PersistentPieceContent::try_from(bytes.freeze());
462        assert!(result.is_err());
463        assert!(matches!(result.unwrap_err(), Error::InvalidPacket(_)));
464    }
465
466    #[test]
467    fn test_persistent_piece_content_with_empty_parent_id() {
468        let persistent_piece_content = PersistentPieceContent::new(
469            1,
470            0,
471            100,
472            "b".repeat(32),
473            String::new(), // Empty parent_id
474            2,
475            Duration::from_secs(1),
476            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
477        );
478
479        let bytes = Bytes::from(persistent_piece_content.clone());
480        let result = PersistentPieceContent::try_from(bytes).unwrap();
481
482        assert_eq!(result.metadata().parent_id, "");
483        assert_eq!(result.metadata().number, 1);
484        assert_eq!(result.metadata().traffic_type, 2);
485        assert_eq!(result.metadata().length, 100);
486    }
487
488    #[test]
489    fn test_persistent_piece_content_with_long_parent_id() {
490        let long_parent_id = "x".repeat(1000);
491        let persistent_piece_content = PersistentPieceContent::new(
492            999,
493            12345,
494            67890,
495            "c".repeat(32),
496            long_parent_id.clone(),
497            255,
498            Duration::from_secs(3600),
499            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
500        );
501
502        let bytes = Bytes::from(persistent_piece_content.clone());
503        let result = PersistentPieceContent::try_from(bytes).unwrap();
504
505        assert_eq!(result.metadata().parent_id, long_parent_id);
506        assert_eq!(result.metadata().number, 999);
507        assert_eq!(result.metadata().traffic_type, 255);
508        assert_eq!(result.metadata().length, 67890);
509    }
510
511    #[test]
512    fn test_persistent_piece_content_with_zero_values() {
513        let persistent_piece_content = PersistentPieceContent::new(
514            0,
515            0,
516            0,
517            "d".repeat(32),
518            "zero_test".to_string(),
519            0,
520            Duration::from_secs(0),
521            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
522        );
523
524        let bytes = Bytes::from(persistent_piece_content.clone());
525        let result = PersistentPieceContent::try_from(bytes).unwrap();
526
527        assert_eq!(result.metadata().number, 0);
528        assert_eq!(result.metadata().offset, 0);
529        assert_eq!(result.metadata().length, 0);
530        assert_eq!(result.metadata().traffic_type, 0);
531        assert_eq!(result.metadata().cost, Duration::from_secs(0));
532        assert!(result.is_empty());
533    }
534
535    #[test]
536    fn test_persistent_piece_content_with_max_values() {
537        let persistent_piece_content = PersistentPieceContent::new(
538            u32::MAX,
539            u64::MAX,
540            u64::MAX,
541            "e".repeat(32),
542            "max_values_test".to_string(),
543            u8::MAX,
544            Duration::from_secs(u64::MAX),
545            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
546        );
547
548        let bytes = Bytes::from(persistent_piece_content.clone());
549        let result = PersistentPieceContent::try_from(bytes).unwrap();
550
551        assert_eq!(result.metadata().number, u32::MAX);
552        assert_eq!(result.metadata().offset, u64::MAX);
553        assert_eq!(result.metadata().length, u64::MAX);
554        assert_eq!(result.metadata().traffic_type, u8::MAX);
555        assert_eq!(result.metadata().cost, Duration::from_secs(u64::MAX));
556    }
557
558    #[test]
559    fn test_persistent_piece_content_metadata_length_calculation() {
560        let persistent_piece_content = PersistentPieceContent::new(
561            123,
562            456,
563            789,
564            "f".repeat(32),
565            "length_test".to_string(),
566            42,
567            Duration::from_secs(100),
568            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
569        );
570
571        let expected_length = (NUMBER_SIZE
572            + OFFSET_SIZE
573            + LENGTH_SIZE
574            + DIGEST_LENGTH_SIZE
575            + 32 // digest length
576            + PARENT_ID_LENGTH_SIZE
577            + "length_test".len()
578            + TRAFFIC_TYPE_SIZE
579            + COST_SIZE
580            + CREATED_AT_SIZE) as u32;
581
582        assert_eq!(persistent_piece_content.metadata_len(), expected_length);
583
584        let bytes = Bytes::from(persistent_piece_content.clone());
585        let result = PersistentPieceContent::try_from(bytes).unwrap();
586        assert_eq!(result.metadata_len(), expected_length);
587    }
588
589    #[test]
590    fn test_persistent_piece_content_with_short_digest() {
591        let persistent_piece_content = PersistentPieceContent::new(
592            1,
593            0,
594            100,
595            "short".to_string(), // Shorter than typical 32-char digest
596            "test".to_string(),
597            1,
598            Duration::from_secs(1),
599            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
600        );
601
602        let bytes = Bytes::from(persistent_piece_content.clone());
603        let result = PersistentPieceContent::try_from(bytes).unwrap();
604
605        assert_eq!(result.metadata().digest, "short");
606        assert_eq!(result.metadata().number, 1);
607    }
608
609    #[test]
610    fn test_persistent_piece_content_with_long_digest() {
611        let long_digest = "g".repeat(128); // Longer than typical digest
612        let persistent_piece_content = PersistentPieceContent::new(
613            5,
614            1000,
615            2000,
616            long_digest.clone(),
617            "digest_test".to_string(),
618            10,
619            Duration::from_secs(50),
620            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
621        );
622
623        let bytes = Bytes::from(persistent_piece_content.clone());
624        let result = PersistentPieceContent::try_from(bytes).unwrap();
625
626        assert_eq!(result.metadata().digest, long_digest);
627        assert_eq!(result.metadata().number, 5);
628        assert_eq!(result.metadata().offset, 1000);
629        assert_eq!(result.metadata().length, 2000);
630    }
631
632    #[test]
633    fn test_persistent_piece_content_bytes_structure() {
634        let persistent_piece_content = create_test_persistent_piece_content();
635        let bytes: Bytes = persistent_piece_content.clone().into();
636
637        let metadata_length_bytes = &bytes[..METADATA_LENGTH_SIZE];
638        let metadata_length = u32::from_be_bytes(metadata_length_bytes.try_into().unwrap());
639        assert_eq!(metadata_length, persistent_piece_content.metadata_len());
640        assert_eq!(bytes.len(), METADATA_LENGTH_SIZE + metadata_length as usize);
641    }
642
643    #[test]
644    fn test_persistent_piece_content_new() {
645        let persistent_piece_content = PersistentPieceContent::new(
646            42,
647            1024,
648            2048,
649            "a".repeat(32),
650            "test_parent_id".to_string(),
651            1,
652            Duration::from_secs(5),
653            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
654        );
655
656        assert_eq!(persistent_piece_content.metadata().number, 42);
657        assert_eq!(persistent_piece_content.metadata().offset, 1024);
658        assert_eq!(persistent_piece_content.metadata().length, 2048);
659        assert_eq!(persistent_piece_content.metadata().digest, "a".repeat(32));
660        assert_eq!(
661            persistent_piece_content.metadata().parent_id,
662            "test_parent_id"
663        );
664        assert_eq!(persistent_piece_content.metadata().traffic_type, 1);
665        assert_eq!(
666            persistent_piece_content.metadata().cost,
667            Duration::from_secs(5)
668        );
669        assert_eq!(
670            persistent_piece_content.metadata_len(),
671            (NUMBER_SIZE
672                + OFFSET_SIZE
673                + LENGTH_SIZE
674                + DIGEST_LENGTH_SIZE
675                + persistent_piece_content.metadata().digest.len()
676                + PARENT_ID_LENGTH_SIZE
677                + persistent_piece_content.metadata().parent_id.len()
678                + TRAFFIC_TYPE_SIZE
679                + COST_SIZE
680                + CREATED_AT_SIZE) as u32,
681        );
682    }
683
684    #[test]
685    fn test_persistent_piece_content_is_empty() {
686        let empty_persistent_piece = PersistentPieceContent::new(
687            0,
688            0,
689            0,
690            "a".repeat(32),
691            "test".to_string(),
692            0,
693            Duration::from_secs(0),
694            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
695        );
696
697        let non_empty_persistent_piece = PersistentPieceContent::new(
698            1,
699            0,
700            100,
701            "a".repeat(32),
702            "test".to_string(),
703            0,
704            Duration::from_secs(0),
705            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
706        );
707
708        assert!(empty_persistent_piece.is_empty());
709        assert!(!non_empty_persistent_piece.is_empty());
710    }
711
712    #[test]
713    fn test_persistent_piece_metadata_conversion_roundtrip() {
714        let metadata = create_test_metadata();
715        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
716        let result = PersistentPieceMetadata::try_from((bytes, length)).unwrap();
717
718        assert_eq!(result.number, metadata.number);
719        assert_eq!(result.offset, metadata.offset);
720        assert_eq!(result.length, metadata.length);
721        assert_eq!(result.digest, metadata.digest);
722        assert_eq!(result.parent_id, metadata.parent_id);
723        assert_eq!(result.traffic_type, metadata.traffic_type);
724        assert_eq!(result.cost, metadata.cost);
725        assert_eq!(result.created_at, metadata.created_at);
726    }
727
728    #[test]
729    fn test_persistent_piece_metadata_try_from_invalid_length() {
730        let metadata = create_test_metadata();
731        let (bytes, correct_length) = <(Bytes, u32)>::from(metadata);
732        let wrong_length = correct_length + 10;
733        let result = PersistentPieceMetadata::try_from((bytes, wrong_length));
734
735        assert!(result.is_err());
736        assert!(matches!(result.unwrap_err(), Error::InvalidLength(_)));
737    }
738
739    #[test]
740    fn test_persistent_piece_metadata_try_from_too_short_bytes() {
741        let short_bytes = Bytes::from(vec![0u8; 10]);
742        let result = PersistentPieceMetadata::try_from((short_bytes, 10));
743        assert!(result.is_err());
744    }
745
746    #[test]
747    fn test_persistent_piece_metadata_with_empty_parent_id() {
748        let metadata = PersistentPieceMetadata {
749            number: 1,
750            offset: 0,
751            length: 100,
752            digest: "b".repeat(32),
753            parent_id: String::new(),
754            traffic_type: 2,
755            cost: Duration::from_secs(1),
756            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
757        };
758
759        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
760        let result = PersistentPieceMetadata::try_from((bytes, length)).unwrap();
761
762        assert_eq!(result.parent_id, "");
763        assert_eq!(result.number, 1);
764        assert_eq!(result.traffic_type, 2);
765    }
766
767    #[test]
768    fn test_persistent_piece_metadata_with_long_parent_id() {
769        let long_parent_id = "x".repeat(1000); // Very long parent_id
770        let metadata = PersistentPieceMetadata {
771            number: 999,
772            offset: 12345,
773            length: 67890,
774            digest: "c".repeat(32),
775            parent_id: long_parent_id.clone(),
776            traffic_type: 255,
777            cost: Duration::from_secs(3600),
778            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
779        };
780
781        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
782        let result = PersistentPieceMetadata::try_from((bytes, length)).unwrap();
783
784        assert_eq!(result.parent_id, long_parent_id);
785        assert_eq!(result.number, 999);
786        assert_eq!(result.traffic_type, 255);
787    }
788
789    #[test]
790    fn test_persistent_piece_metadata_with_zero_cost() {
791        let metadata = PersistentPieceMetadata {
792            number: 0,
793            offset: 0,
794            length: 0,
795            digest: "d".repeat(32),
796            parent_id: "zero_cost_test".to_string(),
797            traffic_type: 0,
798            cost: Duration::from_secs(0),
799            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
800        };
801
802        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
803        let result = PersistentPieceMetadata::try_from((bytes, length)).unwrap();
804
805        assert_eq!(result.cost, Duration::from_secs(0));
806        assert_eq!(result.parent_id, "zero_cost_test");
807    }
808
809    #[test]
810    fn test_persistent_piece_metadata_with_max_values() {
811        let metadata = PersistentPieceMetadata {
812            number: u32::MAX,
813            offset: u64::MAX,
814            length: u64::MAX,
815            digest: "e".repeat(32),
816            parent_id: "max_values_test".to_string(),
817            traffic_type: u8::MAX,
818            cost: Duration::from_secs(u64::MAX),
819            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
820        };
821
822        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
823        let result = PersistentPieceMetadata::try_from((bytes, length)).unwrap();
824
825        assert_eq!(result.number, u32::MAX);
826        assert_eq!(result.offset, u64::MAX);
827        assert_eq!(result.length, u64::MAX);
828        assert_eq!(result.traffic_type, u8::MAX);
829        assert_eq!(result.cost, Duration::from_secs(u64::MAX));
830    }
831
832    #[test]
833    fn test_persistent_piece_metadata_invalid_utf8_in_digest() {
834        let metadata_with_short_digest = PersistentPieceMetadata {
835            number: 1,
836            offset: 0,
837            length: 100,
838            digest: "short".to_string(),
839            parent_id: "test".to_string(),
840            traffic_type: 1,
841            cost: Duration::from_secs(1),
842            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
843        };
844
845        let (bytes, length) = <(Bytes, u32)>::from(metadata_with_short_digest);
846        let result = PersistentPieceMetadata::try_from((bytes, length));
847        assert!(result.is_ok());
848    }
849}