1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
use self::{RibosomeEncodedValue::*, RibosomeErrorCode::*};
use crate::error::HolochainError;
use holochain_json_api::{error::JsonError, json::JsonString};

use bits_n_pieces::u64_split_bits;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{convert::TryFrom, str::FromStr};

/// size of the integer that encodes ribosome codes
pub type RibosomeEncodingBits = u64;
/// size of the integer that wasm sees
pub type RibosomeRuntimeBits = i64;
/// size of the integer that represents a ribosome code
pub type RibosomeCodeBits = u32;

#[derive(Clone, Debug, PartialEq)]
pub struct RibosomeEncodedAllocation(RibosomeEncodingBits);

impl From<RibosomeEncodedAllocation> for RibosomeEncodingBits {
    fn from(ribosome_memory_allocation: RibosomeEncodedAllocation) -> RibosomeEncodingBits {
        ribosome_memory_allocation.0
    }
}

impl From<RibosomeEncodingBits> for RibosomeEncodedAllocation {
    fn from(i: RibosomeEncodingBits) -> Self {
        Self(i)
    }
}

impl ToString for RibosomeEncodedAllocation {
    fn to_string(&self) -> String {
        RibosomeEncodingBits::from(self.to_owned()).to_string()
    }
}

/// Represents all possible values passed to/from wasmi functions
/// All wasmi functions are I64 values
#[repr(u64)]
#[derive(Clone, Debug, PartialEq)]
pub enum RibosomeEncodedValue {
    /// @TODO make this unambiguous or remove
    /// Contextually represents:
    /// - Function succeeded without any allocation
    /// - Empty/nil argument to a function
    /// - Zero length allocation (error)
    Success,
    /// A value that can be safely converted to a wasm allocation
    /// High bits represent offset, low bits represent length
    /// @see WasmAllocation
    Allocation(RibosomeEncodedAllocation),
    /// A value that should be interpreted as an error
    /// Low bits are zero, high bits map to an enum variant
    Failure(RibosomeErrorCode),
}

impl From<RibosomeEncodedValue> for RibosomeEncodingBits {
    fn from(ribosome_return_code: RibosomeEncodedValue) -> RibosomeEncodingBits {
        match ribosome_return_code {
            RibosomeEncodedValue::Success => 0,
            RibosomeEncodedValue::Allocation(allocation) => RibosomeEncodingBits::from(allocation),
            RibosomeEncodedValue::Failure(code) => {
                code as RibosomeRuntimeBits as RibosomeEncodingBits
            }
        }
    }
}

impl From<RibosomeEncodedValue> for RibosomeRuntimeBits {
    fn from(ribosome_return_code: RibosomeEncodedValue) -> RibosomeRuntimeBits {
        RibosomeEncodingBits::from(ribosome_return_code) as RibosomeRuntimeBits
    }
}

impl From<RibosomeEncodingBits> for RibosomeEncodedValue {
    fn from(i: RibosomeEncodingBits) -> Self {
        if i == 0 {
            RibosomeEncodedValue::Success
        } else {
            let (code_int, maybe_allocation_length) = u64_split_bits(i);
            if maybe_allocation_length == 0 {
                RibosomeEncodedValue::Failure(RibosomeErrorCode::from_code_int(code_int))
            } else {
                RibosomeEncodedValue::Allocation(RibosomeEncodedAllocation(i))
            }
        }
    }
}

impl ToString for RibosomeEncodedValue {
    fn to_string(&self) -> String {
        match self {
            Success => "Success".to_string(),
            Allocation(allocation) => allocation.to_string(),
            Failure(code) => code.to_string(),
        }
    }
}

impl From<RibosomeEncodedValue> for String {
    fn from(return_code: RibosomeEncodedValue) -> String {
        return_code.to_string()
    }
}

impl FromStr for RibosomeEncodedValue {
    type Err = HolochainError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "Success" => RibosomeEncodedValue::Success,
            _ => RibosomeEncodedValue::Failure(s.parse()?),
        })
    }
}

impl From<RibosomeEncodedValue> for JsonString {
    fn from(ribosome_return_code: RibosomeEncodedValue) -> JsonString {
        JsonString::from_json(&ribosome_return_code.to_string())
    }
}

