From bd99116f85f1ef86d541ad8357af82498d388c6c Mon Sep 17 00:00:00 2001 From: Fantix King Date: Sat, 27 Apr 2024 12:26:27 -0400 Subject: [PATCH 1/3] Add BytesView This is a 1:1 clone of VecView --- bytes/bytes.mbti | 6 ++++ bytes/view.mbt | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 bytes/view.mbt diff --git a/bytes/bytes.mbti b/bytes/bytes.mbti index dcf4b259c..2c49832a6 100644 --- a/bytes/bytes.mbti +++ b/bytes/bytes.mbti @@ -3,8 +3,14 @@ package moonbitlang/core/bytes // Values // Types and methods +type BytesView +fn BytesView::length(BytesView) -> Int +fn BytesView::op_as_view(BytesView, Int, Int) -> BytesView +fn BytesView::op_get(BytesView, Int) -> Int +fn BytesView::op_set(BytesView, Int, Int) -> Unit fn Bytes::from_array(Array[Int]) -> Bytes fn Bytes::hash(Bytes) -> Int +fn Bytes::op_as_view(Bytes, Int, Int) -> BytesView // Traits diff --git a/bytes/view.mbt b/bytes/view.mbt new file mode 100644 index 000000000..f38b52ce7 --- /dev/null +++ b/bytes/view.mbt @@ -0,0 +1,73 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +struct BytesView { + buf : Bytes + start : Int + len : Int +} + +pub fn length(self : BytesView) -> Int { + self.len +} + +pub fn op_get(self : BytesView, index : Int) -> Int { + if index < 0 || index >= self.len { + let len = self.len + abort( + "index out of bounds: the len is from 0 to \(len) but the index is \(index)", + ) + } + self.buf[self.start + index] +} + +pub fn op_set(self : BytesView, index : Int, value : Int) -> Unit { + if index < 0 || index >= self.len { + let len = self.len + abort( + "index out of bounds: the len is from 0 to \(len) but the index is \(index)", + ) + } + self.buf[self.start + index] = value +} + +pub fn op_as_view(self : Bytes, ~start : Int, ~end : Int) -> BytesView { + if start < 0 { + abort("Slice start index out of bounds") + } else if end > self.length() { + abort("Slice end index out of bounds") + } else if start > end { + abort("Slice start index greater than end index") + } + { buf: self, start, len: end - start } +} + +pub fn op_as_view(self : BytesView, ~start : Int, ~end : Int) -> BytesView { + if start < 0 { + abort("Slice start index out of bounds") + } else if end > self.len { + abort("Slice end index out of bounds") + } else if start > end { + abort("Slice start index greater than end index") + } + { buf: self.buf, start: self.start + start, len: end - start } +} + +test "slice" { + let v = Bytes::[1, 2, 3, 4, 5] + let s = v.op_as_view(start=1, end=4) + @assertion.assert_eq(s.length(), 3)? + @assertion.assert_eq(s[0], 2)? + @assertion.assert_eq(s[1], 3)? +} From 2b22fcafa685fc4c5c943e67d7325166a1dcdd70 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Sat, 27 Apr 2024 13:17:45 -0400 Subject: [PATCH 2/3] Add `@mem` package --- mem/load.mbt | 55 +++++++++++++++++++++++++++ mem/mem.mbti | 30 +++++++++++++++ mem/mem_test.mbt | 94 +++++++++++++++++++++++++++++++++++++++++++++++ mem/moon.pkg.json | 11 ++++++ mem/ops.mbt | 19 ++++++++++ mem/store.mbt | 54 +++++++++++++++++++++++++++ mem/types.mbt | 18 +++++++++ 7 files changed, 281 insertions(+) create mode 100644 mem/load.mbt create mode 100644 mem/mem.mbti create mode 100644 mem/mem_test.mbt create mode 100644 mem/moon.pkg.json create mode 100644 mem/ops.mbt create mode 100644 mem/store.mbt create mode 100644 mem/types.mbt diff --git a/mem/load.mbt b/mem/load.mbt new file mode 100644 index 000000000..3fe87867c --- /dev/null +++ b/mem/load.mbt @@ -0,0 +1,55 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub trait Loadable { + op_get(Self, Int) -> Int // XXX: -> Byte? + length(Self) -> Int + load_int(Self, Int, Endian) -> Int + load_int64(Self, Int, Endian) -> Int64 + // XXX: needs the capability to use labelled params in trait + // https://github.com/moonbitlang/moonbit-docs/issues/190 + // op_as_view(Self, ~start: Int, ~end: Int) -> Loadable +} + +impl Loadable::load_int(self : Self, index : Int, endian : Endian) -> Int { + let bytes = 4 + let idx = match endian { + Little => fn(i) { index + i } + Big => { + let start = index + bytes - 1 + fn(i) { start - i } + } + } + for i = 0, rv = 0; i < bytes; { + continue i + 1, rv.lor(self[idx(i)].lsl(8 * i)) + } else { + rv + } +} + +impl Loadable::load_int64(self : Self, index : Int, endian : Endian) -> Int64 { + let bytes = 8 + let idx = match endian { + Little => fn(i) { index + i } + Big => { + let start = index + bytes - 1 + fn(i) { start - i } + } + } + for i = 0, rv = 0L; i < bytes; { + continue i + 1, rv.lor(self[idx(i)].to_int64().lsl(8 * i)) + } else { + rv + } +} diff --git a/mem/mem.mbti b/mem/mem.mbti new file mode 100644 index 000000000..25893d670 --- /dev/null +++ b/mem/mem.mbti @@ -0,0 +1,30 @@ +package moonbitlang/core/mem + +// Values +fn copy(Storable, Loadable, Int) -> Unit + +// Types and methods +pub enum Endian { + Little + Big +} + +// Traits +pub trait Loadable { + op_get(Self, Int) -> Int + length(Self) -> Int + load_int(Self, Int, Endian) -> Int + load_int64(Self, Int, Endian) -> Int64 +} + +pub trait Storable { + op_set(Self, Int, Int) -> Unit + store_int(Self, Int, Int, Endian) -> Unit + store_int64(Self, Int, Int64, Endian) -> Unit +} + +// Extension Methods +impl Loadable for Loadable + +impl Storable for Storable + diff --git a/mem/mem_test.mbt b/mem/mem_test.mbt new file mode 100644 index 000000000..12396ded2 --- /dev/null +++ b/mem/mem_test.mbt @@ -0,0 +1,94 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +fn test_load(m : Loadable) -> Result[Unit, String] { + inspect(m.load_int(1, Big), content="33752069")? + inspect(m.load_int64(1, Big), content="144964032628459529")? + inspect(m.load_int(1, Little), content="84148994")? + inspect(m.load_int64(1, Little), content="650777868590383874")? + Ok(()) +} + +test "load bytes" { + test_load(Bytes::[1, 2, 3, 4, 5, 6, 7, 8, 9])? +} + +test "load bytes view" { + test_load( + { + Bytes::[0, 1, 2, 3, 4, 5, 6, 7, 8, 9][1..] + }, + )? +} + +test "load int vec" { + test_load(@vec.Vec::[1, 2, 3, 4, 5, 6, 7, 8, 9])? +} + +test "load int vec view" { + test_load( + { + @vec.Vec::[0, 1, 2, 3, 4, 5, 6, 7, 8, 9][1..] + }, + )? +} + +test "bytes" { + let v = Bytes::make(9, 0x04) + (v as Storable).store_int(2, 0x08080808, Big) + inspect(v, content="ЄࠈࠈЄ")? + (v as Storable).store_int64(1, 0x0607080901020304L, Big) + inspect(v, content="\u{604}ࠇĉ\u{302}")? +} + +test "bytes view" { + let v = Bytes::make(9, 0x04) + ((v[1..]) as Storable).store_int(2, 0x08080808, Big) + inspect(v, content="ЄࠄࠈЈ")? + ((v[1..]) as Storable).store_int64(0, 0x0607080901020304L, Big) + inspect(v, content="\u{604}ࠇĉ\u{302}")? +} + +test "int vec" { + let v = @vec.with_capacity(9) + v.fill(0) + (v as Storable).store_int(2, 1239873143, Big) + inspect(v, content="Vec::[0, 0, 73, 230, 246, 119, 0, 0, 0]")? + (v as Storable).store_int64(1, 1239873143123719283L, Big) + inspect(v, content="Vec::[0, 17, 52, 234, 13, 246, 111, 180, 115]")? +} + +test "int vec view" { + let v = @vec.with_capacity(9) + v.fill(0) + ((v[1..]) as Storable).store_int(3, 1239873143, Big) + inspect(v, content="Vec::[0, 0, 0, 0, 73, 230, 246, 119, 0]")? + ((v[1..]) as Storable).store_int64(0, 1239873143123719283L, Big) + inspect(v, content="Vec::[0, 17, 52, 234, 13, 246, 111, 180, 115]")? +} + +test "copy" { + let src = @vec.Vec::[1, 2, 3, 4, 5] + let dst = @vec.Vec::[0, 0, 0, 0, 0] + copy( + { + dst[1..] + }, + { + src[2..] + }, + 3, + ) + inspect(dst, content="Vec::[0, 3, 4, 5, 0]")? +} diff --git a/mem/moon.pkg.json b/mem/moon.pkg.json new file mode 100644 index 000000000..38fd99d43 --- /dev/null +++ b/mem/moon.pkg.json @@ -0,0 +1,11 @@ +{ + "import": [ + "moonbitlang/core/builtin", + "moonbitlang/core/coverage" + ], + "test_import": [ + "moonbitlang/core/bytes", + "moonbitlang/core/iter", + "moonbitlang/core/vec" + ] +} diff --git a/mem/ops.mbt b/mem/ops.mbt new file mode 100644 index 000000000..9686534ae --- /dev/null +++ b/mem/ops.mbt @@ -0,0 +1,19 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub fn copy(dst : Storable, src : Loadable, len : Int) -> Unit { + for i = 0; i < len; i = i + 1 { + dst[i] = src[i] + } +} diff --git a/mem/store.mbt b/mem/store.mbt new file mode 100644 index 000000000..34e013489 --- /dev/null +++ b/mem/store.mbt @@ -0,0 +1,54 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub trait Storable { + op_set(Self, Int, Int) -> Unit // XXX: take Byte? + store_int(Self, Int, Int, Endian) -> Unit + store_int64(Self, Int, Int64, Endian) -> Unit +} + +impl Storable::store_int(self : Self, index : Int, value : Int, endian : Endian) -> Unit { + let bytes = 4 + let idx = match endian { + Little => fn(i) { index + i } + Big => { + let start = index + bytes - 1 + fn(i) { start - i } + } + } + for i = 0, v = value; i < bytes; { + self[idx(i)] = v.land(0xff) + continue i + 1, v.lsr(8) + } +} + +impl Storable::store_int64( + self : Self, + index : Int, + value : Int64, + endian : Endian +) -> Unit { + let bytes = 8 + let idx = match endian { + Little => fn(i) { index + i } + Big => { + let start = index + bytes - 1 + fn(i) { start - i } + } + } + for i = 0, v = value; i < bytes; { + self[idx(i)] = v.land(0xffL).to_int() + continue i + 1, v.lsr(8) + } +} diff --git a/mem/types.mbt b/mem/types.mbt new file mode 100644 index 000000000..dc5c57159 --- /dev/null +++ b/mem/types.mbt @@ -0,0 +1,18 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub enum Endian { + Little + Big +} From 05f71513a921eded0ab2d04f562fbf680dfdb57d Mon Sep 17 00:00:00 2001 From: Fantix King Date: Sat, 27 Apr 2024 13:31:45 -0400 Subject: [PATCH 3/3] Add `@hash` package with SHA1 impl --- hash/hash.mbti | 14 +++ hash/moon.pkg.json | 9 ++ hash/sha1.mbt | 285 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 hash/hash.mbti create mode 100644 hash/moon.pkg.json create mode 100644 hash/sha1.mbt diff --git a/hash/hash.mbti b/hash/hash.mbti new file mode 100644 index 000000000..2fda86d49 --- /dev/null +++ b/hash/hash.mbti @@ -0,0 +1,14 @@ +package moonbitlang/core/hash + +// Values + +// Types and methods +type SHA1 +fn SHA1::digest(SHA1) -> Bytes +fn SHA1::make(Bytes) -> SHA1 +fn SHA1::update(SHA1, Bytes) -> Unit + +// Traits + +// Extension Methods + diff --git a/hash/moon.pkg.json b/hash/moon.pkg.json new file mode 100644 index 000000000..7743b343a --- /dev/null +++ b/hash/moon.pkg.json @@ -0,0 +1,9 @@ +{ + "import": [ + "moonbitlang/core/array", + "moonbitlang/core/builtin", + "moonbitlang/core/bytes", + "moonbitlang/core/coverage", + "moonbitlang/core/mem" + ] +} diff --git a/hash/sha1.mbt b/hash/sha1.mbt new file mode 100644 index 000000000..23afcb63e --- /dev/null +++ b/hash/sha1.mbt @@ -0,0 +1,285 @@ +// Copyright 2024 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file is manually transpiled from the SHA1 C-implementation of +// the HACL* project: +// https://github.com/hacl-star/hacl-star/blob/150843809c5fdbc7dac0af395f7a1bb9606f9f96/dist/gcc-compatible/Hacl_Hash_SHA1.c +// Copyright of the origin: +// * Copyright (c) 2016-2022 INRIA, CMU and Microsoft Corporation +// * Copyright (c) 2022-2023 HACL* Contributors + +let _h0 = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0] + +struct SHA1 { + block_state : Array[Int] + buf : Bytes + mut total_len : Int64 +} + +pub fn SHA1::make(~init : Bytes = Bytes::make(0, 0)) -> SHA1 { + let block_state = _h0.map(fn { x => x }) + let buf = Bytes::make(64, 0) + let total_len = 0L + let rv = SHA1::{ block_state, buf, total_len } + if init.length() > 0 { + rv.update(init) + } + rv +} + +fn update_multi( + h : Array[Int], + blocks : @bytes.BytesView, + n_blocks : Int +) -> Unit { + for i = 0; i < n_blocks; i = i + 1 { + let l = (blocks[i * 64..]) as @mem.Loadable + let [ ha, hb, hc, hd, he, .. ] = h + let _w = Array::make(80, 0) + for i = 0; i < 80; i = i + 1 { + _w[i] = if i < 16 { + l.load_int(i * 4, @mem.Endian::Big) + } else { + let wmit3 = _w[i - 3] + let wmit8 = _w[i - 8] + let wmit14 = _w[i - 14] + let wmit16 = _w[i - 16] + wmit3.lxor(wmit8.lxor(wmit14.lxor(wmit16))).lsl(1).lor( + wmit3.lxor(wmit8.lxor(wmit14.lxor(wmit16))).lsr(31), + ) + } + } + for i = 0; i < 80; i = i + 1 { + let [ _a, _b, _c, _d, _e, .. ] = h + let wmit = _w[i] + let ite0 = if i < 20 { + _b.land(_c).lxor(_b.lnot().land(_d)) + } else if 39 < i && i < 60 { + _b.land(_c).lxor(_b.land(_d).lxor(_c.land(_d))) + } else { + _b.lxor(_c.lxor(_d)) + } + let ite = if i < 20 { + 0x5a827999 + } else if i < 40 { + 0x6ed9eba1 + } else if i < 60 { + 0x8f1bbcdc + } else { + 0xca62c1d6 + } + h[0] = _a.lsl(5).lor(_a.lsr(27)) + ite0 + _e + ite + wmit + h[1] = _a + h[2] = _b.lsl(30).lor(_b.lsr(2)) + h[3] = _c + h[4] = _d + } + for i = 0; i < 80; i = i + 1 { + _w[i] = 0 + } + h[0] += ha + h[1] += hb + h[2] += hc + h[3] += hd + h[4] += he + } +} + +fn update_last( + s : Array[Int], + prev_len : Int64, + input : @bytes.BytesView, + input_len : Int +) -> Unit { + let blocks_n = input_len / 64 + let blocks_len = blocks_n * 64 + let rest_len = input_len - blocks_len + update_multi(s, input, blocks_n) + let total_input_len = prev_len + input_len.to_int64() + let pad_len = 1 + (128 - (9 + (total_input_len % 64L).to_int())) % 64 + 8 + let tmp_len = rest_len + pad_len + let tmp_twoblocks = Bytes::make(128, 0) + @mem.copy( + tmp_twoblocks, + { + input[blocks_len..] + }, + rest_len, + ) + pad( + total_input_len, + { + tmp_twoblocks[rest_len..] + }, + ) + update_multi( + s, + { + tmp_twoblocks[..] + }, + tmp_len / 64, + ) +} + +fn pad(len : Int64, dst : @mem.Storable) -> Unit { + dst[0] = 0x80 + for i = 0; i < (128 - (9 + (len % 64L).to_int())) % 64; i = i + 1 { + dst[i + 1] = 0 + } + dst.store_int64( + 1 + (128 - (9 + (len % 64L).to_int())) % 64, + len.lsl(3), + @mem.Endian::Big, + ) +} + +fn finish(s : Array[Int]) -> Bytes { + for rv = Bytes::make(20, 0), i = 0; i < 5; i = i + 1 { + (rv as @mem.Storable).store_int(i * 4, s[i], @mem.Endian::Big) + } else { + rv + } +} + +pub fn update(self : SHA1, chunk : Bytes) -> Unit { + let total_len = self.total_len + let chunk_len = chunk.length() + if chunk_len.to_int64() > 2305843009213693951L - total_len { + abort("Maximum length exceeded") + } + let sz = { + let remainder = (total_len % 64L).to_int() + if remainder == 0 && total_len > 0L { + 64 + } else { + remainder + } + } + if chunk_len <= 64 - sz { + @mem.copy( + { + self.buf[sz..] + }, + chunk, + chunk_len, + ) + self.total_len += chunk_len.to_int64() + } else if sz == 0 { + let ite = { + let remainder = chunk_len % 64 + if remainder == 0 && chunk_len > 0 { + 64 + } else { + remainder + } + } + let n_blocks = (chunk_len - ite) / 64 + let data1_len = n_blocks * 64 + let data2_len = chunk_len - data1_len + update_multi( + self.block_state, + { + chunk[..] + }, + n_blocks, + ) + @mem.copy( + self.buf, + { + chunk[data1_len..] + }, + data2_len, + ) + self.total_len += chunk_len.to_int64() + } else { + let diff = 64 - sz + @mem.copy( + { + self.buf[sz..] + }, + chunk, + diff, + ) + let total_len = total_len + diff.to_int64() + self.total_len = total_len + update_multi( + self.block_state, + { + self.buf[..] + }, + 1, + ) + let chunk_len = chunk_len - diff + let ite = { + let remainder = chunk_len % 64 + if remainder == 0 && chunk_len > 0 { + 64 + } else { + remainder + } + } + let n_blocks = (chunk_len - ite) / 64 + let data1_len = n_blocks * 64 + let data2_len = chunk_len - data1_len + update_multi( + self.block_state, + { + chunk[diff..] + }, + n_blocks, + ) + @mem.copy( + self.buf, + { + chunk[diff + data1_len..] + }, + data2_len, + ) + self.total_len += chunk_len.to_int64() + } +} + +pub fn digest(self : SHA1) -> Bytes { + let total_len = self.total_len + let r = { + let remainder = (total_len % 64L).to_int() + if remainder == 0 && total_len > 0L { + 64 + } else { + remainder + } + } + let tmp_block_state = self.block_state.map(fn { x => x }) + update_last( + tmp_block_state, + total_len - r.to_int64(), + { + self.buf[..] + }, + r, + ) + finish(tmp_block_state) +} + +test "basic" { + let h = SHA1::make(init=Bytes::[1, 2, 3, 4, 5]) + h.update(Bytes::[6, 7, 8, 9, 10]) + inspect( + h.digest(), + content=Bytes::[ + 197, 57, 30, 48, 138, 242, 91, 66, 213, 147, 77, 106, 32, 26, 52, 232, 152, + 210, 85, 198, + ].to_string(), + )? +}