fp_runtime/asupersync/
codec.rs1use serde::{Deserialize, Serialize};
2
3use crate::asupersync::{config::AsupersyncConfig, error::AsupersyncError};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct ArtifactPayload {
7 pub artifact_id: String,
8 pub bytes: Vec<u8>,
9 pub expected_digest: Option<String>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct EncodedArtifact {
14 pub artifact_id: String,
15 pub source_len: usize,
16 pub encoded_bytes: Vec<u8>,
17 pub repair_symbols: u32,
18}
19
20pub trait ArtifactCodec {
21 fn encode(
22 &self,
23 payload: &ArtifactPayload,
24 config: &AsupersyncConfig,
25 ) -> Result<EncodedArtifact, AsupersyncError>;
26
27 fn decode(
28 &self,
29 encoded: &EncodedArtifact,
30 config: &AsupersyncConfig,
31 ) -> Result<ArtifactPayload, AsupersyncError>;
32}
33
34#[derive(Debug, Clone, Copy, Default)]
35pub struct PassthroughCodec;
36
37impl ArtifactCodec for PassthroughCodec {
38 fn encode(
39 &self,
40 payload: &ArtifactPayload,
41 config: &AsupersyncConfig,
42 ) -> Result<EncodedArtifact, AsupersyncError> {
43 if config.max_repair_symbols == 0 {
44 return Err(AsupersyncError::Configuration(
45 "max_repair_symbols must be greater than zero",
46 ));
47 }
48
49 Ok(EncodedArtifact {
50 artifact_id: payload.artifact_id.clone(),
51 source_len: payload.bytes.len(),
52 encoded_bytes: payload.bytes.clone(),
53 repair_symbols: config.max_repair_symbols,
54 })
55 }
56
57 fn decode(
58 &self,
59 encoded: &EncodedArtifact,
60 _config: &AsupersyncConfig,
61 ) -> Result<ArtifactPayload, AsupersyncError> {
62 if encoded.repair_symbols == 0 {
63 return Err(AsupersyncError::Codec(
64 "repair_symbols must be greater than zero".to_string(),
65 ));
66 }
67 if encoded.source_len > encoded.encoded_bytes.len() {
68 return Err(AsupersyncError::Codec(
69 "source_len exceeds encoded payload length".to_string(),
70 ));
71 }
72 let bytes = encoded
73 .encoded_bytes
74 .get(..encoded.source_len)
75 .ok_or_else(|| {
76 AsupersyncError::Codec("source_len exceeds encoded payload length".to_string())
77 })?
78 .to_vec();
79
80 Ok(ArtifactPayload {
81 artifact_id: encoded.artifact_id.clone(),
82 bytes,
83 expected_digest: None,
84 })
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::{ArtifactCodec, ArtifactPayload, EncodedArtifact, PassthroughCodec};
91 use crate::asupersync::{config::AsupersyncConfig, error::AsupersyncError};
92
93 #[test]
94 fn passthrough_codec_rejects_zero_repair_symbols_on_encode_20jod() {
95 let codec = PassthroughCodec;
96 let config = AsupersyncConfig {
97 max_repair_symbols: 0,
98 ..AsupersyncConfig::default()
99 };
100 let payload = ArtifactPayload {
101 artifact_id: "artifact-20jod".to_string(),
102 bytes: b"payload".to_vec(),
103 expected_digest: None,
104 };
105
106 let err = codec.encode(&payload, &config).err();
107 assert!(matches!(err, Some(AsupersyncError::Configuration(_))));
108 }
109
110 #[test]
111 fn passthrough_codec_rejects_zero_repair_symbols_on_decode_20jod() {
112 let codec = PassthroughCodec;
113 let config = AsupersyncConfig::default();
114 let encoded = EncodedArtifact {
115 artifact_id: "artifact-20jod".to_string(),
116 source_len: 7,
117 encoded_bytes: b"payload".to_vec(),
118 repair_symbols: 0,
119 };
120
121 let err = codec.decode(&encoded, &config).err();
122 assert!(
123 matches!(err, Some(AsupersyncError::Codec(message)) if message.contains("repair_symbols"))
124 );
125 }
126
127 #[test]
128 fn passthrough_codec_round_trip_preserves_payload_with_manifest_20jod()
129 -> Result<(), AsupersyncError> {
130 let codec = PassthroughCodec;
131 let config = AsupersyncConfig {
132 max_repair_symbols: 4,
133 ..AsupersyncConfig::default()
134 };
135 let payload = ArtifactPayload {
136 artifact_id: "artifact-20jod".to_string(),
137 bytes: b"payload".to_vec(),
138 expected_digest: Some("digest".to_string()),
139 };
140
141 let encoded = codec.encode(&payload, &config)?;
142 assert_eq!(encoded.repair_symbols, 4);
143
144 let decoded = codec.decode(&encoded, &config)?;
145 assert_eq!(decoded.artifact_id, payload.artifact_id);
146 assert_eq!(decoded.bytes, payload.bytes);
147 assert_eq!(decoded.expected_digest, None);
148 Ok(())
149 }
150}