Start moving editor over to using transactions.
Also switch from LedHash to TentHash.
This commit is contained in:
parent
03cfb01942
commit
fa55afeebe
109
Cargo.lock
generated
109
Cargo.lock
generated
|
@ -26,18 +26,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayref"
|
|
||||||
version = "0.3.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -59,10 +47,10 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
name = "backend"
|
name = "backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake3",
|
|
||||||
"criterion",
|
"criterion",
|
||||||
"proptest",
|
"proptest",
|
||||||
"ropey",
|
"ropey",
|
||||||
|
"tenthash",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -93,29 +81,6 @@ version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "blake3"
|
|
||||||
version = "1.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f"
|
|
||||||
dependencies = [
|
|
||||||
"arrayref",
|
|
||||||
"arrayvec",
|
|
||||||
"cc",
|
|
||||||
"cfg-if",
|
|
||||||
"constant_time_eq",
|
|
||||||
"digest",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "block-buffer"
|
|
||||||
version = "0.10.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -146,12 +111,6 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cc"
|
|
||||||
version = "1.0.73"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -179,12 +138,6 @@ version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
|
checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "constant_time_eq"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "criterion"
|
name = "criterion"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
@ -291,16 +244,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crypto-common"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array",
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "csv"
|
name = "csv"
|
||||||
version = "1.1.6"
|
version = "1.1.6"
|
||||||
|
@ -323,17 +266,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "digest"
|
|
||||||
version = "0.10.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"crypto-common",
|
|
||||||
"subtle",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "discard"
|
name = "discard"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -373,16 +305,6 @@ dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.14.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -778,11 +700,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ropey"
|
name = "ropey"
|
||||||
version = "1.3.1"
|
version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
|
checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"str_indices",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -980,18 +903,18 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "str_indices"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8eeaedde8e50d8a331578c9fa9a288df146ce5e16173ad26ce82f6e263e2be4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "subtle"
|
|
||||||
version = "2.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.73"
|
version = "1.0.73"
|
||||||
|
@ -1017,6 +940,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tenthash"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8df174134a1907e769ee0a328dc62126989bea7385d288253f61c7ec92ee1df2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textwrap"
|
name = "textwrap"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -1094,12 +1023,6 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.7.1"
|
version = "1.7.1"
|
||||||
|
|
|
@ -3,14 +3,10 @@
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{max, min},
|
cmp::{max, min},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::File,
|
io,
|
||||||
io::{self, BufWriter, Write},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use backend::{
|
use backend::{buffer::Buffer, marks::Mark};
|
||||||
buffer::{Buffer, BufferPath},
|
|
||||||
marks::Mark,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
formatter::LineFormatter,
|
formatter::LineFormatter,
|
||||||
|
@ -70,19 +66,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_if_dirty(&mut self) -> io::Result<()> {
|
pub fn save_if_dirty(&mut self) -> io::Result<()> {
|
||||||
if let BufferPath::File(ref file_path) = self.buffer.path {
|
if self.buffer.is_dirty() {
|
||||||
if self.buffer.is_dirty {
|
self.buffer.save()
|
||||||
let mut f = BufWriter::new(File::create(file_path)?);
|
} else {
|
||||||
|
Ok(())
|
||||||
for c in self.buffer.text.chunks() {
|
|
||||||
f.write(c.as_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buffer.is_dirty = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto_detect_line_ending(&mut self) {
|
pub fn auto_detect_line_ending(&mut self) {
|
||||||
|
@ -254,27 +242,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
pub fn undo(&mut self) {
|
||||||
// TODO: handle multiple cursors properly
|
self.buffer.undo();
|
||||||
if let Some((_start, end)) = self.buffer.undo() {
|
self.move_view_to_cursor();
|
||||||
self.buffer.mark_sets[self.c_msi].reduce_to_main();
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].head = end;
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].tail = end;
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].hh_pos = None;
|
|
||||||
|
|
||||||
self.move_view_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
pub fn redo(&mut self) {
|
||||||
// TODO: handle multiple cursors properly
|
self.buffer.redo();
|
||||||
if let Some((_start, end)) = self.buffer.redo() {
|
self.move_view_to_cursor();
|
||||||
self.buffer.mark_sets[self.c_msi].reduce_to_main();
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].head = end;
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].tail = end;
|
|
||||||
self.buffer.mark_sets[self.c_msi][0].hh_pos = None;
|
|
||||||
|
|
||||||
self.move_view_to_cursor();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the editor's view the minimum amount to show the cursor
|
/// Moves the editor's view the minimum amount to show the cursor
|
||||||
|
|
|
@ -464,7 +464,7 @@ impl TermUI {
|
||||||
BufferPath::File(ref p) => format!("{}", p.display()),
|
BufferPath::File(ref p) => format!("{}", p.display()),
|
||||||
BufferPath::Temp(i) => format!("Scratch #{}", i + 1),
|
BufferPath::Temp(i) => format!("Scratch #{}", i + 1),
|
||||||
};
|
};
|
||||||
let dirty_char = if editor.buffer.is_dirty { "*" } else { "" };
|
let dirty_char = if editor.buffer.is_dirty() { "*" } else { "" };
|
||||||
let name = format!("{}{}", filename, dirty_char);
|
let name = format!("{}{}", filename, dirty_char);
|
||||||
self.screen.draw(c1.1 + 1, c1.0, &name[..], STYLE_INFO);
|
self.screen.draw(c1.1 + 1, c1.0, &name[..], STYLE_INFO);
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,9 @@ path = "src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ropey = "1"
|
ropey = "1"
|
||||||
# ropey = { git = "https://github.com/cessen/ropey", branch = "master" }
|
tenthash = "0.2"
|
||||||
unicode-segmentation = "1.7"
|
unicode-segmentation = "1.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
blake3 = "1.3.1"
|
|
||||||
proptest = "1.0"
|
proptest = "1.0"
|
||||||
criterion = "0.3.6"
|
criterion = "0.3.6"
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "hash"
|
|
||||||
harness = false
|
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
use backend::hash::LedHash256;
|
|
||||||
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
|
|
||||||
|
|
||||||
//----
|
|
||||||
|
|
||||||
fn hash_10b(c: &mut Criterion) {
|
|
||||||
let size = 10;
|
|
||||||
|
|
||||||
let mut group = c.benchmark_group("hash_10b");
|
|
||||||
let data: Vec<u8> = b"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.cycle()
|
|
||||||
.take(size)
|
|
||||||
.collect();
|
|
||||||
group.throughput(Throughput::Bytes(size as u64));
|
|
||||||
|
|
||||||
group.bench_function("led256", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = LedHash256::new();
|
|
||||||
hash.update(&data);
|
|
||||||
hash.finish();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("blake3", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = blake3::Hasher::new();
|
|
||||||
hash.update(&data);
|
|
||||||
hash.finalize();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_1kb(c: &mut Criterion) {
|
|
||||||
let size = 1000;
|
|
||||||
|
|
||||||
let mut group = c.benchmark_group("hash_1kb");
|
|
||||||
let data: Vec<u8> = b"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.cycle()
|
|
||||||
.take(size)
|
|
||||||
.collect();
|
|
||||||
group.throughput(Throughput::Bytes(size as u64));
|
|
||||||
|
|
||||||
group.bench_function("led256", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = LedHash256::new();
|
|
||||||
hash.update(&data);
|
|
||||||
hash.finish();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("blake3", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = blake3::Hasher::new();
|
|
||||||
hash.update(&data);
|
|
||||||
hash.finalize();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_100kb(c: &mut Criterion) {
|
|
||||||
let size = 100_000;
|
|
||||||
|
|
||||||
let mut group = c.benchmark_group("hash_100kb");
|
|
||||||
let data: Vec<u8> = b"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.cycle()
|
|
||||||
.take(size)
|
|
||||||
.collect();
|
|
||||||
group.throughput(Throughput::Bytes(size as u64));
|
|
||||||
|
|
||||||
group.bench_function("led256_1k_chunk", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = LedHash256::new();
|
|
||||||
for chunk in data.chunks(1000) {
|
|
||||||
hash.update(chunk);
|
|
||||||
}
|
|
||||||
hash.finish();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("led256_10k_chunk", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = LedHash256::new();
|
|
||||||
for chunk in data.chunks(10000) {
|
|
||||||
hash.update(chunk);
|
|
||||||
}
|
|
||||||
hash.finish();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("blake3_1k_chunk", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = blake3::Hasher::new();
|
|
||||||
for chunk in data.chunks(1000) {
|
|
||||||
hash.update(chunk);
|
|
||||||
}
|
|
||||||
hash.finalize();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
group.bench_function("blake3_10k_chunk", |bench| {
|
|
||||||
bench.iter(|| {
|
|
||||||
let mut hash = blake3::Hasher::new();
|
|
||||||
for chunk in data.chunks(10000) {
|
|
||||||
hash.update(chunk);
|
|
||||||
}
|
|
||||||
hash.finalize();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//----
|
|
||||||
|
|
||||||
criterion_group!(benches, hash_10b, hash_1kb, hash_100kb);
|
|
||||||
criterion_main!(benches);
|
|
|
@ -1,4 +1,9 @@
|
||||||
use std::{borrow::Cow, path::PathBuf};
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
fs::File,
|
||||||
|
io::{self, BufWriter, Write},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use ropey::Rope;
|
use ropey::Rope;
|
||||||
|
|
||||||
|
@ -40,9 +45,22 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves the buffer to disk.
|
/// Saves the buffer to disk.
|
||||||
pub fn save(&mut self) {
|
pub fn save(&mut self) -> io::Result<()> {
|
||||||
self.edits_since_saved = 0;
|
if let BufferPath::File(ref file_path) = self.path {
|
||||||
todo!();
|
let mut f = BufWriter::new(File::create(file_path)?);
|
||||||
|
|
||||||
|
for c in self.text.chunks() {
|
||||||
|
f.write(c.as_bytes())?;
|
||||||
|
}
|
||||||
|
self.edits_since_saved = 0;
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::NotFound,
|
||||||
|
"Cannot save: buffer has no on-disk file associated with it.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the given range of chars with the given text.
|
/// Replaces the given range of chars with the given text.
|
||||||
|
|
|
@ -1,266 +0,0 @@
|
||||||
//! A 256-bit non-cryptographic hash function.
|
|
||||||
//!
|
|
||||||
//! This is intended to be used as a fast, high-quality checksum for
|
|
||||||
//! non-adversarial data identification. It is not intended to stand
|
|
||||||
//! up to attacks of any kind. (It does use the MIX function and
|
|
||||||
//! constants from Skein v1.3, but is not otherwise related.)
|
|
||||||
//!
|
|
||||||
//! This hash does *not* reliably have a full 256 bits worth of power to
|
|
||||||
//! distinguish different data. Rather, that number is somewhere
|
|
||||||
//! between 128 and 192 bits (much closer to the latter). The 256 bits
|
|
||||||
//! of output *are*, however, fully diffused. So you can truncate them
|
|
||||||
//! to whatever size you like without harm.
|
|
||||||
//!
|
|
||||||
//! This implementation should work on platforms of any endianness,
|
|
||||||
//! but has only been tested on little endian platforms. Running the
|
|
||||||
//! unit tests on a big-endian platform can verify.
|
|
||||||
|
|
||||||
const BLOCK_SIZE: usize = 256 / 8; // Block size of the hash, in bytes.
|
|
||||||
const UPDATE_MIX_ROUNDS: usize = 6; // Number of mix rounds after each block of data is added.
|
|
||||||
const FINISH_MIX_ROUNDS: usize = 12; // Number of mix rounds used to finalize the hash.
|
|
||||||
|
|
||||||
/// A hasher. Consumes bytes and generates a 256-bit hash.
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
#[repr(C)]
|
|
||||||
#[repr(align(32))]
|
|
||||||
pub struct LedHash256 {
|
|
||||||
state: [u64; 4], // Hash state.
|
|
||||||
buf: [u8; BLOCK_SIZE], // Accumulates message data for processing.
|
|
||||||
buf_length: usize, // The number of message bytes currently stored in buf[].
|
|
||||||
message_length: u64, // Accumulates the total message length, in bytes.
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LedHash256 {
|
|
||||||
pub fn new() -> LedHash256 {
|
|
||||||
LedHash256 {
|
|
||||||
state: [
|
|
||||||
0xe2b8d3b67882709f,
|
|
||||||
0x045e21ec46bcea22,
|
|
||||||
0x51ea37fa96fbae67,
|
|
||||||
0xf5d94991b6b9b944,
|
|
||||||
],
|
|
||||||
buf: [0; BLOCK_SIZE],
|
|
||||||
buf_length: 0,
|
|
||||||
message_length: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the hash with new data.
|
|
||||||
pub fn update(&mut self, data: &[u8]) {
|
|
||||||
self.message_length += data.len() as u64;
|
|
||||||
|
|
||||||
let mut data = data;
|
|
||||||
while !data.is_empty() {
|
|
||||||
if self.buf_length == BLOCK_SIZE {
|
|
||||||
// Process the filled buffer.
|
|
||||||
add_data_to_state(&mut self.state, &self.buf);
|
|
||||||
mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
|
|
||||||
self.buf_length = 0;
|
|
||||||
} else if self.buf_length == 0 && data.len() >= BLOCK_SIZE {
|
|
||||||
// Process data directly, skipping the buffer.
|
|
||||||
add_data_to_state(&mut self.state, data);
|
|
||||||
mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
|
|
||||||
data = &data[BLOCK_SIZE..];
|
|
||||||
} else {
|
|
||||||
// Fill the buffer.
|
|
||||||
let n = (BLOCK_SIZE - self.buf_length).min(data.len());
|
|
||||||
(&mut self.buf[self.buf_length..(self.buf_length + n)]).copy_from_slice(&data[..n]);
|
|
||||||
data = &data[n..];
|
|
||||||
self.buf_length += n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finishes the hash calculations and returns the digest.
|
|
||||||
pub fn finish(mut self) -> [u8; BLOCK_SIZE] {
|
|
||||||
// Hash the remaining bytes if there are any.
|
|
||||||
if self.buf_length > 0 {
|
|
||||||
(&mut self.buf[self.buf_length..]).fill(0);
|
|
||||||
add_data_to_state(&mut self.state, &self.buf);
|
|
||||||
mix_state(&mut self.state, UPDATE_MIX_ROUNDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Incorporate the message length (in bits) and do the
|
|
||||||
// final mixing.
|
|
||||||
self.state[0] ^= self.message_length * 8;
|
|
||||||
mix_state(&mut self.state, FINISH_MIX_ROUNDS);
|
|
||||||
|
|
||||||
// Get the digest as a byte array and return it.
|
|
||||||
let mut digest = [0u8; BLOCK_SIZE];
|
|
||||||
digest[0..8].copy_from_slice(&self.state[0].to_le_bytes());
|
|
||||||
digest[8..16].copy_from_slice(&self.state[1].to_le_bytes());
|
|
||||||
digest[16..24].copy_from_slice(&self.state[2].to_le_bytes());
|
|
||||||
digest[24..32].copy_from_slice(&self.state[3].to_le_bytes());
|
|
||||||
return digest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the printable hex string version of a digest.
|
|
||||||
pub fn digest_to_string(digest: &[u8]) -> String {
|
|
||||||
fn low_bits_to_char(n: u8) -> char {
|
|
||||||
match n {
|
|
||||||
0 => '0',
|
|
||||||
1 => '1',
|
|
||||||
2 => '2',
|
|
||||||
3 => '3',
|
|
||||||
4 => '4',
|
|
||||||
5 => '5',
|
|
||||||
6 => '6',
|
|
||||||
7 => '7',
|
|
||||||
8 => '8',
|
|
||||||
9 => '9',
|
|
||||||
10 => 'a',
|
|
||||||
11 => 'b',
|
|
||||||
12 => 'c',
|
|
||||||
13 => 'd',
|
|
||||||
14 => 'e',
|
|
||||||
15 => 'f',
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut s = String::new();
|
|
||||||
for byte in digest.iter() {
|
|
||||||
s.push(low_bits_to_char(byte >> 4u8));
|
|
||||||
s.push(low_bits_to_char(byte & 0b00001111));
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds message data to the hash state.
|
|
||||||
///
|
|
||||||
/// The data must be at least 32 bytes long. Only the first 32 bytes
|
|
||||||
/// are added.
|
|
||||||
#[inline(always)]
|
|
||||||
fn add_data_to_state(state: &mut [u64; 4], data: &[u8]) {
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
// Convert the data to native endian u64's and xor into the
|
|
||||||
// hash state.
|
|
||||||
assert!(data.len() >= BLOCK_SIZE);
|
|
||||||
state[0] ^= u64::from_le_bytes((&data[0..8]).try_into().unwrap());
|
|
||||||
state[1] ^= u64::from_le_bytes((&data[8..16]).try_into().unwrap());
|
|
||||||
state[2] ^= u64::from_le_bytes((&data[16..24]).try_into().unwrap());
|
|
||||||
state[3] ^= u64::from_le_bytes((&data[24..32]).try_into().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mixes the passed hash state.
|
|
||||||
///
|
|
||||||
/// Inspired by Skein 1.3's MIX function and permutation approach.
|
|
||||||
///
|
|
||||||
/// 6 rounds is enough for each bit to have a reasonable chance of
|
|
||||||
/// affecting most other bits. 9 rounds is enough for full diffusion.
|
|
||||||
#[inline(always)]
|
|
||||||
fn mix_state(state: &mut [u64; 4], rounds: usize) {
|
|
||||||
const ROTATIONS: &[[u32; 2]] = &[[31, 25], [5, 48], [20, 34], [21, 57], [11, 41], [18, 33]];
|
|
||||||
|
|
||||||
debug_assert!(rounds % 2 == 0);
|
|
||||||
for round in 0..rounds {
|
|
||||||
let rot = ROTATIONS[round % ROTATIONS.len()];
|
|
||||||
|
|
||||||
// MIX function.
|
|
||||||
state[0] = state[0].wrapping_add(state[2]);
|
|
||||||
state[2] = state[2].rotate_left(rot[0]) ^ state[0];
|
|
||||||
state[1] = state[1].wrapping_add(state[3]);
|
|
||||||
state[3] = state[3].rotate_left(rot[1]) ^ state[1];
|
|
||||||
|
|
||||||
state.swap(2, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn hash(data: &[u8]) -> [u8; BLOCK_SIZE] {
|
|
||||||
let mut h = LedHash256::new();
|
|
||||||
h.update(data);
|
|
||||||
h.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_empty() {
|
|
||||||
let correct_digest = "e0d4e0a2608a8741e349fa1ea0263fedbd65f66dfbcbcecd77a334c809424cb6";
|
|
||||||
assert_eq!(digest_to_string(&hash(&[])), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_zero() {
|
|
||||||
let correct_digest = "6e5f483d20443bb6e70c300b0a5aa64ce36d346793ea62d53a198d8ae48f60f3";
|
|
||||||
assert_eq!(digest_to_string(&hash(&[0u8])), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_string_01() {
|
|
||||||
let s = "0123456789";
|
|
||||||
let correct_digest = "f12f795967313e9a0e822edaa307c3d7b7d19ce38773955e4ba25aa0bce17f56";
|
|
||||||
assert_eq!(digest_to_string(&hash(s.as_bytes())), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_string_02() {
|
|
||||||
let s = "abcdefghijklmnopqrstuvwxyz";
|
|
||||||
let correct_digest = "8f578c05439217eeac0dc46d7df2805f91ffad995468eca8cf9f8eb954f7da18";
|
|
||||||
assert_eq!(digest_to_string(&hash(s.as_bytes())), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_string_03() {
|
|
||||||
let s = "The quick brown fox jumps over the lazy dog.";
|
|
||||||
let correct_digest = "0be19c6dc03f6800743e41c70f0ee0c2d75bad674f8496fea75a18af280e6100";
|
|
||||||
assert_eq!(digest_to_string(&hash(s.as_bytes())), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_string_04() {
|
|
||||||
let s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
|
||||||
let correct_digest = "6faf47daac8a767a1d7ed6da36cbe50616a1b83ae9b2802564c948a4283f7833";
|
|
||||||
assert_eq!(digest_to_string(&hash(s.as_bytes())), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_multi_part_processing() {
|
|
||||||
let test_string1 =
|
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor";
|
|
||||||
let test_string2 = " incididunt ut l";
|
|
||||||
let test_string3 = "abore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat ";
|
|
||||||
let test_string4 = "cup";
|
|
||||||
let test_string5 =
|
|
||||||
"idatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
|
||||||
let correct_digest = "6faf47daac8a767a1d7ed6da36cbe50616a1b83ae9b2802564c948a4283f7833";
|
|
||||||
|
|
||||||
let mut hasher = LedHash256::new();
|
|
||||||
hasher.update(test_string1.as_bytes());
|
|
||||||
hasher.update(test_string2.as_bytes());
|
|
||||||
hasher.update(test_string3.as_bytes());
|
|
||||||
hasher.update(test_string4.as_bytes());
|
|
||||||
hasher.update(test_string5.as_bytes());
|
|
||||||
let digest = hasher.finish();
|
|
||||||
|
|
||||||
assert_eq!(digest_to_string(&digest), correct_digest);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hash_length() {
|
|
||||||
// We're testing here to make sure the length of the data properly
|
|
||||||
// affects the hash. Internally in the hash, the last block of data
|
|
||||||
// is padded with zeros, so here we're forcing that last block to be
|
|
||||||
// all zeros, and only changing the length of input.
|
|
||||||
let len_0 = &[];
|
|
||||||
let len_1 = &[0u8];
|
|
||||||
let len_2 = &[0u8, 0];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
digest_to_string(&hash(len_0)),
|
|
||||||
"e0d4e0a2608a8741e349fa1ea0263fedbd65f66dfbcbcecd77a334c809424cb6",
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
digest_to_string(&hash(len_1)),
|
|
||||||
"6e5f483d20443bb6e70c300b0a5aa64ce36d346793ea62d53a198d8ae48f60f3",
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
digest_to_string(&hash(len_2)),
|
|
||||||
"ffaf1c6954edb55a7ac10c16b6f309c8e1cc7b5c29e4fd4992eb0f416b965ee0",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ extern crate unicode_segmentation;
|
||||||
|
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod hash;
|
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub mod marks;
|
pub mod marks;
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user