vortex_protocol/tlv/
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/// CachePieceContent represents a cache piece metadata and cache 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 CachePieceContent {
75    metadata_length: u32,
76    metadata: CachePieceMetadata,
77}
78
79/// CachePieceContent implements the CachePieceContent functions.
80impl CachePieceContent {
81    /// new creates a new CachePieceContent 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: CachePieceMetadata {
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 cache piece metadata.
118    pub fn metadata(&self) -> CachePieceMetadata {
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 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 CachePieceContent for conversion from a byte slice.
134impl TryFrom<Bytes> for CachePieceContent {
135    type Error = Error;
136
137    /// try_from decodes the 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 CachePieceContent, 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(CachePieceContent {
163            metadata_length,
164            metadata,
165        })
166    }
167}
168
169/// Implement From<CachePieceContent> for Bytes for conversion to a byte slice.
170impl From<CachePieceContent> for Bytes {
171    /// from converts the cache piece content request to a byte slice.
172    fn from(content: CachePieceContent) -> 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/// CachePieceMetadata holds the metadata information for a cache piece.
182#[derive(Debug, Clone)]
183pub struct CachePieceMetadata {
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/// CachePieceMetadata implements the CachePieceMetadata functions.
195impl CachePieceMetadata {
196    /// new creates a new CachePieceMetadata.
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 CachePieceMetadata for conversion from a byte slice.
222impl TryFrom<(Bytes, u32)> for CachePieceMetadata {
223    type Error = Error;
224
225    /// try_from decodes the 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 CachePieceMetadata, 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(CachePieceMetadata {
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<CachePieceMetadata> for Bytes for conversion to a byte slice.
353impl From<CachePieceMetadata> for (Bytes, u32) {
354    /// from converts the persistent cache piece metadata request to a byte slice.
355    fn from(metadata: CachePieceMetadata) -> (Bytes, u32) {
356        let CachePieceMetadata {
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_cache_piece_content() -> CachePieceContent {
401        CachePieceContent::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() -> CachePieceMetadata {
414        CachePieceMetadata {
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_cache_piece_content_conversion_roundtrip() {
428        let original = create_test_cache_piece_content();
429        let bytes = Bytes::from(original.clone());
430        let result = CachePieceContent::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_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 = CachePieceContent::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_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 = CachePieceContent::try_from(bytes.freeze());
462        assert!(result.is_err());
463        assert!(matches!(result.unwrap_err(), Error::InvalidPacket(_)));
464    }
465
466    #[test]
467    fn test_cache_piece_content_with_empty_parent_id() {
468        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
480        let result = CachePieceContent::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_cache_piece_content_with_long_parent_id() {
490        let long_parent_id = "x".repeat(1000);
491        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
503        let result = CachePieceContent::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_cache_piece_content_with_zero_values() {
513        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
525        let result = CachePieceContent::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_cache_piece_content_with_max_values() {
537        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
549        let result = CachePieceContent::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_cache_piece_content_metadata_length_calculation() {
560        let cache_piece_content = CachePieceContent::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!(cache_piece_content.metadata_len(), expected_length);
583
584        let bytes = Bytes::from(cache_piece_content.clone());
585        let result = CachePieceContent::try_from(bytes).unwrap();
586        assert_eq!(result.metadata_len(), expected_length);
587    }
588
589    #[test]
590    fn test_cache_piece_content_with_short_digest() {
591        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
603        let result = CachePieceContent::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_cache_piece_content_with_long_digest() {
611        let long_digest = "g".repeat(128); // Longer than typical digest
612        let cache_piece_content = CachePieceContent::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(cache_piece_content.clone());
624        let result = CachePieceContent::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_cache_piece_content_bytes_structure() {
634        let cache_piece_content = create_test_cache_piece_content();
635        let bytes: Bytes = cache_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, cache_piece_content.metadata_len());
640        assert_eq!(bytes.len(), METADATA_LENGTH_SIZE + metadata_length as usize);
641    }
642
643    #[test]
644    fn test_cache_piece_content_new() {
645        let cache_piece_content = CachePieceContent::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!(cache_piece_content.metadata().number, 42);
657        assert_eq!(cache_piece_content.metadata().offset, 1024);
658        assert_eq!(cache_piece_content.metadata().length, 2048);
659        assert_eq!(cache_piece_content.metadata().digest, "a".repeat(32));
660        assert_eq!(cache_piece_content.metadata().parent_id, "test_parent_id");
661        assert_eq!(cache_piece_content.metadata().traffic_type, 1);
662        assert_eq!(cache_piece_content.metadata().cost, Duration::from_secs(5));
663        assert_eq!(
664            cache_piece_content.metadata_len(),
665            (NUMBER_SIZE
666                + OFFSET_SIZE
667                + LENGTH_SIZE
668                + DIGEST_LENGTH_SIZE
669                + cache_piece_content.metadata().digest.len()
670                + PARENT_ID_LENGTH_SIZE
671                + cache_piece_content.metadata().parent_id.len()
672                + TRAFFIC_TYPE_SIZE
673                + COST_SIZE
674                + CREATED_AT_SIZE) as u32,
675        );
676    }
677
678    #[test]
679    fn test_cache_piece_content_is_empty() {
680        let empty_cache_piece_content = CachePieceContent::new(
681            0,
682            0,
683            0,
684            "a".repeat(32),
685            "test".to_string(),
686            0,
687            Duration::from_secs(0),
688            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
689        );
690
691        let non_empty_cache_piece_content = CachePieceContent::new(
692            1,
693            0,
694            100,
695            "a".repeat(32),
696            "test".to_string(),
697            0,
698            Duration::from_secs(0),
699            DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
700        );
701
702        assert!(empty_cache_piece_content.is_empty());
703        assert!(!non_empty_cache_piece_content.is_empty());
704    }
705
706    #[test]
707    fn test_cache_piece_metadata_conversion_roundtrip() {
708        let metadata = create_test_metadata();
709        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
710        let result = CachePieceMetadata::try_from((bytes, length)).unwrap();
711
712        assert_eq!(result.number, metadata.number);
713        assert_eq!(result.offset, metadata.offset);
714        assert_eq!(result.length, metadata.length);
715        assert_eq!(result.digest, metadata.digest);
716        assert_eq!(result.parent_id, metadata.parent_id);
717        assert_eq!(result.traffic_type, metadata.traffic_type);
718        assert_eq!(result.cost, metadata.cost);
719        assert_eq!(result.created_at, metadata.created_at);
720    }
721
722    #[test]
723    fn test_cache_piece_metadata_try_from_invalid_length() {
724        let metadata = create_test_metadata();
725        let (bytes, correct_length) = <(Bytes, u32)>::from(metadata);
726        let wrong_length = correct_length + 10;
727        let result = CachePieceMetadata::try_from((bytes, wrong_length));
728
729        assert!(result.is_err());
730        assert!(matches!(result.unwrap_err(), Error::InvalidLength(_)));
731    }
732
733    #[test]
734    fn test_cache_piece_metadata_try_from_too_short_bytes() {
735        let short_bytes = Bytes::from(vec![0u8; 10]);
736        let result = CachePieceMetadata::try_from((short_bytes, 10));
737        assert!(result.is_err());
738    }
739
740    #[test]
741    fn test_cache_piece_metadata_with_empty_parent_id() {
742        let metadata = CachePieceMetadata {
743            number: 1,
744            offset: 0,
745            length: 100,
746            digest: "b".repeat(32),
747            parent_id: String::new(),
748            traffic_type: 2,
749            cost: Duration::from_secs(1),
750            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
751        };
752
753        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
754        let result = CachePieceMetadata::try_from((bytes, length)).unwrap();
755
756        assert_eq!(result.parent_id, "");
757        assert_eq!(result.number, 1);
758        assert_eq!(result.traffic_type, 2);
759    }
760
761    #[test]
762    fn test_cache_piece_metadata_with_long_parent_id() {
763        let long_parent_id = "x".repeat(1000); // Very long parent_id
764        let metadata = CachePieceMetadata {
765            number: 999,
766            offset: 12345,
767            length: 67890,
768            digest: "c".repeat(32),
769            parent_id: long_parent_id.clone(),
770            traffic_type: 255,
771            cost: Duration::from_secs(3600),
772            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
773        };
774
775        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
776        let result = CachePieceMetadata::try_from((bytes, length)).unwrap();
777
778        assert_eq!(result.parent_id, long_parent_id);
779        assert_eq!(result.number, 999);
780        assert_eq!(result.traffic_type, 255);
781    }
782
783    #[test]
784    fn test_cache_piece_metadata_with_zero_cost() {
785        let metadata = CachePieceMetadata {
786            number: 0,
787            offset: 0,
788            length: 0,
789            digest: "d".repeat(32),
790            parent_id: "zero_cost_test".to_string(),
791            traffic_type: 0,
792            cost: Duration::from_secs(0),
793            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
794        };
795
796        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
797        let result = CachePieceMetadata::try_from((bytes, length)).unwrap();
798
799        assert_eq!(result.cost, Duration::from_secs(0));
800        assert_eq!(result.parent_id, "zero_cost_test");
801    }
802
803    #[test]
804    fn test_cache_piece_metadata_with_max_values() {
805        let metadata = CachePieceMetadata {
806            number: u32::MAX,
807            offset: u64::MAX,
808            length: u64::MAX,
809            digest: "e".repeat(32),
810            parent_id: "max_values_test".to_string(),
811            traffic_type: u8::MAX,
812            cost: Duration::from_secs(u64::MAX),
813            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
814        };
815
816        let (bytes, length) = <(Bytes, u32)>::from(metadata.clone());
817        let result = CachePieceMetadata::try_from((bytes, length)).unwrap();
818
819        assert_eq!(result.number, u32::MAX);
820        assert_eq!(result.offset, u64::MAX);
821        assert_eq!(result.length, u64::MAX);
822        assert_eq!(result.traffic_type, u8::MAX);
823        assert_eq!(result.cost, Duration::from_secs(u64::MAX));
824    }
825
826    #[test]
827    fn test_cache_piece_metadata_invalid_utf8_in_digest() {
828        let metadata_with_short_digest = CachePieceMetadata {
829            number: 1,
830            offset: 0,
831            length: 100,
832            digest: "short".to_string(),
833            parent_id: "test".to_string(),
834            traffic_type: 1,
835            cost: Duration::from_secs(1),
836            created_at: DateTime::from_timestamp(1693152000, 0).unwrap().naive_utc(),
837        };
838
839        let (bytes, length) = <(Bytes, u32)>::from(metadata_with_short_digest);
840        let result = CachePieceMetadata::try_from((bytes, length));
841        assert!(result.is_ok());
842    }
843}