impl From<HolochainError> for RibosomeEncodedValue {
    fn from(error: HolochainError) -> Self {
        RibosomeEncodedValue::Failure(RibosomeErrorCode::from(error))
    }
}

impl TryFrom<JsonString> for RibosomeEncodedValue {
    type Error = HolochainError;

    fn try_from(json_string: JsonString) -> Result<Self, Self::Error> {
        String::from(json_string).parse()
    }
}

impl RibosomeEncodedValue {
    pub fn from_error(err_code: RibosomeErrorCode) -> Self {
        Failure(err_code)
    }
}

/// Enum of all possible ERROR codes that a Zome API Function could return.
#[repr(u64)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, DefaultJson, PartialOrd, Ord)]
#[rustfmt::skip]
pub enum RibosomeErrorCode {
    Unspecified                     = 1 << 32,
    ArgumentDeserializationFailed   = 2 << 32,
    OutOfMemory                     = 3 << 32,
    ReceivedWrongActionResult       = 4 << 32,
    CallbackFailed                  = 5 << 32,
    RecursiveCallForbidden          = 6 << 32,
    ResponseSerializationFailed     = 7 << 32,
    NotAnAllocation                 = 8 << 32,
    ZeroSizedAllocation             = 9 << 32,
    UnknownEntryType                = 10 << 32,
    MismatchWasmCallDataType        = 11 << 32,
    EntryNotFound                   = 12 << 32,
    WorkflowFailed                  = 13 << 32,
}

#[rustfmt::skip]
impl RibosomeErrorCode {
    pub fn as_str(&self) -> &str {
        match self {
            Unspecified                     => "Unspecified",
            ArgumentDeserializationFailed   => "Argument deserialization failed",
            OutOfMemory                     => "Out of memory",
            ReceivedWrongActionResult       => "Received wrong action result",
            CallbackFailed                  => "Callback failed",
            RecursiveCallForbidden          => "Recursive call forbidden",
            ResponseSerializationFailed     => "Response serialization failed",
            NotAnAllocation                 => "Not an allocation",
            ZeroSizedAllocation             => "Zero-sized allocation",
            UnknownEntryType                => "Unknown entry type",
            MismatchWasmCallDataType        => "Mismatched WasmCallData type",
            EntryNotFound                   => "Entry Could Not Be Found",
            WorkflowFailed                  => "Workflow failed",
        }
    }
}

impl From<HolochainError> for RibosomeErrorCode {
    fn from(error: HolochainError) -> RibosomeErrorCode {
        // the mapping between HolochainError and RibosomeErrorCode is pretty poor overall
        match error {
            HolochainError::ErrorGeneric(_) => RibosomeErrorCode::Unspecified,
            HolochainError::CryptoError(_) => RibosomeErrorCode::Unspecified,
            HolochainError::NotImplemented(_) => RibosomeErrorCode::CallbackFailed,
            HolochainError::LoggingError => RibosomeErrorCode::Unspecified,
            HolochainError::DnaMissing => RibosomeErrorCode::Unspecified,
            HolochainError::Dna(_) => RibosomeErrorCode::Unspecified,
            HolochainError::IoError(_) => RibosomeErrorCode::Unspecified,
            HolochainError::SerializationError(_) => {
                RibosomeErrorCode::ArgumentDeserializationFailed
            }
            HolochainError::InvalidOperationOnSysEntry => RibosomeErrorCode::UnknownEntryType,
            HolochainError::CapabilityCheckFailed => RibosomeErrorCode::Unspecified,
            HolochainError::ValidationFailed(_) => RibosomeErrorCode::CallbackFailed,
            HolochainError::ValidationPending => RibosomeErrorCode::Unspecified,
            HolochainError::Ribosome(e) => e,
            HolochainError::RibosomeFailed(_) => RibosomeErrorCode::CallbackFailed,
            HolochainError::ConfigError(_) => RibosomeErrorCode::Unspecified,
            HolochainError::Timeout => RibosomeErrorCode::Unspecified,
            HolochainError::InitializationFailed(_) => RibosomeErrorCode::Unspecified,
            HolochainError::LifecycleError(_) => RibosomeErrorCode::Unspecified,
            HolochainError::DnaHashMismatch(_, _) => RibosomeErrorCode::Unspecified,
            HolochainError::EntryNotFoundLocally => RibosomeErrorCode::Unspecified,
            HolochainError::EntryIsPrivate => RibosomeErrorCode::Unspecified,
            HolochainError::List(_) => RibosomeErrorCode::Unspecified,
        }
    }
}

