Skip to content

Commit d7ded73

Browse files
committed
Implement Decoder/Encoder support for i/u128
1 parent f944588 commit d7ded73

File tree

6 files changed

+176
-0
lines changed

6 files changed

+176
-0
lines changed

rustler/src/types/i128.rs

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use crate::{Decoder, Encoder, Env, Error, NifResult, Term};
2+
use std::convert::TryFrom;
3+
4+
const EXTERNAL_TERM_FORMAT_VERSION: u8 = 131;
5+
const SMALL_BIG_EXT: u8 = 110;
6+
7+
impl Encoder for i128 {
8+
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
9+
if let Ok(int) = i64::try_from(*self) {
10+
int.encode(env)
11+
} else {
12+
let mut etf = [0u8; 4 + 16];
13+
etf[0] = EXTERNAL_TERM_FORMAT_VERSION;
14+
etf[1] = SMALL_BIG_EXT;
15+
etf[2] = 16; // length in bytes
16+
if *self < 0 {
17+
etf[3] = 1;
18+
let bytes = (-self).to_le_bytes();
19+
etf[4..].copy_from_slice(&bytes);
20+
} else {
21+
etf[4..].copy_from_slice(&self.to_le_bytes());
22+
}
23+
let (term, _) = env.binary_to_term(&etf).unwrap();
24+
term
25+
}
26+
}
27+
}
28+
29+
impl Encoder for u128 {
30+
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
31+
if let Ok(int) = u64::try_from(*self) {
32+
int.encode(env)
33+
} else {
34+
let mut etf = [0u8; 4 + 16];
35+
etf[0] = EXTERNAL_TERM_FORMAT_VERSION;
36+
etf[1] = SMALL_BIG_EXT;
37+
etf[2] = 16; // length in bytes
38+
etf[4..].copy_from_slice(&self.to_le_bytes());
39+
let (term, _) = env.binary_to_term(&etf).unwrap();
40+
term
41+
}
42+
}
43+
}
44+
45+
impl<'a> Decoder<'a> for i128 {
46+
fn decode(term: Term<'a>) -> NifResult<Self> {
47+
if !term.is_integer() {
48+
return Err(Error::BadArg);
49+
}
50+
51+
if let Ok(int) = term.decode::<i64>() {
52+
return Ok(int as i128);
53+
}
54+
55+
let input = term.to_binary();
56+
let input = input.as_slice();
57+
if input.len() < 4 {
58+
return Err(Error::BadArg);
59+
}
60+
61+
if input[0] != EXTERNAL_TERM_FORMAT_VERSION {
62+
return Err(Error::BadArg);
63+
}
64+
65+
if input[1] != SMALL_BIG_EXT {
66+
return Err(Error::BadArg);
67+
}
68+
69+
let n = input[2] as usize;
70+
if n > 16 {
71+
return Err(Error::BadArg);
72+
}
73+
74+
let mut res = [0u8; 16];
75+
res[16 - n..].copy_from_slice(&input[4..4 + n]);
76+
let res = i128::from_le_bytes(res);
77+
if res < 0 {
78+
// The stored data is supposed to be unsigned, so if we interpret it as negative here,
79+
// it was too large.
80+
return Err(Error::BadArg);
81+
}
82+
83+
if input[3] == 0 {
84+
Ok(res)
85+
} else {
86+
Ok(-res)
87+
}
88+
}
89+
}
90+
91+
impl<'a> Decoder<'a> for u128 {
92+
fn decode(term: Term<'a>) -> NifResult<Self> {
93+
if !term.is_integer() {
94+
return Err(Error::BadArg);
95+
}
96+
97+
if let Ok(int) = term.decode::<u64>() {
98+
return Ok(int as u128);
99+
}
100+
101+
let input = term.to_binary();
102+
let input = input.as_slice();
103+
104+
if input.len() < 4 {
105+
return Err(Error::BadArg);
106+
}
107+
108+
if input[0] != EXTERNAL_TERM_FORMAT_VERSION {
109+
return Err(Error::BadArg);
110+
}
111+
112+
if input[1] != SMALL_BIG_EXT {
113+
return Err(Error::BadArg);
114+
}
115+
116+
let n = input[2] as usize;
117+
if n > 16 {
118+
return Err(Error::BadArg);
119+
}
120+
121+
if input[3] == 1 {
122+
// Negative value
123+
return Err(Error::BadArg);
124+
}
125+
126+
let mut res = [0u8; 16];
127+
res[16 - n..].copy_from_slice(&input[4..4 + n]);
128+
Ok(u128::from_le_bytes(res))
129+
}
130+
}

