vortex_protocol/tlv/
persistent_cache_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/// PersistentCachePieceContent represents a persistent cache 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 PersistentCachePieceContent {
75    metadata_length: u32,
76    metadata: PersistentCachePieceMetadata,
77}
78
79/// PersistentCachePieceContent implements the PersistentCachePieceContent functions.
80impl PersistentCachePieceContent {
81    /// new creates a new PersistentCachePieceContent 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: PersistentCachePieceMetadata {
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 cache piece metadata.
118    pub fn metadata(&self) -> PersistentCachePieceMetadata {
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 cache 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 PersistentCachePieceContent for conversion from a byte slice.
134impl TryFrom<Bytes> for PersistentCachePieceContent {
135    type Error = Error;
136
137    /// try_from decodes the persistent cache 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 PersistentCachePieceContent, 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(PersistentCachePieceContent {
163            metadata_length,
164            metadata,
165        })
166    }
167}
168
169/// Implement From<PersistentCachePieceContent> for Bytes for conversion to a byte slice.
170impl From<PersistentCachePieceContent> for Bytes {
171    /// from converts the persistent cache piece content request to a byte slice.
172    fn from(content: PersistentCachePieceContent) -> 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/// PersistentCachePieceMetadata holds the metadata information for a persistent cache piece.
182#[derive(Debug, Clone)]
183pub struct PersistentCachePieceMetadata {
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/// PersistentCachePieceMetadata implements the PersistentCachePieceMetadata functions.
195impl PersistentCachePieceMetadata {
196    /// new creates a new PersistentCachePieceMetadata.
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 PersistentCachePieceMetadata for conversion from a byte slice.
222impl TryFrom<(Bytes, u32)> for PersistentCachePieceMetadata {
223    type Error = Error;
224
225    /// try_from decodes the persistent cache 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 PersistentCachePieceMetadata, 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(PersistentCachePieceMetadata {
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<PersistentCachePieceMetadata> for Bytes for conversion to a byte slice.
353impl From<PersistentCachePieceMetadata> for (Bytes, u32) {
354    /// from converts the persistent cache piece metadata request to a byte slice.
355    fn from(metadata: PersistentCachePieceMetadata) -> (Bytes, u32) {
356        let PersistentCachePieceMetadata {
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_cache_piece_content() -> PersistentCachePieceContent {
401        PersistentCachePieceContent::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() -> PersistentCachePieceMetadata {
414        PersistentCachePieceMetadata {
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_cache_piece_content_conversion_roundtrip() {
428        let original = create_test_persistent_cache_piece_content();
429        let bytes = Bytes::from(original.clone());
430        let result = PersistentCachePieceContent::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_cache_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 = PersistentCachePieceContent::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_cache_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 = PersistentCachePieceContent::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_cache_piece_content_with_empty_parent_id() {
468        let persistent_cache_piece_content = PersistentCachePieceContent::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_cache_piece_content.clone());
480        let result = PersistentCachePieceContent::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_cache_piece_content_with_long_parent_id() {
490        let long_parent_id = "x".repeat(1000);
491        let persistent_cache_piece_content = PersistentCachePieceContent::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_cache_piece_content.clone());
503        let result = PersistentCachePieceContent::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_cache_piece_content_with_zero_values() {
513        let persistent_cache_piece_content = PersistentCachePieceContent::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_cache_piece_content.clone());
525        let result = PersistentCachePieceContent::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_cache_piece_content_with_max_values() {
537        let persistent_cache_piece_content = PersistentCachePieceContent::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_cache_piece_content.clone());
549        let result = PersistentCachePieceContent::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_cache_piece_content_metadata_length_calculation() {
560        let persistent_cache_piece_content = PersistentCachePieceContent::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!(
583            persistent_cache_piece_content.metadata_len(),
584            expected_length
585        );
586
587        let bytes = Bytes::from(persistent_cache_piece_content.clone());
588        let result = PersistentCachePieceContent::try_from(bytes).unwrap();
589        assert_eq!(result.metadata_len(), expected_length);
590    }
591
592    #[test]
593    fn test_persistent_cache_piece_content_with_short_digest() {
594        let persistent_cache_piece_content = PersistentCachePieceContent::new(
595            1,
596            0,
597            100,
598            "short".to_string(), // Shorter than typical 32-char digest
599            "test".to_string(),
600            1,
601            Duration::from_secs(1),
602            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
603        );
604
605        let bytes = Bytes::from(persistent_cache_piece_content.clone());
606        let result = PersistentCachePieceContent::try_from(bytes).unwrap();
607
608        assert_eq!(result.metadata().digest, "short");
609        assert_eq!(result.metadata().number, 1);
610    }
611
612    #[test]
613    fn test_persistent_cache_piece_content_with_long_digest() {
614        let long_digest = "g".repeat(128); // Longer than typical digest
615        let persistent_cache_piece_content = PersistentCachePieceContent::new(
616            5,
617            1000,
618            2000,
619            long_digest.clone(),
620            "digest_test".to_string(),
621            10,
622            Duration::from_secs(50),
623            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
624        );
625
626        let bytes = Bytes::from(persistent_cache_piece_content.clone());
627        let result = PersistentCachePieceContent::try_from(bytes).unwrap();
628
629        assert_eq!(result.metadata().digest, long_digest);
630        assert_eq!(result.metadata().number, 5);
631        assert_eq!(result.metadata().offset, 1000);
632        assert_eq!(result.metadata().length, 2000);
633    }
634
635    #[test]
636    fn test_persistent_cache_piece_content_bytes_structure() {
637        let persistent_cache_piece_content = create_test_persistent_cache_piece_content();
638        let bytes: Bytes = persistent_cache_piece_content.clone().into();
639
640        let metadata_length_bytes = &bytes[..METADATA_LENGTH_SIZE];
641        let metadata_length = u32::from_be_bytes(metadata_length_bytes.try_into().unwrap());
642        assert_eq!(
643            metadata_length,
644            persistent_cache_piece_content.metadata_len()
645        );
646        assert_eq!(bytes.len(), METADATA_LENGTH_SIZE + metadata_length as usize);
647    }
648
649    #[test]
650    fn test_persistent_cache_piece_content_new() {
651        let persistent_cache_piece_content = PersistentCachePieceContent::new(
652            42,
653            1024,
654            2048,
655            "a".repeat(32),
656            "test_parent_id".to_string(),
657            1,
658            Duration::from_secs(5),
659            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
660        );
661
662        assert_eq!(persistent_cache_piece_content.metadata().number, 42);
663        assert_eq!(persistent_cache_piece_content.metadata().offset, 1024);
664        assert_eq!(persistent_cache_piece_content.metadata().length, 2048);
665        assert_eq!(
666            persistent_cache_piece_content.metadata().digest,
667            "a".repeat(32)
668        );
669        assert_eq!(
670            persistent_cache_piece_content.metadata().parent_id,
671            "test_parent_id"
672        );
673        assert_eq!(persistent_cache_piece_content.metadata().traffic_type, 1);
674        assert_eq!(
675            persistent_cache_piece_content.metadata().cost,
676            Duration::from_secs(5)
677        );
678        assert_eq!(
679            persistent_cache_piece_content.metadata_len(),
680            (NUMBER_SIZE
681                + OFFSET_SIZE
682                + LENGTH_SIZE
683                + DIGEST_LENGTH_SIZE
684                + persistent_cache_piece_content.metadata().digest.len()
685                + PARENT_ID_LENGTH_SIZE
686                + persistent_cache_piece_content.metadata().parent_id.len()
687                + TRAFFIC_TYPE_SIZE
688                + COST_SIZE
689                + CREATED_AT_SIZE) as u32,
690        );
691    }
692
693    #[test]
694    fn test_persistent_cache_piece_content_is_empty() {
695        let empty_persistent_cache_piece = PersistentCachePieceContent::new(
696            0,
697            0,
698            0,
699            "a".repeat(32),
700            "test".to_string(),
701            0,
702            Duration::from_secs(0),
703            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
704        );
705
706        let non_empty_persistent_cache_piece = PersistentCachePieceContent::new(
707            1,
708            0,
709            100,
710            "a".repeat(32),
711            "test".to_string(),
712            0,
713            Duration::from_secs(0),
714            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
715        );
716
717        assert!(empty_persistent_cache_piece.is_empty());
718        assert!(!non_empty_persistent_cache_piece.is_empty());
719    }
720
721    #[test]
722    fn test_persistent_cache_piece_metadata_conversion_roundtrip() {
723        let metadata = create_test_metadata();
724        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
725        let result = PersistentCachePieceMetadata::try_from((bytes, length)).unwrap();
726
727        assert_eq!(result.number, metadata.number);
728        assert_eq!(result.offset, metadata.offset);
729        assert_eq!(result.length, metadata.length);
730        assert_eq!(result.digest, metadata.digest);
731        assert_eq!(result.parent_id, metadata.parent_id);
732        assert_eq!(result.traffic_type, metadata.traffic_type);
733        assert_eq!(result.cost, metadata.cost);
734        assert_eq!(result.created_at, metadata.created_at);
735    }
736
737    #[test]
738    fn test_persistent_cache_piece_metadata_try_from_invalid_length() {
739        let metadata = create_test_metadata();
740        let (bytes, correct_length) = <(Bytes, u32)>::from(metadata);
741        let wrong_length = correct_length + 10;
742        let result = PersistentCachePieceMetadata::try_from((bytes, wrong_length));
743
744        assert!(result.is_err());
745        assert!(matches!(result.unwrap_err(), Error::InvalidLength(_)));
746    }
747
748    #[test]
749    fn test_persistent_cache_piece_metadata_try_from_too_short_bytes() {
750        let short_bytes = Bytes::from(vec![0u8; 10]);
751        let result = PersistentCachePieceMetadata::try_from((short_bytes, 10));
752        assert!(result.is_err());
753    }
754
755    #[test]
756    fn test_persistent_cache_piece_metadata_with_empty_parent_id() {
757        let metadata = PersistentCachePieceMetadata {
758            number: 1,
759            offset: 0,
760            length: 100,
761            digest: "b".repeat(32),
762            parent_id: String::new(),
763            traffic_type: 2,
764            cost: Duration::from_secs(1),
765            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
766        };
767
768        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
769        let result = PersistentCachePieceMetadata::try_from((bytes, length)).unwrap();
770
771        assert_eq!(result.parent_id, "");
772        assert_eq!(result.number, 1);
773        assert_eq!(result.traffic_type, 2);
774    }
775
776    #[test]
777    fn test_persistent_cache_piece_metadata_with_long_parent_id() {
778        let long_parent_id = "x".repeat(1000); // Very long parent_id
779        let metadata = PersistentCachePieceMetadata {
780            number: 999,
781            offset: 12345,
782            length: 67890,
783            digest: "c".repeat(32),
784            parent_id: long_parent_id.clone(),
785            traffic_type: 255,
786            cost: Duration::from_secs(3600),
787            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
788        };
789
790        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
791        let result = PersistentCachePieceMetadata::try_from((bytes, length)).unwrap();
792
793        assert_eq!(result.parent_id, long_parent_id);
794        assert_eq!(result.number, 999);
795        assert_eq!(result.traffic_type, 255);
796    }
797
798    #[test]
799    fn test_persistent_cache_piece_metadata_with_zero_cost() {
800        let metadata = PersistentCachePieceMetadata {
801            number: 0,
802            offset: 0,
803            length: 0,
804            digest: "d".repeat(32),
805            parent_id: "zero_cost_test".to_string(),
806            traffic_type: 0,
807            cost: Duration::from_secs(0),
808            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
809        };
810
811        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
812        let result = PersistentCachePieceMetadata::try_from((bytes, length)).unwrap();
813
814        assert_eq!(result.cost, Duration::from_secs(0));
815        assert_eq!(result.parent_id, "zero_cost_test");
816    }
817
818    #[test]
819    fn test_persistent_cache_piece_metadata_with_max_values() {
820        let metadata = PersistentCachePieceMetadata {
821            number: u32::MAX,
822            offset: u64::MAX,
823            length: u64::MAX,
824            digest: "e".repeat(32),
825            parent_id: "max_values_test".to_string(),
826            traffic_type: u8::MAX,
827            cost: Duration::from_secs(u64::MAX),
828            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
829        };
830
831        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
832        let result = PersistentCachePieceMetadata::try_from((bytes, length)).unwrap();
833
834        assert_eq!(result.number, u32::MAX);
835        assert_eq!(result.offset, u64::MAX);
836        assert_eq!(result.length, u64::MAX);
837        assert_eq!(result.traffic_type, u8::MAX);
838        assert_eq!(result.cost, Duration::from_secs(u64::MAX));
839    }
840
841    #[test]
842    fn test_persistent_cache_piece_metadata_invalid_utf8_in_digest() {
843        let metadata_with_short_digest = PersistentCachePieceMetadata {
844            number: 1,
845            offset: 0,
846            length: 100,
847            digest: "short".to_string(),
848            parent_id: "test".to_string(),
849            traffic_type: 1,
850            cost: Duration::from_secs(1),
851            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
852        };
853
854        let (bytes, length) = <(Bytes, u32)>::from(metadata_with_short_digest);
855        let result = PersistentCachePieceMetadata::try_from((bytes, length));
856        assert!(result.is_ok());
857    }
858}