impl ToString for RibosomeErrorCode {
    fn to_string(&self) -> String {
        self.as_str().to_string()
    }
}

impl From<RibosomeErrorCode> for String {
    fn from(ribosome_error_code: RibosomeErrorCode) -> Self {
        ribosome_error_code.to_string()
    }
}

impl RibosomeErrorCode {
    pub fn from_code_int(code: RibosomeCodeBits) -> Self {
        match code {
            0 => panic!(format!("RibosomeErrorCode == {:?} encountered", code)),
            2 => ArgumentDeserializationFailed,
            3 => OutOfMemory,
            4 => ReceivedWrongActionResult,
            5 => CallbackFailed,
            6 => RecursiveCallForbidden,
            7 => ResponseSerializationFailed,
            8 => NotAnAllocation,
            9 => ZeroSizedAllocation,
            10 => UnknownEntryType,
            12 => EntryNotFound,
            13 => WorkflowFailed,
            1 | _ => Unspecified,
        }
    }

    pub fn from_return_code(ret_code: RibosomeEncodedValue) -> Self {
        match ret_code {
            Failure(rib_err) => rib_err,
            _ => panic!(format!(
                "RibosomeEncodedValue == {:?} encountered",
                ret_code
            )),
        }
    }
}

// @TODO review this serialization, can it be an i32 instead of a full string?
// @see https://github.com/holochain/holochain-rust/issues/591
impl Serialize for RibosomeErrorCode {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.to_string())
    }
}

impl<'de> Deserialize<'de> for RibosomeErrorCode {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Ok(RibosomeErrorCode::from_str(&s).expect("could not deserialize RibosomeErrorCode"))
    }
}

impl FromStr for RibosomeErrorCode {
    type Err = HolochainError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Unspecified" => Ok(RibosomeErrorCode::Unspecified),
            "Argument deserialization failed" => {
                Ok(RibosomeErrorCode::ArgumentDeserializationFailed)
            }
            "Out of memory" => Ok(RibosomeErrorCode::OutOfMemory),
            "Received wrong action result" => Ok(RibosomeErrorCode::ReceivedWrongActionResult),
            "Callback failed" => Ok(RibosomeErrorCode::CallbackFailed),
            "Recursive call forbidden" => Ok(RibosomeErrorCode::RecursiveCallForbidden),
            "Response serialization failed" => Ok(RibosomeErrorCode::ResponseSerializationFailed),
            "Not an allocation" => Ok(RibosomeErrorCode::NotAnAllocation),
            "Zero-sized allocation" => Ok(RibosomeErrorCode::ZeroSizedAllocation),
            "Unknown entry type" => Ok(RibosomeErrorCode::UnknownEntryType),
            "Entry Could Not Be Found" => Ok(EntryNotFound),
            "Workflow failed" => Ok(WorkflowFailed),
            _ => Err(HolochainError::ErrorGeneric(String::from(
                "Unknown RibosomeErrorCode",
            ))),
        }
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;

    #[test]
    fn ribosome_error_code_round_trip() {
        let oom = RibosomeErrorCode::from_code_int(
            ((RibosomeErrorCode::OutOfMemory as u64) >> 32) as RibosomeCodeBits,
        );
        assert_eq!(RibosomeErrorCode::OutOfMemory, oom);
        assert_eq!(RibosomeErrorCode::OutOfMemory.to_string(), oom.to_string());
    }

    #[test]
    fn error_conversion() {
        // TODO could use strum crate to iteratively
        // gather all known codes.
        for code in 1..=13 {
            let mut err = RibosomeErrorCode::from_code_int(code);

            let err_str = err.as_str().to_owned();

            err = err_str.parse().expect("unable to parse error");

            let inner_code = RibosomeEncodedValue::from_error(err);

            let _one_int: i64 = inner_code.clone().into();
            let _another_int: u64 = inner_code.clone().into();
        }
    }

    #[test]
    #[should_panic]
    fn code_zero() {
        RibosomeErrorCode::from_code_int(0);
    }
}