rustler/src/types/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{Env, Error, NifResult, Term};
22

33
#[macro_use]
44
pub mod atom;
5+
pub mod i128;
56
pub use crate::types::atom::Atom;
67

78
pub mod binary;

rustler_tests/lib/rustler_test.ex

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ defmodule RustlerTest do
2424
def add_u32(_, _), do: err()
2525
def add_i32(_, _), do: err()
2626
def echo_u8(_), do: err()
27+
def echo_u128(_), do: err()
28+
def echo_i128(_), do: err()
2729
def option_inc(_), do: err()
2830
def erlang_option_inc(_), do: err()
2931
def result_to_int(_), do: err()

rustler_tests/native/rustler_test/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ rustler::init!(
2323
test_primitives::option_inc,
2424
test_primitives::erlang_option_inc,
2525
test_primitives::result_to_int,
26+
test_primitives::echo_u128,
27+
test_primitives::echo_i128,
2628
test_list::sum_list,
2729
test_list::make_list,
2830
test_term::term_debug,

rustler_tests/native/rustler_test/src/test_primitives.rs

+10
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ pub fn result_to_int(res: Result<bool, &str>) -> Result<usize, String> {
3333
Err(errstr) => Err(format!("{}{}", errstr, errstr)),
3434
}
3535
}
36+
37+
#[rustler::nif]
38+
pub fn echo_u128(n: u128) -> u128 {
39+
n
40+
}
41+
42+
#[rustler::nif]
43+
pub fn echo_i128(n: i128) -> i128 {
44+
n
45+
}

rustler_tests/test/primitives_test.exs

+31
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,35 @@ defmodule RustlerTest.PrimitivesTest do
3232
assert {:error, "watwat"} == RustlerTest.result_to_int({:error, "wat"})
3333
assert_raise ArgumentError, fn -> RustlerTest.result_to_int({:great, true}) end
3434
end
35+
36+
test "i128 support" do
37+
import Bitwise
38+
39+
i = 1 <<< 62
40+
assert i == RustlerTest.echo_i128(i)
41+
assert -i == RustlerTest.echo_i128(-i)
42+
43+
i = 1 <<< 126
44+
assert i == RustlerTest.echo_i128(i)
45+
assert -i == RustlerTest.echo_i128(-i)
46+
47+
assert_raise ArgumentError, fn -> RustlerTest.echo_i128(:non_int) end
48+
assert_raise ArgumentError, fn -> RustlerTest.echo_i128(123.45) end
49+
assert_raise ArgumentError, fn -> RustlerTest.echo_i128(1 <<< 127) end
50+
assert_raise ArgumentError, fn -> RustlerTest.echo_i128(1 <<< 128) end
51+
end
52+
53+
test "u128 support" do
54+
import Bitwise
55+
56+
i = 1 <<< 63
57+
assert i == RustlerTest.echo_u128(i)
58+
59+
i = 1 <<< 127
60+
assert i == RustlerTest.echo_u128(i)
61+
62+
assert_raise ArgumentError, fn -> RustlerTest.echo_u128(:non_int) end
63+
assert_raise ArgumentError, fn -> RustlerTest.echo_u128(123.45) end
64+
assert_raise ArgumentError, fn -> RustlerTest.echo_i128(1 <<< 128) end
65+
end
3566
end

0 commit comments

Comments
 (0)