Skip to content

Commit 54e2459

Browse files
committed
feat(rad): new RadonBytes operators
1 parent 62a291a commit 54e2459

File tree

5 files changed

+105
-7
lines changed

5 files changed

+105
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rad/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ tokio = "1.44.1"
1414

1515
[dependencies]
1616
anyhow = "1.0.98"
17+
base64 = "0.22.1"
1718
cbor-codec = { git = "https://github.com/witnet/cbor-codec.git", branch = "feat/ldexpf-shim" }
1819
futures = "0.3.31"
1920
hex = "0.4.1"

rad/src/operators/bytes.rs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
1+
use base64::Engine;
12
use serde_cbor::value::{Value, from_value};
23
use std::convert::TryFrom;
34

45
use crate::{
56
error::RadError,
67
hash_functions::{self, RadonHashFunctions},
7-
types::{RadonType, bytes::RadonBytes, string::RadonString},
8+
types::{
9+
RadonType,
10+
bytes::{RadonBytes, RadonBytesEncoding},
11+
integer::RadonInteger,
12+
string::RadonString,
13+
},
814
};
915

10-
pub fn to_string(input: &RadonBytes) -> Result<RadonString, RadError> {
11-
RadonString::try_from(Value::Text(hex::encode(input.value())))
16+
pub fn as_integer(input: &RadonBytes) -> Result<RadonInteger, RadError> {
17+
let input_value_len = input.value().len();
18+
match input_value_len {
19+
1..=16 => {
20+
let mut bytes_array = [0u8; 16];
21+
bytes_array[16 - input_value_len..].copy_from_slice(&input.value());
22+
Ok(RadonInteger::from(i128::from_be_bytes(bytes_array)))
23+
}
24+
17.. => Err(RadError::ParseInt {
25+
message: "Input buffer too big".to_string(),
26+
}),
27+
_ => Err(RadError::EmptyArray),
28+
}
1229
}
1330

1431
pub fn hash(input: &RadonBytes, args: &[Value]) -> Result<RadonBytes, RadError> {
@@ -27,14 +44,70 @@ pub fn hash(input: &RadonBytes, args: &[Value]) -> Result<RadonBytes, RadError>
2744

2845
Ok(RadonBytes::from(digest))
2946
}
47+
48+
pub fn length(input: &RadonBytes) -> RadonInteger {
49+
RadonInteger::from(input.value().len() as i128)
50+
}
51+
52+
pub fn slice(input: &RadonBytes, args: &[Value]) -> Result<RadonBytes, RadError> {
53+
let wrong_args = || RadError::WrongArguments {
54+
input_type: RadonString::radon_type_name(),
55+
operator: "BytesSlice".to_string(),
56+
args: args.to_vec(),
57+
};
58+
let end_index = input.value().len();
59+
if end_index > 0 {
60+
let start_index = from_value::<i64>(args[0].clone())
61+
.unwrap_or_default()
62+
.rem_euclid(end_index as i64) as usize;
63+
let mut slice = input.value().as_slice().split_at(start_index).1.to_vec();
64+
if args.len() == 2 {
65+
let end_index = from_value::<i64>(args[1].clone())
66+
.unwrap_or_default()
67+
.rem_euclid(end_index as i64) as usize;
68+
slice.truncate(end_index - start_index);
69+
}
70+
Ok(RadonBytes::from(slice))
71+
} else {
72+
Err(wrong_args())
73+
}
74+
}
75+
76+
pub fn to_string(input: &RadonBytes, args: &Option<Vec<Value>>) -> Result<RadonString, RadError> {
77+
let wrong_args = || RadError::WrongArguments {
78+
input_type: RadonString::radon_type_name(),
79+
operator: "Stringify".to_string(),
80+
args: args.to_owned().unwrap_or_default().to_vec(),
81+
};
82+
let mut bytes_encoding = RadonBytesEncoding::Hex;
83+
if let Some(args) = args {
84+
if !args.is_empty() {
85+
let arg = args.first().ok_or_else(wrong_args)?.to_owned();
86+
let bytes_encoding_u8 = from_value::<u8>(arg).map_err(|_| wrong_args())?;
87+
bytes_encoding =
88+
RadonBytesEncoding::try_from(bytes_encoding_u8).map_err(|_| wrong_args())?;
89+
}
90+
}
91+
match bytes_encoding {
92+
RadonBytesEncoding::Hex => RadonString::try_from(Value::Text(hex::encode(input.value()))),
93+
RadonBytesEncoding::Base64 => RadonString::try_from(Value::Text(
94+
base64::engine::general_purpose::STANDARD.encode(input.value()),
95+
)),
96+
RadonBytesEncoding::Utf8 => Ok(RadonString::from(
97+
String::from_utf8(input.value().to_vec()).unwrap_or_default(),
98+
)),
99+
}
100+
}
101+
30102
#[cfg(test)]
31103
mod tests {
32104
use super::*;
33105

34106
#[test]
35107
fn test_bytes_to_string() {
36108
let input = RadonBytes::from(vec![0x01, 0x02, 0x03]);
37-
let output = to_string(&input).unwrap().value();
109+
let valid_args = Some(vec![Value::from(0x00)]);
110+
let output = to_string(&input, &valid_args).unwrap().value();
38111

39112
let valid_expected = "010203".to_string();
40113

rad/src/operators/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ pub enum RadonOpCodes {
4949
BooleanNegate = 0x22,
5050
///////////////////////////////////////////////////////////////////////
5151
// Bytes operator codes (start at 0x30)
52-
BytesAsString = 0x30,
52+
BytesToString = 0x30,
5353
BytesHash = 0x31,
54+
BytesAsInteger = 0x32,
55+
BytesLength = 0x34,
56+
BytesSlice = 0x3C,
57+
5458
///////////////////////////////////////////////////////////////////////
5559
// Integer operator codes (start at 0x40)
5660
IntegerAbsolute = 0x40,

rad/src/types/bytes.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::{
44
script::RadonCall,
55
types::{RadonType, RadonTypes},
66
};
7+
use num_enum::TryFromPrimitive;
8+
use serde::Serialize;
79
use serde_cbor::value::Value;
810
use std::{
911
convert::{TryFrom, TryInto},
@@ -13,6 +15,16 @@ use witnet_data_structures::radon_report::ReportContext;
1315

1416
const RADON_BYTES_TYPE_NAME: &str = "RadonBytes";
1517

18+
/// List of support string-encoding algorithms for buffers
19+
#[derive(Debug, Default, PartialEq, Eq, Serialize, TryFromPrimitive)]
20+
#[repr(u8)]
21+
pub enum RadonBytesEncoding {
22+
#[default]
23+
Hex = 0,
24+
Base64 = 1,
25+
Utf8 = 2,
26+
}
27+
1628
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
1729
pub struct RadonBytes {
1830
value: Vec<u8>,
@@ -84,12 +96,19 @@ impl Operable for RadonBytes {
8496
match call {
8597
// Identity
8698
(RadonOpCodes::Identity, None) => identity(RadonTypes::from(self.clone())),
87-
(RadonOpCodes::BytesAsString, None) => {
88-
bytes_operators::to_string(self).map(RadonTypes::from)
99+
(RadonOpCodes::BytesAsInteger, None) => bytes_operators::as_integer(self)
100+
.map(RadonTypes::from),
101+
(RadonOpCodes::BytesLength, None) => {
102+
Ok(RadonTypes::from(bytes_operators::length(self)))
89103
}
90104
(RadonOpCodes::BytesHash, Some(args)) => {
91105
bytes_operators::hash(self, args.as_slice()).map(RadonTypes::from)
92106
}
107+
(RadonOpCodes::BytesSlice, Some(args)) => bytes_operators::slice(self, args.as_slice())
108+
.map(RadonTypes::from),
109+
(RadonOpCodes::BytesToString, args) => bytes_operators::to_string(self, args)
110+
.map(RadonTypes::from),
111+
93112
// Unsupported / unimplemented
94113
(op_code, args) => Err(RadError::UnsupportedOperator {
95114
input_type: RADON_BYTES_TYPE_NAME.to_string(),

0 commit comments

Comments
 (0)