freenet_stdlib/
versioning.rs

1use std::fmt;
2use std::fmt::{Display, Formatter};
3use std::io::{Cursor, Read};
4use std::path::Path;
5use std::sync::Arc;
6
7use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    client_api::{TryFromFbs, WsApiError},
12    common_generated::common::{ContractContainer as FbsContractContainer, ContractType},
13    contract_interface::{ContractInstanceId, ContractKey},
14    generated::client_request::{DelegateContainer as FbsDelegateContainer, DelegateType},
15    parameters::Parameters,
16    prelude::{
17        CodeHash, ContractCode, ContractWasmAPIVersion::V1, Delegate, DelegateCode, DelegateKey,
18        WrappedContract,
19    },
20};
21
22/// Contains the different versions available for WASM delegates.
23#[non_exhaustive]
24#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
25pub enum DelegateWasmAPIVersion {
26    V1(#[serde(deserialize_with = "Delegate::deserialize_delegate")] Delegate<'static>),
27}
28
29impl DelegateWasmAPIVersion {}
30
31#[non_exhaustive]
32#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
33pub enum DelegateContainer {
34    Wasm(DelegateWasmAPIVersion),
35}
36
37impl From<DelegateContainer> for APIVersion {
38    fn from(delegate: DelegateContainer) -> APIVersion {
39        match delegate {
40            DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
41        }
42    }
43}
44
45impl DelegateContainer {
46    pub fn key(&self) -> &DelegateKey {
47        match self {
48            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.key(),
49        }
50    }
51
52    pub fn code(&self) -> &DelegateCode {
53        match self {
54            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code(),
55        }
56    }
57
58    pub fn code_hash(&self) -> &CodeHash {
59        match self {
60            Self::Wasm(DelegateWasmAPIVersion::V1(delegate_v1)) => delegate_v1.code_hash(),
61        }
62    }
63}
64
65impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for DelegateContainer {
66    type Error = std::io::Error;
67
68    fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
69        let (contract_code, version) = DelegateCode::load_versioned_from_path(path)?;
70
71        match version {
72            APIVersion::Version0_0_1 => {
73                let delegate = Delegate::from((&contract_code, &params));
74                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
75                    delegate,
76                )))
77            }
78        }
79    }
80}
81
82impl<'a, P> TryFrom<(Vec<u8>, P)> for DelegateContainer
83where
84    P: std::ops::Deref<Target = Parameters<'a>>,
85{
86    type Error = std::io::Error;
87
88    fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
89        let params = params.deref().clone().into_owned();
90
91        let (contract_code, version) =
92            DelegateCode::load_versioned_from_bytes(versioned_contract_bytes)?;
93
94        match version {
95            APIVersion::Version0_0_1 => {
96                let delegate = Delegate::from((&contract_code, &params));
97                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
98                    delegate,
99                )))
100            }
101        }
102    }
103}
104
105impl<'a> TryFromFbs<&FbsDelegateContainer<'a>> for DelegateContainer {
106    fn try_decode_fbs(container: &FbsDelegateContainer<'a>) -> Result<Self, WsApiError> {
107        match container.delegate_type() {
108            DelegateType::WasmDelegateV1 => {
109                let delegate = container.delegate_as_wasm_delegate_v1().unwrap();
110                let data = DelegateCode::from(delegate.data().data().bytes().to_vec());
111                let params = Parameters::from(delegate.parameters().bytes().to_vec());
112                Ok(DelegateContainer::Wasm(DelegateWasmAPIVersion::V1(
113                    Delegate::from((&data, &params)),
114                )))
115            }
116            _ => unreachable!(),
117        }
118    }
119}
120
121impl DelegateCode<'static> {
122    fn load_versioned(
123        mut contract_data: Cursor<Vec<u8>>,
124    ) -> Result<(Self, APIVersion), std::io::Error> {
125        // Get contract version
126        let version = contract_data
127            .read_u64::<BigEndian>()
128            .map_err(|_| std::io::ErrorKind::InvalidData)
129            .map(APIVersion::from_u64)?;
130
131        if version == APIVersion::Version0_0_1 {
132            let mut code_hash = [0u8; 32];
133            contract_data.read_exact(&mut code_hash)?;
134        }
135
136        // Get contract code
137        let mut code_data: Vec<u8> = vec![];
138        contract_data
139            .read_to_end(&mut code_data)
140            .map_err(|_| std::io::ErrorKind::InvalidData)?;
141        Ok((DelegateCode::from(code_data), version))
142    }
143
144    /// Loads contract code which has been versioned from the fs.
145    pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
146        let contract_data = Cursor::new(Self::load_bytes(path)?);
147        Self::load_versioned(contract_data)
148    }
149
150    /// Loads contract code which has been versioned from the fs.
151    pub fn load_versioned_from_bytes(
152        versioned_code: Vec<u8>,
153    ) -> Result<(Self, APIVersion), std::io::Error> {
154        let contract_data = Cursor::new(versioned_code);
155        Self::load_versioned(contract_data)
156    }
157}
158
159impl DelegateCode<'_> {
160    pub fn to_bytes_versioned(
161        &self,
162        version: APIVersion,
163    ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
164        match version {
165            APIVersion::Version0_0_1 => {
166                let output_size =
167                    std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
168                let mut output: Vec<u8> = Vec::with_capacity(output_size);
169                output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
170                output.extend(self.hash().0.iter());
171                output.extend(self.data());
172                Ok(output)
173            }
174        }
175    }
176}
177
178/// Contains the different versions available for WASM contracts.
179#[non_exhaustive]
180#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
181
182pub enum ContractWasmAPIVersion {
183    V1(WrappedContract),
184}
185
186impl From<ContractWasmAPIVersion> for ContractContainer {
187    fn from(value: ContractWasmAPIVersion) -> Self {
188        ContractContainer::Wasm(value)
189    }
190}
191
192impl Display for ContractWasmAPIVersion {
193    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
194        match self {
195            ContractWasmAPIVersion::V1(contract_v1) => {
196                write!(f, "[api=0.0.1]({contract_v1})")
197            }
198        }
199    }
200}
201
202/// Wrapper that allows contract versioning. This enum maintains the types of contracts that are
203/// allowed and their corresponding version.
204#[non_exhaustive]
205#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
206pub enum ContractContainer {
207    Wasm(ContractWasmAPIVersion),
208}
209
210impl From<ContractContainer> for APIVersion {
211    fn from(contract: ContractContainer) -> APIVersion {
212        match contract {
213            ContractContainer::Wasm(ContractWasmAPIVersion::V1(_)) => APIVersion::Version0_0_1,
214        }
215    }
216}
217
218impl ContractContainer {
219    /// Return the `ContractKey` from the specific contract version.
220    pub fn key(&self) -> ContractKey {
221        match self {
222            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => *contract_v1.key(),
223        }
224    }
225
226    /// Return the `ContractInstanceId` from the specific contract version.
227    pub fn id(&self) -> &ContractInstanceId {
228        match self {
229            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.key().id(),
230        }
231    }
232
233    /// Return the `Parameters` from the specific contract version.
234    pub fn params(&self) -> Parameters<'static> {
235        match self {
236            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.params().clone(),
237        }
238    }
239
240    /// Return the contract code from the specific contract version as `Vec<u8>`.
241    pub fn data(&self) -> &[u8] {
242        match self {
243            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1.data.data(),
244        }
245    }
246
247    pub fn unwrap_v1(self) -> WrappedContract {
248        match self {
249            Self::Wasm(ContractWasmAPIVersion::V1(contract_v1)) => contract_v1,
250        }
251    }
252}
253
254impl Display for ContractContainer {
255    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
256        match self {
257            ContractContainer::Wasm(wasm_version) => {
258                write!(f, "WasmContainer({wasm_version})")
259            }
260        }
261    }
262}
263
264impl<'a> TryFrom<(&'a Path, Parameters<'static>)> for ContractContainer {
265    type Error = std::io::Error;
266
267    fn try_from((path, params): (&'a Path, Parameters<'static>)) -> Result<Self, Self::Error> {
268        let (contract_code, version) = ContractCode::load_versioned_from_path(path)?;
269
270        match version {
271            APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
272                WrappedContract::new(Arc::new(contract_code), params),
273            ))),
274        }
275    }
276}
277
278impl<'a, P> TryFrom<(Vec<u8>, P)> for ContractContainer
279where
280    P: std::ops::Deref<Target = Parameters<'a>>,
281{
282    type Error = std::io::Error;
283
284    fn try_from((versioned_contract_bytes, params): (Vec<u8>, P)) -> Result<Self, Self::Error> {
285        let params = params.deref().clone().into_owned();
286
287        let (contract_code, version) =
288            ContractCode::load_versioned_from_bytes(versioned_contract_bytes)?;
289
290        match version {
291            APIVersion::Version0_0_1 => Ok(ContractContainer::Wasm(ContractWasmAPIVersion::V1(
292                WrappedContract::new(Arc::new(contract_code), params),
293            ))),
294        }
295    }
296}
297
298impl ContractCode<'static> {
299    fn load_versioned(
300        mut contract_data: Cursor<Vec<u8>>,
301    ) -> Result<(Self, APIVersion), std::io::Error> {
302        // Get contract version
303        let version = contract_data
304            .read_u64::<BigEndian>()
305            .map_err(|_| std::io::ErrorKind::InvalidData)
306            .map(APIVersion::from_u64)?;
307
308        if version == APIVersion::Version0_0_1 {
309            let mut code_hash = [0u8; 32];
310            contract_data.read_exact(&mut code_hash)?;
311        }
312
313        // Get contract code
314        let mut code_data: Vec<u8> = vec![];
315        contract_data
316            .read_to_end(&mut code_data)
317            .map_err(|_| std::io::ErrorKind::InvalidData)?;
318        Ok((ContractCode::from(code_data), version))
319    }
320
321    /// Loads contract code which has been versioned from the fs.
322    pub fn load_versioned_from_path(path: &Path) -> Result<(Self, APIVersion), std::io::Error> {
323        let contract_data = Cursor::new(Self::load_bytes(path)?);
324        Self::load_versioned(contract_data)
325    }
326
327    /// Loads contract code which has been versioned from the fs.
328    pub fn load_versioned_from_bytes(
329        versioned_code: Vec<u8>,
330    ) -> Result<(Self, APIVersion), std::io::Error> {
331        let contract_data = Cursor::new(versioned_code);
332        Self::load_versioned(contract_data)
333    }
334}
335
336#[derive(PartialEq, Eq, Clone, Copy)]
337pub enum APIVersion {
338    Version0_0_1,
339}
340
341impl APIVersion {
342    fn from_u64(version: u64) -> Self {
343        match version {
344            0 => Self::Version0_0_1,
345            _ => panic!("unsupported incremental API version: {version}"),
346        }
347    }
348
349    fn into_u64(self) -> u64 {
350        match self {
351            Self::Version0_0_1 => 0,
352        }
353    }
354}
355
356impl Display for APIVersion {
357    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
358        match self {
359            APIVersion::Version0_0_1 => write!(f, "0.0.1"),
360        }
361    }
362}
363
364impl<'a> TryFrom<&'a semver::Version> for APIVersion {
365    type Error = Box<dyn std::error::Error + Send + Sync>;
366    fn try_from(value: &'a semver::Version) -> Result<Self, Self::Error> {
367        match value {
368            ver if ver == &semver::Version::new(0, 0, 1) => Ok(APIVersion::Version0_0_1),
369            other => Err(format!("{other} version not supported").into()),
370        }
371    }
372}
373
374impl ContractCode<'_> {
375    pub fn to_bytes_versioned(
376        &self,
377        version: APIVersion,
378    ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync + 'static>> {
379        match version {
380            APIVersion::Version0_0_1 => {
381                let output_size =
382                    std::mem::size_of::<u64>() + self.data().len() + self.hash().0.len();
383                let mut output: Vec<u8> = Vec::with_capacity(output_size);
384                output.write_u64::<BigEndian>(APIVersion::Version0_0_1.into_u64())?;
385                output.extend(self.hash().0.iter());
386                output.extend(self.data());
387                Ok(output)
388            }
389        }
390    }
391}
392
393impl<'a> TryFromFbs<&FbsContractContainer<'a>> for ContractContainer {
394    fn try_decode_fbs(value: &FbsContractContainer<'a>) -> Result<Self, WsApiError> {
395        match value.contract_type() {
396            ContractType::WasmContractV1 => {
397                let contract = value.contract_as_wasm_contract_v1().unwrap();
398                let data = Arc::new(ContractCode::from(contract.data().data().bytes().to_vec()));
399                let params = Parameters::from(contract.parameters().bytes().to_vec());
400                let key = ContractKey::from_params_and_code(&params, &*data);
401                Ok(ContractContainer::from(V1(WrappedContract {
402                    data,
403                    params,
404                    key,
405                })))
406            }
407            _ => unreachable!(),
408        }
409    }
410}