my ts rlp decoder seems to work
i feel like i would have more confidence in my code if i didn't test it
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
export {
|
||||||
|
decoded_data,
|
||||||
|
decode_result,
|
||||||
|
decode
|
||||||
|
}
|
||||||
|
|
||||||
|
type decoded_data
|
||||||
|
= Uint8Array
|
||||||
|
| Array<decoded_data>;
|
||||||
|
|
||||||
|
type decode_result
|
||||||
|
= {decoded_data : decoded_data,
|
||||||
|
remainder : Uint8Array};
|
||||||
|
|
||||||
|
function
|
||||||
|
decode(bytes: Uint8Array): decode_result {
|
||||||
|
// check the first byte
|
||||||
|
let first_byte: number = bytes[0];
|
||||||
|
let rest : Uint8Array = bytes.slice(1);
|
||||||
|
// if the first byte is between 0 and 127, that is the data
|
||||||
|
if
|
||||||
|
(first_byte <= 127) {
|
||||||
|
return dr(new Uint8Array([first_byte]), rest);
|
||||||
|
}
|
||||||
|
// if the first byte is between 128 and 183 = 128 + 55, it is a bytestring
|
||||||
|
// and the length is Byte - 128
|
||||||
|
else if
|
||||||
|
(first_byte <= 183) {
|
||||||
|
let payload_byte_length : number = first_byte - 128;
|
||||||
|
let payload : Uint8Array = rest.slice(0, payload_byte_length);
|
||||||
|
let rest2 : Uint8Array = rest.slice(payload_byte_length);
|
||||||
|
return dr(payload, rest2);
|
||||||
|
}
|
||||||
|
// if the first byte is between 184 = 183 + 1 and 191 = 183 + 8, it is a
|
||||||
|
// bytestring. the byte length of bytestring is FirstByte - 183. Then pull
|
||||||
|
// out the actual data
|
||||||
|
else if
|
||||||
|
(first_byte <= 191) {
|
||||||
|
let byte_length_of_byte_length : number = first_byte - 183;
|
||||||
|
let bytes_of_byte_length : Uint8Array = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length : number = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let bytes : Uint8Array = rest.slice(byte_length_of_byte_length,
|
||||||
|
byte_length + byte_length_of_byte_length);
|
||||||
|
let rest2 : Uint8Array = rest.slice(byte_length + byte_length_of_byte_length);
|
||||||
|
return dr(bytes, rest2);
|
||||||
|
}
|
||||||
|
// If the first byte is between 192 and 247 = 192 + 55, it is a list. The
|
||||||
|
// byte length of the list-payload is FirstByte - 192. Then the list
|
||||||
|
// payload, which needs to be decoded on its own.
|
||||||
|
else if
|
||||||
|
(first_byte <= 247) {
|
||||||
|
let byte_length_of_list : number = first_byte - 192;
|
||||||
|
let list_payload : Uint8Array = rest.slice(0, byte_length_of_list);
|
||||||
|
let list : Array<decoded_data> = decode_list(list_payload);
|
||||||
|
let rest2 : Uint8Array = rest.slice(byte_length_of_list);
|
||||||
|
return dr(list, rest2);
|
||||||
|
}
|
||||||
|
// If the first byte is between 248 = 247 + 1 and 255 = 247 + 8, it is a
|
||||||
|
// list. The byte length of the byte length of the list-payload is
|
||||||
|
// FirstByte - 247. Then the byte length of the list. Then the list
|
||||||
|
// payload, which needs to be decoded on its own.
|
||||||
|
else {
|
||||||
|
let byte_length_of_byte_length : number = first_byte - 247;
|
||||||
|
let bytes_of_byte_length : Uint8Array = rest.slice(0, byte_length_of_byte_length);
|
||||||
|
let byte_length : number = bytes_to_number(bytes_of_byte_length);
|
||||||
|
let list_bytes : Uint8Array = rest.slice(byte_length_of_byte_length,
|
||||||
|
byte_length + byte_length_of_byte_length);
|
||||||
|
let list : Array<decoded_data> = decode_list(list_bytes);
|
||||||
|
let rest2 : Uint8Array = rest.slice(byte_length + byte_length_of_byte_length);
|
||||||
|
return dr(list, rest2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decode_list(bytes: Uint8Array): Array<decoded_data> {
|
||||||
|
let arr : Array<decoded_data> = [];
|
||||||
|
while (bytes.length > 0) {
|
||||||
|
// grab an item off the bytes
|
||||||
|
let {decoded_data, remainder} = decode(bytes);
|
||||||
|
// push it
|
||||||
|
arr.push(decoded_data);
|
||||||
|
// update bytes
|
||||||
|
bytes = remainder;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert bytestring to number
|
||||||
|
function bytes_to_number(bytes: Uint8Array) {
|
||||||
|
let n : number = 0;
|
||||||
|
for (let b of bytes) {
|
||||||
|
n <<= 8;
|
||||||
|
n += b;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dr(x : decoded_data, y : Uint8Array) {
|
||||||
|
return {decoded_data: x, remainder: y};
|
||||||
|
}
|
||||||
@@ -20,6 +20,13 @@
|
|||||||
<h5>Failed case log:</h5>
|
<h5>Failed case log:</h5>
|
||||||
<pre id="b64-assershins"></pre>
|
<pre id="b64-assershins"></pre>
|
||||||
|
|
||||||
|
<h1>RLP Encode/Decode Tests</h1>
|
||||||
|
<p>Current case: <span id="rlp-current-case"></span></p>
|
||||||
|
<p>Total cases: <span id="rlp-total-cases"></span></p>
|
||||||
|
<button id="rlp-go">Go!</button>
|
||||||
|
<h5>Failed case log:</h5>
|
||||||
|
<pre id="rlp-assershins"></pre>
|
||||||
|
|
||||||
<script type="module" src="dist/test.js"></script>
|
<script type="module" src="dist/test.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import * as rlpcases from './rlpcases.js';
|
||||||
|
|
||||||
|
export const rlp = rlpcases.cases;
|
||||||
|
|
||||||
export const base58 = [
|
export const base58 = [
|
||||||
{encoded: "1",
|
{encoded: "1",
|
||||||
decoded: new Uint8Array([0])},
|
decoded: new Uint8Array([0])},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,52 @@ import * as cases from './cases.js';
|
|||||||
|
|
||||||
import * as b64 from './jex_include/local-vanillae-0.1.0/dist/base64.js';
|
import * as b64 from './jex_include/local-vanillae-0.1.0/dist/base64.js';
|
||||||
import * as b58 from './jex_include/local-vanillae-0.1.0/dist/base58.js';
|
import * as b58 from './jex_include/local-vanillae-0.1.0/dist/base58.js';
|
||||||
|
import * as rlp from './jex_include/local-vanillae-0.1.0/dist/rlp.js';
|
||||||
|
|
||||||
function uint8arr_eq(a: Uint8Array, b: Uint8Array) {
|
|
||||||
|
function deepeq(dd1: rlp.decoded_data, dd2: rlp.decoded_data): boolean {
|
||||||
|
// if both are Uint8Arrays
|
||||||
|
if ((dd1 instanceof Uint8Array) && (dd2 instanceof Uint8Array)) {
|
||||||
|
return uint8arr_eq(dd1, dd2);
|
||||||
|
}
|
||||||
|
// if both are Arrays
|
||||||
|
else if ((dd1 instanceof Array) && (dd2 instanceof Array)) {
|
||||||
|
return arr_eq(dd1, dd2);
|
||||||
|
}
|
||||||
|
// otherwise
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function arr_eq(a: Array<rlp.decoded_data>, b: Array<rlp.decoded_data>): boolean {
|
||||||
|
// make sure they have the same length
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// they have the same length now
|
||||||
|
else {
|
||||||
|
let len : number = a.length;
|
||||||
|
// loop over the items
|
||||||
|
for (let i = 0;
|
||||||
|
i < len;
|
||||||
|
i++)
|
||||||
|
{
|
||||||
|
let a_elt : rlp.decoded_data = a[i];
|
||||||
|
let b_elt : rlp.decoded_data = b[i];
|
||||||
|
let eq : boolean = deepeq(a_elt, b_elt);
|
||||||
|
// if they are not equal, break and return false
|
||||||
|
if (!eq) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// at the end, if we havent' proven ourselves wrong yet,
|
||||||
|
// the arrays are equal
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uint8arr_eq(a: Uint8Array, b: Uint8Array): boolean {
|
||||||
// first test if the length
|
// first test if the length
|
||||||
if (a.length !== b.length) {
|
if (a.length !== b.length) {
|
||||||
return false;
|
return false;
|
||||||
@@ -108,15 +152,58 @@ function b58_tests(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function rlp_tests(): void {
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let rlp_pre : HTMLElement = document.getElementById('rlp-assershins');
|
||||||
|
// @ts-ignore ts can't prove to itself that the element exists
|
||||||
|
let rlp_casen : HTMLElement = document.getElementById('rlp-current-case');
|
||||||
|
let case_n : number = 1;
|
||||||
|
|
||||||
|
for(let this_case of cases.rlp) {
|
||||||
|
// i love this type error
|
||||||
|
// can't set the inner html to a number
|
||||||
|
// but a string plus a number is totally cool
|
||||||
|
rlp_casen.innerHTML = '' + case_n;
|
||||||
|
case_n++;
|
||||||
|
|
||||||
|
let {encoded, decoded} = this_case;
|
||||||
|
//let my_encoded = rlp.encode(decoded);
|
||||||
|
let my_decoded = rlp.decode(encoded).decoded_data;
|
||||||
|
|
||||||
|
//let encodes_correctly = (encoded === my_encoded);
|
||||||
|
let decodes_correctly = deepeq(decoded, my_decoded);
|
||||||
|
|
||||||
|
//if (!encodes_correctly) {
|
||||||
|
// rlp_pre.innerHTML +=
|
||||||
|
// '===================================\n' +
|
||||||
|
// 'FAILED CASE: encode\n' +
|
||||||
|
// '===================================\n' +
|
||||||
|
// 'decoded : ' + decoded + '\n' +
|
||||||
|
// 'expected: ' + encoded + '\n' +
|
||||||
|
// 'actual : ' + my_encoded + '\n\n' ;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (!decodes_correctly) {
|
||||||
|
rlp_pre.innerHTML +=
|
||||||
|
'===================================\n' +
|
||||||
|
'FAILED CASE: decode\n' +
|
||||||
|
'===================================\n' +
|
||||||
|
'encoded : ' + encoded + '\n' +
|
||||||
|
'expected: ' + decoded + '\n' +
|
||||||
|
'actual : ' + my_decoded + '\n\n' ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function main(): void {
|
function main(): void {
|
||||||
// @ts-ignore ts can't prove to itself that the element exists
|
document.getElementById('b64-total-cases')!.innerHTML = '' + cases.base64.length;
|
||||||
document.getElementById('b64-total-cases')!.innerHTML = cases.base64.length;
|
|
||||||
// @ts-ignore ts can't prove to itself that the element exists
|
|
||||||
document.getElementById('b64-go')!.onclick = b64_tests;
|
document.getElementById('b64-go')!.onclick = b64_tests;
|
||||||
|
|
||||||
// @ts-ignore ts can't prove to itself that the element exists
|
document.getElementById('b58-total-cases')!.innerHTML = '' + cases.base58.length;
|
||||||
document.getElementById('b58-total-cases')!.innerHTML = cases.base58.length;
|
|
||||||
// @ts-ignore ts can't prove to itself that the element exists
|
|
||||||
document.getElementById('b58-go')!.onclick = b58_tests;
|
document.getElementById('b58-go')!.onclick = b58_tests;
|
||||||
|
|
||||||
|
document.getElementById('rlp-total-cases')!.innerHTML = '' + cases.rlp.length;
|
||||||
|
document.getElementById('rlp-go')!.onclick = rlp_tests;
|
||||||
}
|
}
|
||||||
main();
|
main();
|
||||||
|
|||||||
@@ -2,15 +2,83 @@
|
|||||||
%-compile(export_all).
|
%-compile(export_all).
|
||||||
|
|
||||||
-mode(compile).
|
-mode(compile).
|
||||||
%-compile(nowarn_unused).
|
-compile([nowarn_unused_function]).
|
||||||
|
|
||||||
main([]) ->
|
main([]) ->
|
||||||
% Decoded cases
|
% Decoded cases
|
||||||
DecodedCases = rand_decode_datas(100_000),
|
DecodedCases = rand_decode_datas(10_000),
|
||||||
%io:format("~p~n", [DecodedCases]),
|
%io:format("~p~n", [DecodedCases]),
|
||||||
io:format("~s~n", [format_cases_py(DecodedCases)]),
|
io:format("~s~n", [format_cases_js(DecodedCases)]),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%% JS FORMATTING %%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
format_cases_js(Cases) ->
|
||||||
|
format_stringlist_js(lists:map(fun format_case_js/1, Cases)).
|
||||||
|
|
||||||
|
% format a list of strings into a python list
|
||||||
|
format_stringlist_js(List) ->
|
||||||
|
% stringlist commas are the same in python/js, so reusing python
|
||||||
|
["import * as rlp from './jex_include/local-vanillae-0.1.0/dist/rlp.js';\n"
|
||||||
|
"\n"
|
||||||
|
"type rlpcases = {decoded: rlp.decoded_data, encoded: Uint8Array};\n"
|
||||||
|
"\n"
|
||||||
|
"// @ts-ignore never mind; jesus christ\n"
|
||||||
|
"export const cases : Array<rlpcases> = [\n", slcommas(List, []), "];"].
|
||||||
|
|
||||||
|
% input: decoded_data
|
||||||
|
% format a case as
|
||||||
|
% {'decoded_data': <js term for rlist>,
|
||||||
|
% 'encoded_bytes': new Uint8Array([B1, B2, B3, ...])}
|
||||||
|
format_case_js(DecodedData_rlist) ->
|
||||||
|
% EncodedData_bytes = rlp:encode(
|
||||||
|
% DD_js = format_data_js(DecodedData_rlist),
|
||||||
|
EncodedData_bytes = rlp:encode(DecodedData_rlist),
|
||||||
|
EncodedBytes_py = format_bytes_js(EncodedData_bytes),
|
||||||
|
DecodedData_py = format_data_js(DecodedData_rlist),
|
||||||
|
[" {'decoded': ", DecodedData_py, ",\n",
|
||||||
|
" 'encoded': ", EncodedBytes_py, "}"].
|
||||||
|
|
||||||
|
|
||||||
|
format_data_js(List) when is_list(List) ->
|
||||||
|
format_list_js(List);
|
||||||
|
format_data_js(Bytes) when is_binary(Bytes) ->
|
||||||
|
format_bytes_js(Bytes).
|
||||||
|
|
||||||
|
|
||||||
|
format_list_js(List) ->
|
||||||
|
[$[, js_lcommas(List, []), $]].
|
||||||
|
|
||||||
|
|
||||||
|
% similar to commas/2 below but for a list
|
||||||
|
% cases
|
||||||
|
% - list is empty -> special case
|
||||||
|
% - exactly one element -> special case
|
||||||
|
% - two or more elements -> peel off one at a time until terminal case of exactly one
|
||||||
|
% initial input empty, so return empty
|
||||||
|
js_lcommas([], []) ->
|
||||||
|
[];
|
||||||
|
% one item left, do not add comma
|
||||||
|
js_lcommas([Item], Acc) ->
|
||||||
|
[Acc, format_data_js(Item)];
|
||||||
|
% two or more items left, add comma
|
||||||
|
js_lcommas([Item | Rest], Acc) ->
|
||||||
|
js_lcommas(Rest, [Acc, format_data_js(Item), ", "]).
|
||||||
|
|
||||||
|
% format a bytestring as "bytes([Byte1, Byte2, ...])"
|
||||||
|
format_bytes_js(Bytes) ->
|
||||||
|
% commas are the same as in python so reusing that
|
||||||
|
Commas = commas(Bytes, []),
|
||||||
|
["new Uint8Array([", Commas, "])"].
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%% PYTHON FORMATTING %%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
format_cases_py(Cases) ->
|
format_cases_py(Cases) ->
|
||||||
format_stringlist_py(lists:map(fun format_case_py/1, Cases)).
|
format_stringlist_py(lists:map(fun format_case_py/1, Cases)).
|
||||||
|
|
||||||
@@ -99,6 +167,11 @@ commas(<<B, Rest/binary>>, Acc) ->
|
|||||||
commas(Rest, [Acc, integer_to_list(B), ", "]).
|
commas(Rest, [Acc, integer_to_list(B), ", "]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%% INVERSE PROPERTY TEST %%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
% test that the inverse property is correct
|
% test that the inverse property is correct
|
||||||
|
|
||||||
test_inverse() ->
|
test_inverse() ->
|
||||||
@@ -121,6 +194,12 @@ check_inverse(DecodedData) ->
|
|||||||
{DeEncodedData, <<>>} = rlp:decode(rlp:encode(DecodedData)),
|
{DeEncodedData, <<>>} = rlp:decode(rlp:encode(DecodedData)),
|
||||||
DeEncodedData =:= DecodedData.
|
DeEncodedData =:= DecodedData.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
%%% RANDOM CASE GENERATION %%%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
%mkcase(DecodedData) ->
|
%mkcase(DecodedData) ->
|
||||||
% Encoded = rlp:encode(DecodedData),
|
% Encoded = rlp:encode(DecodedData),
|
||||||
% {Deencoded, <<>>} = rlp:decode(
|
% {Deencoded, <<>>} = rlp:decode(
|
||||||
|
|||||||
Reference in New Issue
Block a user