diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f00cdcd..ae6b223 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -72,3 +72,27 @@ jobs: - name: Run test script from repo run: make run-test + + go-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: 'true' + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.85.0 + - name: Verify interop fixture is up to date + run: | + (cd verity-core && python3 make_testfiles.py) + cargo run -p cryptpilot-verity -- format verity-core/testfiles --hash-output - --label env=prod --force + diff verity-core/testfiles/cryptpilot-verity.metadata.fb verity-go/metadata/testdata/rust.metadata.fb \ + || { echo "ERROR: interop fixture is stale. Run verity-go/metadata/gen_fixture.sh to regenerate."; exit 1; } + - name: Run Go tests + run: | + cd verity-go + go test -race -v ./... + - name: Check FlatBuffers schemas in sync + run: make check-fbs diff --git a/.gitignore b/.gitignore index f5a43d7..eb844bf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,9 @@ cryptpilot-verity/benchmark/verity_source_data/ # Superpowers design docs docs/superpowers/ + +# Claude session artifacts +.claude/ + +# Generated test files +testfiles/ diff --git a/CLAUDE.md b/CLAUDE.md index 9bd8659..eb4cd47 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,13 @@ # CLAUDE.md +## Rust-Go Sync + +When modifying the Rust `cryptpilot-verity`, `verity-core`, or `verity-fuse` code, always evaluate whether the corresponding Go library (`verity-go/`) needs the same change. If the change affects core algorithms (hash computation, merkle tree, descriptor format) or metadata structures (FlatBuffers schema, serialization), apply the equivalent change to the Go code in the same commit. + +## Excluded Paths + +Never commit files under `docs/superpowers/` or `.claude/` to git. These are Claude session artifacts and should be kept local only. Add them to `.gitignore` if not already present. + ## Git Commit Requirements When creating or amending commits: diff --git a/Cargo.lock b/Cargo.lock index b6e3200..950e592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1065,6 +1065,7 @@ dependencies = [ "anyhow", "async-trait", "async-walkdir", + "base64 0.22.1", "clap", "flatbuffers", "flatc", diff --git a/Makefile b/Makefile index ac61f6e..5a8ac34 100644 --- a/Makefile +++ b/Makefile @@ -352,3 +352,27 @@ docker-build-verity: .PHONY: docker-build-all docker-build-all: docker-build +# Cross-language quality assurance targets + +.PHONY: check-fbs +check-fbs: + @test -L verity-go/metadata/metadata.fbs || { echo "ERROR: metadata.fbs should be a symlink to Rust source"; exit 1; } + @test -L verity-go/metadata/metadata_hash.fbs || { echo "ERROR: metadata_hash.fbs should be a symlink to Rust source"; exit 1; } + @echo "FlatBuffers schemas are shared via symlink." + +.PHONY: go-test +go-test: + cd verity-go && /usr/local/go/bin/go test -race -v ./... -skip TestInterop + +.PHONY: check-all +check-all: clippy go-test check-fbs + cargo fmt --check + +.PHONY: gen-interop-fixture +gen-interop-fixture: + @echo "=== Generating interop fixture ===" + cd verity-core && python3 make_testfiles.py + cargo run -p cryptpilot-verity -- format verity-core/testfiles --hash-output - --label env=prod --force + cp verity-core/testfiles/cryptpilot-verity.metadata.fb verity-go/metadata/testdata/rust.metadata.fb + @echo "Fixture updated: verity-go/metadata/testdata/rust.metadata.fb" + diff --git a/cryptpilot-verity/Cargo.toml b/cryptpilot-verity/Cargo.toml index dfde413..3bd9111 100644 --- a/cryptpilot-verity/Cargo.toml +++ b/cryptpilot-verity/Cargo.toml @@ -6,6 +6,7 @@ version.workspace = true [dependencies] anyhow = {workspace = true} +base64 = {workspace = true} async-trait = {workspace = true} async-walkdir = {workspace = true} clap = {workspace = true} diff --git a/cryptpilot-verity/src/metadata/metadata_generated.rs b/cryptpilot-verity/src/metadata/metadata_generated.rs index d80afbd..4bb4a3f 100644 --- a/cryptpilot-verity/src/metadata/metadata_generated.rs +++ b/cryptpilot-verity/src/metadata/metadata_generated.rs @@ -2,879 +2,656 @@ // @generated extern crate alloc; + #[allow(unused_imports, dead_code)] pub mod cryptpilot { - #[allow(unused_imports, dead_code)] - pub mod verity { - - pub enum FsVerityDescriptorOffset {} - #[derive(Copy, Clone, PartialEq)] - - pub struct FsVerityDescriptor<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } - - impl<'a> ::flatbuffers::Follow<'a> for FsVerityDescriptor<'a> { - type Inner = FsVerityDescriptor<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } - - impl<'a> FsVerityDescriptor<'a> { - pub const VT_VERSION: ::flatbuffers::VOffsetT = 4; - pub const VT_HASH_ALGORITHM: ::flatbuffers::VOffsetT = 6; - pub const VT_LOG_BLOCKSIZE: ::flatbuffers::VOffsetT = 8; - pub const VT_DATA_SIZE: ::flatbuffers::VOffsetT = 10; - pub const VT_ROOT_HASH: ::flatbuffers::VOffsetT = 12; - pub const VT_SALT: ::flatbuffers::VOffsetT = 14; - - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - FsVerityDescriptor { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args FsVerityDescriptorArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = FsVerityDescriptorBuilder::new(_fbb); - builder.add_data_size(args.data_size); - if let Some(x) = args.salt { - builder.add_salt(x); - } - if let Some(x) = args.root_hash { - builder.add_root_hash(x); - } - builder.add_log_blocksize(args.log_blocksize); - builder.add_hash_algorithm(args.hash_algorithm); - builder.add_version(args.version); - builder.finish() - } - - #[inline] - pub fn version(&self) -> u8 { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::(FsVerityDescriptor::VT_VERSION, Some(0)) - .unwrap() - } - } - #[inline] - pub fn hash_algorithm(&self) -> u8 { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::(FsVerityDescriptor::VT_HASH_ALGORITHM, Some(0)) - .unwrap() - } - } - #[inline] - pub fn log_blocksize(&self) -> u8 { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::(FsVerityDescriptor::VT_LOG_BLOCKSIZE, Some(0)) - .unwrap() - } - } - #[inline] - pub fn data_size(&self) -> u64 { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::(FsVerityDescriptor::VT_DATA_SIZE, Some(0)) - .unwrap() - } - } - #[inline] - pub fn root_hash(&self) -> Option<::flatbuffers::Vector<'a, u8>> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>( - FsVerityDescriptor::VT_ROOT_HASH, - None, - ) - } - } - #[inline] - pub fn salt(&self) -> Option<::flatbuffers::Vector<'a, u8>> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>( - FsVerityDescriptor::VT_SALT, - None, - ) - } - } - } - - impl ::flatbuffers::Verifiable for FsVerityDescriptor<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::("version", Self::VT_VERSION, false)? - .visit_field::("hash_algorithm", Self::VT_HASH_ALGORITHM, false)? - .visit_field::("log_blocksize", Self::VT_LOG_BLOCKSIZE, false)? - .visit_field::("data_size", Self::VT_DATA_SIZE, false)? - .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>( - "root_hash", - Self::VT_ROOT_HASH, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>( - "salt", - Self::VT_SALT, - false, - )? - .finish(); - Ok(()) - } - } - pub struct FsVerityDescriptorArgs<'a> { - pub version: u8, - pub hash_algorithm: u8, - pub log_blocksize: u8, - pub data_size: u64, - pub root_hash: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, - pub salt: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, - } - impl<'a> Default for FsVerityDescriptorArgs<'a> { - #[inline] - fn default() -> Self { - FsVerityDescriptorArgs { - version: 0, - hash_algorithm: 0, - log_blocksize: 0, - data_size: 0, - root_hash: None, - salt: None, - } - } - } - - pub struct FsVerityDescriptorBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FsVerityDescriptorBuilder<'a, 'b, A> { - #[inline] - pub fn add_version(&mut self, version: u8) { - self.fbb_ - .push_slot::(FsVerityDescriptor::VT_VERSION, version, 0); - } - #[inline] - pub fn add_hash_algorithm(&mut self, hash_algorithm: u8) { - self.fbb_ - .push_slot::(FsVerityDescriptor::VT_HASH_ALGORITHM, hash_algorithm, 0); - } - #[inline] - pub fn add_log_blocksize(&mut self, log_blocksize: u8) { - self.fbb_ - .push_slot::(FsVerityDescriptor::VT_LOG_BLOCKSIZE, log_blocksize, 0); - } - #[inline] - pub fn add_data_size(&mut self, data_size: u64) { - self.fbb_ - .push_slot::(FsVerityDescriptor::VT_DATA_SIZE, data_size, 0); - } - #[inline] - pub fn add_root_hash( - &mut self, - root_hash: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b, u8>>, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FsVerityDescriptor::VT_ROOT_HASH, - root_hash, - ); - } - #[inline] - pub fn add_salt( - &mut self, - salt: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b, u8>>, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FsVerityDescriptor::VT_SALT, - salt, - ); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> FsVerityDescriptorBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - FsVerityDescriptorBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } - - impl ::core::fmt::Debug for FsVerityDescriptor<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("FsVerityDescriptor"); - ds.field("version", &self.version()); - ds.field("hash_algorithm", &self.hash_algorithm()); - ds.field("log_blocksize", &self.log_blocksize()); - ds.field("data_size", &self.data_size()); - ds.field("root_hash", &self.root_hash()); - ds.field("salt", &self.salt()); - ds.finish() - } - } - pub enum KeyValueOffset {} - #[derive(Copy, Clone, PartialEq)] - - pub struct KeyValue<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } - - impl<'a> ::flatbuffers::Follow<'a> for KeyValue<'a> { - type Inner = KeyValue<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } - - impl<'a> KeyValue<'a> { - pub const VT_KEY: ::flatbuffers::VOffsetT = 4; - pub const VT_VALUE: ::flatbuffers::VOffsetT = 6; - - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - KeyValue { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args KeyValueArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = KeyValueBuilder::new(_fbb); - if let Some(x) = args.value { - builder.add_value(x); - } - if let Some(x) = args.key { - builder.add_key(x); - } - builder.finish() - } - - #[inline] - pub fn key(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<&str>>(KeyValue::VT_KEY, None) - } - } - #[inline] - pub fn value(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<&str>>(KeyValue::VT_VALUE, None) - } - } - } - - impl ::flatbuffers::Verifiable for KeyValue<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "key", - Self::VT_KEY, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "value", - Self::VT_VALUE, - false, - )? - .finish(); - Ok(()) - } - } - pub struct KeyValueArgs<'a> { - pub key: Option<::flatbuffers::WIPOffset<&'a str>>, - pub value: Option<::flatbuffers::WIPOffset<&'a str>>, - } - impl<'a> Default for KeyValueArgs<'a> { - #[inline] - fn default() -> Self { - KeyValueArgs { - key: None, - value: None, - } - } - } - - pub struct KeyValueBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> KeyValueBuilder<'a, 'b, A> { - #[inline] - pub fn add_key(&mut self, key: ::flatbuffers::WIPOffset<&'b str>) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset<_>>(KeyValue::VT_KEY, key); - } - #[inline] - pub fn add_value(&mut self, value: ::flatbuffers::WIPOffset<&'b str>) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset<_>>(KeyValue::VT_VALUE, value); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> KeyValueBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - KeyValueBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } - - impl ::core::fmt::Debug for KeyValue<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("KeyValue"); - ds.field("key", &self.key()); - ds.field("value", &self.value()); - ds.finish() - } - } - pub enum FileInfoOffset {} - #[derive(Copy, Clone, PartialEq)] - - pub struct FileInfo<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } - - impl<'a> ::flatbuffers::Follow<'a> for FileInfo<'a> { - type Inner = FileInfo<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } - - impl<'a> FileInfo<'a> { - pub const VT_PATH: ::flatbuffers::VOffsetT = 4; - pub const VT_DESCRIPTOR: ::flatbuffers::VOffsetT = 6; - pub const VT_MERKLE_TREE_LEVEL1: ::flatbuffers::VOffsetT = 8; - pub const VT_DESCRIPTOR_HASH: ::flatbuffers::VOffsetT = 10; - - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - FileInfo { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args FileInfoArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = FileInfoBuilder::new(_fbb); - if let Some(x) = args.descriptor_hash { - builder.add_descriptor_hash(x); - } - if let Some(x) = args.merkle_tree_level1 { - builder.add_merkle_tree_level1(x); - } - if let Some(x) = args.descriptor { - builder.add_descriptor(x); - } - if let Some(x) = args.path { - builder.add_path(x); - } - builder.finish() - } - - #[inline] - pub fn path(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<&str>>(FileInfo::VT_PATH, None) - } - } - #[inline] - pub fn descriptor(&self) -> Option> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset>( - FileInfo::VT_DESCRIPTOR, - None, - ) - } - } - #[inline] - pub fn merkle_tree_level1(&self) -> Option<::flatbuffers::Vector<'a, u8>> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>( - FileInfo::VT_MERKLE_TREE_LEVEL1, - None, - ) - } - } - #[inline] - pub fn descriptor_hash(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>( - FileInfo::VT_DESCRIPTOR_HASH, - None, - ) - } - } - } - - impl ::flatbuffers::Verifiable for FileInfo<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "path", - Self::VT_PATH, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset>( - "descriptor", - Self::VT_DESCRIPTOR, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>( - "merkle_tree_level1", - Self::VT_MERKLE_TREE_LEVEL1, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "descriptor_hash", - Self::VT_DESCRIPTOR_HASH, - false, - )? - .finish(); - Ok(()) - } - } - pub struct FileInfoArgs<'a> { - pub path: Option<::flatbuffers::WIPOffset<&'a str>>, - pub descriptor: Option<::flatbuffers::WIPOffset>>, - pub merkle_tree_level1: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, - pub descriptor_hash: Option<::flatbuffers::WIPOffset<&'a str>>, - } - impl<'a> Default for FileInfoArgs<'a> { - #[inline] - fn default() -> Self { - FileInfoArgs { - path: None, - descriptor: None, - merkle_tree_level1: None, - descriptor_hash: None, - } - } - } - - pub struct FileInfoBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FileInfoBuilder<'a, 'b, A> { - #[inline] - pub fn add_path(&mut self, path: ::flatbuffers::WIPOffset<&'b str>) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset<_>>(FileInfo::VT_PATH, path); - } - #[inline] - pub fn add_descriptor( - &mut self, - descriptor: ::flatbuffers::WIPOffset>, - ) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset>( - FileInfo::VT_DESCRIPTOR, - descriptor, - ); - } - #[inline] - pub fn add_merkle_tree_level1( - &mut self, - merkle_tree_level1: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b, u8>>, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FileInfo::VT_MERKLE_TREE_LEVEL1, - merkle_tree_level1, - ); - } - #[inline] - pub fn add_descriptor_hash( - &mut self, - descriptor_hash: ::flatbuffers::WIPOffset<&'b str>, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FileInfo::VT_DESCRIPTOR_HASH, - descriptor_hash, - ); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> FileInfoBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - FileInfoBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } - - impl ::core::fmt::Debug for FileInfo<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("FileInfo"); - ds.field("path", &self.path()); - ds.field("descriptor", &self.descriptor()); - ds.field("merkle_tree_level1", &self.merkle_tree_level1()); - ds.field("descriptor_hash", &self.descriptor_hash()); - ds.finish() - } - } - pub enum MetadataOffset {} - #[derive(Copy, Clone, PartialEq)] - - pub struct Metadata<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } - - impl<'a> ::flatbuffers::Follow<'a> for Metadata<'a> { - type Inner = Metadata<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } - - impl<'a> Metadata<'a> { - pub const VT_VERSION: ::flatbuffers::VOffsetT = 4; - pub const VT_FILES: ::flatbuffers::VOffsetT = 6; - pub const VT_LABELS: ::flatbuffers::VOffsetT = 8; - - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - Metadata { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args MetadataArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = MetadataBuilder::new(_fbb); - if let Some(x) = args.labels { - builder.add_labels(x); - } - if let Some(x) = args.files { - builder.add_files(x); - } - builder.add_version(args.version); - builder.finish() - } - - #[inline] - pub fn version(&self) -> u32 { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { self._tab.get::(Metadata::VT_VERSION, Some(1)).unwrap() } - } - #[inline] - pub fn files( - &self, - ) -> Option<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>> - { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>, - >>(Metadata::VT_FILES, None) - } - } - #[inline] - pub fn labels( - &self, - ) -> Option<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>> - { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>, - >>(Metadata::VT_LABELS, None) - } - } - } - - impl ::flatbuffers::Verifiable for Metadata<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::("version", Self::VT_VERSION, false)? - .visit_field::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector<'_, ::flatbuffers::ForwardsUOffset>, - >>("files", Self::VT_FILES, false)? - .visit_field::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector<'_, ::flatbuffers::ForwardsUOffset>, - >>("labels", Self::VT_LABELS, false)? - .finish(); - Ok(()) - } - } - pub struct MetadataArgs<'a> { - pub version: u32, - pub files: Option< - ::flatbuffers::WIPOffset< - ::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>, - >, - >, - pub labels: Option< - ::flatbuffers::WIPOffset< - ::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>, - >, - >, - } - impl<'a> Default for MetadataArgs<'a> { - #[inline] - fn default() -> Self { - MetadataArgs { - version: 1, - files: None, - labels: None, - } - } - } - - pub struct MetadataBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> MetadataBuilder<'a, 'b, A> { - #[inline] - pub fn add_version(&mut self, version: u32) { - self.fbb_.push_slot::(Metadata::VT_VERSION, version, 1); - } - #[inline] - pub fn add_files( - &mut self, - files: ::flatbuffers::WIPOffset< - ::flatbuffers::Vector<'b, ::flatbuffers::ForwardsUOffset>>, - >, - ) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset<_>>(Metadata::VT_FILES, files); - } - #[inline] - pub fn add_labels( - &mut self, - labels: ::flatbuffers::WIPOffset< - ::flatbuffers::Vector<'b, ::flatbuffers::ForwardsUOffset>>, - >, - ) { - self.fbb_ - .push_slot_always::<::flatbuffers::WIPOffset<_>>(Metadata::VT_LABELS, labels); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> MetadataBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - MetadataBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } - - impl ::core::fmt::Debug for Metadata<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("Metadata"); - ds.field("version", &self.version()); - ds.field("files", &self.files()); - ds.field("labels", &self.labels()); - ds.finish() - } - } - #[inline] - /// Verifies that a buffer of bytes contains a `Metadata` - /// and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_unchecked`. - pub fn root_as_metadata( - buf: &[u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::root::(buf) - } - #[inline] - /// Verifies that a buffer of bytes contains a size prefixed - /// `Metadata` and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `size_prefixed_root_as_metadata_unchecked`. - pub fn size_prefixed_root_as_metadata( - buf: &[u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::size_prefixed_root::(buf) - } - #[inline] - /// Verifies, with the given options, that a buffer of bytes - /// contains a `Metadata` and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_unchecked`. - pub fn root_as_metadata_with_opts<'b, 'o>( - opts: &'o ::flatbuffers::VerifierOptions, - buf: &'b [u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::root_with_opts::>(opts, buf) - } - #[inline] - /// Verifies, with the given verifier options, that a buffer of - /// bytes contains a size prefixed `Metadata` and returns - /// it. Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_unchecked`. - pub fn size_prefixed_root_as_metadata_with_opts<'b, 'o>( - opts: &'o ::flatbuffers::VerifierOptions, - buf: &'b [u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::size_prefixed_root_with_opts::>(opts, buf) - } - #[inline] - /// Assumes, without verification, that a buffer of bytes contains a Metadata and returns it. - /// # Safety - /// Callers must trust the given bytes do indeed contain a valid `Metadata`. - pub unsafe fn root_as_metadata_unchecked(buf: &[u8]) -> Metadata<'_> { - unsafe { ::flatbuffers::root_unchecked::(buf) } - } - #[inline] - /// Assumes, without verification, that a buffer of bytes contains a size prefixed Metadata and returns it. - /// # Safety - /// Callers must trust the given bytes do indeed contain a valid size prefixed `Metadata`. - pub unsafe fn size_prefixed_root_as_metadata_unchecked(buf: &[u8]) -> Metadata<'_> { - unsafe { ::flatbuffers::size_prefixed_root_unchecked::(buf) } - } - #[inline] - pub fn finish_metadata_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>( - fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - root: ::flatbuffers::WIPOffset>, - ) { - fbb.finish(root, None); - } - - #[inline] - pub fn finish_size_prefixed_metadata_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>( - fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - root: ::flatbuffers::WIPOffset>, - ) { - fbb.finish_size_prefixed(root, None); - } - } // pub mod verity -} // pub mod cryptpilot +#[allow(unused_imports, dead_code)] +pub mod verity { + + +pub enum FsVerityDescriptorOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct FsVerityDescriptor<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} + +impl<'a> ::flatbuffers::Follow<'a> for FsVerityDescriptor<'a> { + type Inner = FsVerityDescriptor<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} + +impl<'a> FsVerityDescriptor<'a> { + pub const VT_VERSION: ::flatbuffers::VOffsetT = 4; + pub const VT_HASH_ALGORITHM: ::flatbuffers::VOffsetT = 6; + pub const VT_LOG_BLOCKSIZE: ::flatbuffers::VOffsetT = 8; + pub const VT_DATA_SIZE: ::flatbuffers::VOffsetT = 10; + pub const VT_ROOT_HASH: ::flatbuffers::VOffsetT = 12; + pub const VT_SALT: ::flatbuffers::VOffsetT = 14; + + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + FsVerityDescriptor { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args FsVerityDescriptorArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = FsVerityDescriptorBuilder::new(_fbb); + builder.add_data_size(args.data_size); + if let Some(x) = args.salt { builder.add_salt(x); } + if let Some(x) = args.root_hash { builder.add_root_hash(x); } + builder.add_log_blocksize(args.log_blocksize); + builder.add_hash_algorithm(args.hash_algorithm); + builder.add_version(args.version); + builder.finish() + } + + + #[inline] + pub fn version(&self) -> u8 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(FsVerityDescriptor::VT_VERSION, Some(0)).unwrap()} + } + #[inline] + pub fn hash_algorithm(&self) -> u8 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(FsVerityDescriptor::VT_HASH_ALGORITHM, Some(0)).unwrap()} + } + #[inline] + pub fn log_blocksize(&self) -> u8 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(FsVerityDescriptor::VT_LOG_BLOCKSIZE, Some(0)).unwrap()} + } + #[inline] + pub fn data_size(&self) -> u64 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(FsVerityDescriptor::VT_DATA_SIZE, Some(0)).unwrap()} + } + #[inline] + pub fn root_hash(&self) -> Option<::flatbuffers::Vector<'a, u8>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>(FsVerityDescriptor::VT_ROOT_HASH, None)} + } + #[inline] + pub fn salt(&self) -> Option<::flatbuffers::Vector<'a, u8>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>(FsVerityDescriptor::VT_SALT, None)} + } +} + +impl ::flatbuffers::Verifiable for FsVerityDescriptor<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::("version", Self::VT_VERSION, false)? + .visit_field::("hash_algorithm", Self::VT_HASH_ALGORITHM, false)? + .visit_field::("log_blocksize", Self::VT_LOG_BLOCKSIZE, false)? + .visit_field::("data_size", Self::VT_DATA_SIZE, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>("root_hash", Self::VT_ROOT_HASH, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>("salt", Self::VT_SALT, false)? + .finish(); + Ok(()) + } +} +pub struct FsVerityDescriptorArgs<'a> { + pub version: u8, + pub hash_algorithm: u8, + pub log_blocksize: u8, + pub data_size: u64, + pub root_hash: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, + pub salt: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, +} +impl<'a> Default for FsVerityDescriptorArgs<'a> { + #[inline] + fn default() -> Self { + FsVerityDescriptorArgs { + version: 0, + hash_algorithm: 0, + log_blocksize: 0, + data_size: 0, + root_hash: None, + salt: None, + } + } +} + +pub struct FsVerityDescriptorBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FsVerityDescriptorBuilder<'a, 'b, A> { + #[inline] + pub fn add_version(&mut self, version: u8) { + self.fbb_.push_slot::(FsVerityDescriptor::VT_VERSION, version, 0); + } + #[inline] + pub fn add_hash_algorithm(&mut self, hash_algorithm: u8) { + self.fbb_.push_slot::(FsVerityDescriptor::VT_HASH_ALGORITHM, hash_algorithm, 0); + } + #[inline] + pub fn add_log_blocksize(&mut self, log_blocksize: u8) { + self.fbb_.push_slot::(FsVerityDescriptor::VT_LOG_BLOCKSIZE, log_blocksize, 0); + } + #[inline] + pub fn add_data_size(&mut self, data_size: u64) { + self.fbb_.push_slot::(FsVerityDescriptor::VT_DATA_SIZE, data_size, 0); + } + #[inline] + pub fn add_root_hash(&mut self, root_hash: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , u8>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FsVerityDescriptor::VT_ROOT_HASH, root_hash); + } + #[inline] + pub fn add_salt(&mut self, salt: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , u8>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FsVerityDescriptor::VT_SALT, salt); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> FsVerityDescriptorBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + FsVerityDescriptorBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} + +impl ::core::fmt::Debug for FsVerityDescriptor<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("FsVerityDescriptor"); + ds.field("version", &self.version()); + ds.field("hash_algorithm", &self.hash_algorithm()); + ds.field("log_blocksize", &self.log_blocksize()); + ds.field("data_size", &self.data_size()); + ds.field("root_hash", &self.root_hash()); + ds.field("salt", &self.salt()); + ds.finish() + } +} +pub enum KeyValueOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct KeyValue<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} + +impl<'a> ::flatbuffers::Follow<'a> for KeyValue<'a> { + type Inner = KeyValue<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} + +impl<'a> KeyValue<'a> { + pub const VT_KEY: ::flatbuffers::VOffsetT = 4; + pub const VT_VALUE: ::flatbuffers::VOffsetT = 6; + + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + KeyValue { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args KeyValueArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = KeyValueBuilder::new(_fbb); + if let Some(x) = args.value { builder.add_value(x); } + if let Some(x) = args.key { builder.add_key(x); } + builder.finish() + } + + + #[inline] + pub fn key(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(KeyValue::VT_KEY, None)} + } + #[inline] + pub fn value(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(KeyValue::VT_VALUE, None)} + } +} + +impl ::flatbuffers::Verifiable for KeyValue<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("key", Self::VT_KEY, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("value", Self::VT_VALUE, false)? + .finish(); + Ok(()) + } +} +pub struct KeyValueArgs<'a> { + pub key: Option<::flatbuffers::WIPOffset<&'a str>>, + pub value: Option<::flatbuffers::WIPOffset<&'a str>>, +} +impl<'a> Default for KeyValueArgs<'a> { + #[inline] + fn default() -> Self { + KeyValueArgs { + key: None, + value: None, + } + } +} + +pub struct KeyValueBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> KeyValueBuilder<'a, 'b, A> { + #[inline] + pub fn add_key(&mut self, key: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(KeyValue::VT_KEY, key); + } + #[inline] + pub fn add_value(&mut self, value: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(KeyValue::VT_VALUE, value); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> KeyValueBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + KeyValueBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} + +impl ::core::fmt::Debug for KeyValue<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("KeyValue"); + ds.field("key", &self.key()); + ds.field("value", &self.value()); + ds.finish() + } +} +pub enum FileInfoOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct FileInfo<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} + +impl<'a> ::flatbuffers::Follow<'a> for FileInfo<'a> { + type Inner = FileInfo<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} + +impl<'a> FileInfo<'a> { + pub const VT_PATH: ::flatbuffers::VOffsetT = 4; + pub const VT_DESCRIPTOR: ::flatbuffers::VOffsetT = 6; + pub const VT_MERKLE_TREE_LEVEL1: ::flatbuffers::VOffsetT = 8; + pub const VT_DESCRIPTOR_HASH: ::flatbuffers::VOffsetT = 10; + + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + FileInfo { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args FileInfoArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = FileInfoBuilder::new(_fbb); + if let Some(x) = args.descriptor_hash { builder.add_descriptor_hash(x); } + if let Some(x) = args.merkle_tree_level1 { builder.add_merkle_tree_level1(x); } + if let Some(x) = args.descriptor { builder.add_descriptor(x); } + if let Some(x) = args.path { builder.add_path(x); } + builder.finish() + } + + + #[inline] + pub fn path(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(FileInfo::VT_PATH, None)} + } + #[inline] + pub fn descriptor(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset>(FileInfo::VT_DESCRIPTOR, None)} + } + #[inline] + pub fn merkle_tree_level1(&self) -> Option<::flatbuffers::Vector<'a, u8>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, u8>>>(FileInfo::VT_MERKLE_TREE_LEVEL1, None)} + } + #[inline] + pub fn descriptor_hash(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(FileInfo::VT_DESCRIPTOR_HASH, None)} + } +} + +impl ::flatbuffers::Verifiable for FileInfo<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("path", Self::VT_PATH, false)? + .visit_field::<::flatbuffers::ForwardsUOffset>("descriptor", Self::VT_DESCRIPTOR, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, u8>>>("merkle_tree_level1", Self::VT_MERKLE_TREE_LEVEL1, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("descriptor_hash", Self::VT_DESCRIPTOR_HASH, false)? + .finish(); + Ok(()) + } +} +pub struct FileInfoArgs<'a> { + pub path: Option<::flatbuffers::WIPOffset<&'a str>>, + pub descriptor: Option<::flatbuffers::WIPOffset>>, + pub merkle_tree_level1: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, u8>>>, + pub descriptor_hash: Option<::flatbuffers::WIPOffset<&'a str>>, +} +impl<'a> Default for FileInfoArgs<'a> { + #[inline] + fn default() -> Self { + FileInfoArgs { + path: None, + descriptor: None, + merkle_tree_level1: None, + descriptor_hash: None, + } + } +} + +pub struct FileInfoBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FileInfoBuilder<'a, 'b, A> { + #[inline] + pub fn add_path(&mut self, path: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FileInfo::VT_PATH, path); + } + #[inline] + pub fn add_descriptor(&mut self, descriptor: ::flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset>(FileInfo::VT_DESCRIPTOR, descriptor); + } + #[inline] + pub fn add_merkle_tree_level1(&mut self, merkle_tree_level1: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , u8>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FileInfo::VT_MERKLE_TREE_LEVEL1, merkle_tree_level1); + } + #[inline] + pub fn add_descriptor_hash(&mut self, descriptor_hash: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FileInfo::VT_DESCRIPTOR_HASH, descriptor_hash); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> FileInfoBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + FileInfoBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} + +impl ::core::fmt::Debug for FileInfo<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("FileInfo"); + ds.field("path", &self.path()); + ds.field("descriptor", &self.descriptor()); + ds.field("merkle_tree_level1", &self.merkle_tree_level1()); + ds.field("descriptor_hash", &self.descriptor_hash()); + ds.finish() + } +} +pub enum MetadataOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct Metadata<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} + +impl<'a> ::flatbuffers::Follow<'a> for Metadata<'a> { + type Inner = Metadata<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} + +impl<'a> Metadata<'a> { + pub const VT_VERSION: ::flatbuffers::VOffsetT = 4; + pub const VT_FILES: ::flatbuffers::VOffsetT = 6; + pub const VT_LABELS: ::flatbuffers::VOffsetT = 8; + + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + Metadata { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args MetadataArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = MetadataBuilder::new(_fbb); + if let Some(x) = args.labels { builder.add_labels(x); } + if let Some(x) = args.files { builder.add_files(x); } + builder.add_version(args.version); + builder.finish() + } + + + #[inline] + pub fn version(&self) -> u32 { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::(Metadata::VT_VERSION, Some(1)).unwrap()} + } + #[inline] + pub fn files(&self) -> Option<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>(Metadata::VT_FILES, None)} + } + #[inline] + pub fn labels(&self) -> Option<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>(Metadata::VT_LABELS, None)} + } +} + +impl ::flatbuffers::Verifiable for Metadata<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::("version", Self::VT_VERSION, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, ::flatbuffers::ForwardsUOffset>>>("files", Self::VT_FILES, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, ::flatbuffers::ForwardsUOffset>>>("labels", Self::VT_LABELS, false)? + .finish(); + Ok(()) + } +} +pub struct MetadataArgs<'a> { + pub version: u32, + pub files: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>>, + pub labels: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>>, +} +impl<'a> Default for MetadataArgs<'a> { + #[inline] + fn default() -> Self { + MetadataArgs { + version: 1, + files: None, + labels: None, + } + } +} + +pub struct MetadataBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> MetadataBuilder<'a, 'b, A> { + #[inline] + pub fn add_version(&mut self, version: u32) { + self.fbb_.push_slot::(Metadata::VT_VERSION, version, 1); + } + #[inline] + pub fn add_files(&mut self, files: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , ::flatbuffers::ForwardsUOffset>>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(Metadata::VT_FILES, files); + } + #[inline] + pub fn add_labels(&mut self, labels: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , ::flatbuffers::ForwardsUOffset>>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(Metadata::VT_LABELS, labels); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> MetadataBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + MetadataBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} + +impl ::core::fmt::Debug for Metadata<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("Metadata"); + ds.field("version", &self.version()); + ds.field("files", &self.files()); + ds.field("labels", &self.labels()); + ds.finish() + } +} +#[inline] +/// Verifies that a buffer of bytes contains a `Metadata` +/// and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_unchecked`. +pub fn root_as_metadata(buf: &[u8]) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::root::(buf) +} +#[inline] +/// Verifies that a buffer of bytes contains a size prefixed +/// `Metadata` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `size_prefixed_root_as_metadata_unchecked`. +pub fn size_prefixed_root_as_metadata(buf: &[u8]) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::size_prefixed_root::(buf) +} +#[inline] +/// Verifies, with the given options, that a buffer of bytes +/// contains a `Metadata` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_unchecked`. +pub fn root_as_metadata_with_opts<'b, 'o>( + opts: &'o ::flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::root_with_opts::>(opts, buf) +} +#[inline] +/// Verifies, with the given verifier options, that a buffer of +/// bytes contains a size prefixed `Metadata` and returns +/// it. Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_unchecked`. +pub fn size_prefixed_root_as_metadata_with_opts<'b, 'o>( + opts: &'o ::flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::size_prefixed_root_with_opts::>(opts, buf) +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a Metadata and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid `Metadata`. +pub unsafe fn root_as_metadata_unchecked(buf: &[u8]) -> Metadata<'_> { + unsafe { ::flatbuffers::root_unchecked::(buf) } +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a size prefixed Metadata and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid size prefixed `Metadata`. +pub unsafe fn size_prefixed_root_as_metadata_unchecked(buf: &[u8]) -> Metadata<'_> { + unsafe { ::flatbuffers::size_prefixed_root_unchecked::(buf) } +} +#[inline] +pub fn finish_metadata_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>( + fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + root: ::flatbuffers::WIPOffset>) { + fbb.finish(root, None); +} + +#[inline] +pub fn finish_size_prefixed_metadata_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>(fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, root: ::flatbuffers::WIPOffset>) { + fbb.finish_size_prefixed(root, None); +} +} // pub mod verity +} // pub mod cryptpilot + diff --git a/cryptpilot-verity/src/metadata/metadata_hash_generated.rs b/cryptpilot-verity/src/metadata/metadata_hash_generated.rs index 81557fd..2d767e0 100644 --- a/cryptpilot-verity/src/metadata/metadata_hash_generated.rs +++ b/cryptpilot-verity/src/metadata/metadata_hash_generated.rs @@ -2,392 +2,298 @@ // @generated extern crate alloc; + #[allow(unused_imports, dead_code)] pub mod cryptpilot { - #[allow(unused_imports, dead_code)] - pub mod verity { +#[allow(unused_imports, dead_code)] +pub mod verity { + +#[allow(unused_imports, dead_code)] +pub mod hash { + + +pub enum FileHashEntryOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub struct FileHashEntry<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} - #[allow(unused_imports, dead_code)] - pub mod hash { +impl<'a> ::flatbuffers::Follow<'a> for FileHashEntry<'a> { + type Inner = FileHashEntry<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} - pub enum FileHashEntryOffset {} - #[derive(Copy, Clone, PartialEq)] +impl<'a> FileHashEntry<'a> { + pub const VT_PATH: ::flatbuffers::VOffsetT = 4; + pub const VT_DESCRIPTOR_HASH: ::flatbuffers::VOffsetT = 6; - pub struct FileHashEntry<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + FileHashEntry { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args FileHashEntryArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = FileHashEntryBuilder::new(_fbb); + if let Some(x) = args.descriptor_hash { builder.add_descriptor_hash(x); } + if let Some(x) = args.path { builder.add_path(x); } + builder.finish() + } - impl<'a> ::flatbuffers::Follow<'a> for FileHashEntry<'a> { - type Inner = FileHashEntry<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } - impl<'a> FileHashEntry<'a> { - pub const VT_PATH: ::flatbuffers::VOffsetT = 4; - pub const VT_DESCRIPTOR_HASH: ::flatbuffers::VOffsetT = 6; + #[inline] + pub fn path(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(FileHashEntry::VT_PATH, None)} + } + #[inline] + pub fn descriptor_hash(&self) -> Option<&'a str> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>(FileHashEntry::VT_DESCRIPTOR_HASH, None)} + } +} - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - FileHashEntry { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args FileHashEntryArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = FileHashEntryBuilder::new(_fbb); - if let Some(x) = args.descriptor_hash { - builder.add_descriptor_hash(x); - } - if let Some(x) = args.path { - builder.add_path(x); - } - builder.finish() - } +impl ::flatbuffers::Verifiable for FileHashEntry<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("path", Self::VT_PATH, false)? + .visit_field::<::flatbuffers::ForwardsUOffset<&str>>("descriptor_hash", Self::VT_DESCRIPTOR_HASH, false)? + .finish(); + Ok(()) + } +} +pub struct FileHashEntryArgs<'a> { + pub path: Option<::flatbuffers::WIPOffset<&'a str>>, + pub descriptor_hash: Option<::flatbuffers::WIPOffset<&'a str>>, +} +impl<'a> Default for FileHashEntryArgs<'a> { + #[inline] + fn default() -> Self { + FileHashEntryArgs { + path: None, + descriptor_hash: None, + } + } +} - #[inline] - pub fn path(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>( - FileHashEntry::VT_PATH, - None, - ) - } - } - #[inline] - pub fn descriptor_hash(&self) -> Option<&'a str> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset<&str>>( - FileHashEntry::VT_DESCRIPTOR_HASH, - None, - ) - } - } - } +pub struct FileHashEntryBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FileHashEntryBuilder<'a, 'b, A> { + #[inline] + pub fn add_path(&mut self, path: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FileHashEntry::VT_PATH, path); + } + #[inline] + pub fn add_descriptor_hash(&mut self, descriptor_hash: ::flatbuffers::WIPOffset<&'b str>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(FileHashEntry::VT_DESCRIPTOR_HASH, descriptor_hash); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> FileHashEntryBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + FileHashEntryBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} - impl ::flatbuffers::Verifiable for FileHashEntry<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "path", - Self::VT_PATH, - false, - )? - .visit_field::<::flatbuffers::ForwardsUOffset<&str>>( - "descriptor_hash", - Self::VT_DESCRIPTOR_HASH, - false, - )? - .finish(); - Ok(()) - } - } - pub struct FileHashEntryArgs<'a> { - pub path: Option<::flatbuffers::WIPOffset<&'a str>>, - pub descriptor_hash: Option<::flatbuffers::WIPOffset<&'a str>>, - } - impl<'a> Default for FileHashEntryArgs<'a> { - #[inline] - fn default() -> Self { - FileHashEntryArgs { - path: None, - descriptor_hash: None, - } - } - } +impl ::core::fmt::Debug for FileHashEntry<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("FileHashEntry"); + ds.field("path", &self.path()); + ds.field("descriptor_hash", &self.descriptor_hash()); + ds.finish() + } +} +pub enum MetadataHashOffset {} +#[derive(Copy, Clone, PartialEq)] - pub struct FileHashEntryBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> FileHashEntryBuilder<'a, 'b, A> { - #[inline] - pub fn add_path(&mut self, path: ::flatbuffers::WIPOffset<&'b str>) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FileHashEntry::VT_PATH, - path, - ); - } - #[inline] - pub fn add_descriptor_hash( - &mut self, - descriptor_hash: ::flatbuffers::WIPOffset<&'b str>, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - FileHashEntry::VT_DESCRIPTOR_HASH, - descriptor_hash, - ); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> FileHashEntryBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - FileHashEntryBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } +pub struct MetadataHash<'a> { + pub _tab: ::flatbuffers::Table<'a>, +} - impl ::core::fmt::Debug for FileHashEntry<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("FileHashEntry"); - ds.field("path", &self.path()); - ds.field("descriptor_hash", &self.descriptor_hash()); - ds.finish() - } - } - pub enum MetadataHashOffset {} - #[derive(Copy, Clone, PartialEq)] +impl<'a> ::flatbuffers::Follow<'a> for MetadataHash<'a> { + type Inner = MetadataHash<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: unsafe { ::flatbuffers::Table::new(buf, loc) } } + } +} - pub struct MetadataHash<'a> { - pub _tab: ::flatbuffers::Table<'a>, - } +impl<'a> MetadataHash<'a> { + pub const VT_FILES: ::flatbuffers::VOffsetT = 4; - impl<'a> ::flatbuffers::Follow<'a> for MetadataHash<'a> { - type Inner = MetadataHash<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: unsafe { ::flatbuffers::Table::new(buf, loc) }, - } - } - } + #[inline] + pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { + MetadataHash { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: ::flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args MetadataHashArgs<'args> + ) -> ::flatbuffers::WIPOffset> { + let mut builder = MetadataHashBuilder::new(_fbb); + if let Some(x) = args.files { builder.add_files(x); } + builder.finish() + } - impl<'a> MetadataHash<'a> { - pub const VT_FILES: ::flatbuffers::VOffsetT = 4; - #[inline] - pub unsafe fn init_from_table(table: ::flatbuffers::Table<'a>) -> Self { - MetadataHash { _tab: table } - } - #[allow(unused_mut)] - pub fn create< - 'bldr: 'args, - 'args: 'mut_bldr, - 'mut_bldr, - A: ::flatbuffers::Allocator + 'bldr, - >( - _fbb: &'mut_bldr mut ::flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args MetadataHashArgs<'args>, - ) -> ::flatbuffers::WIPOffset> { - let mut builder = MetadataHashBuilder::new(_fbb); - if let Some(x) = args.files { - builder.add_files(x); - } - builder.finish() - } + #[inline] + pub fn files(&self) -> Option<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>(MetadataHash::VT_FILES, None)} + } +} - #[inline] - pub fn files( - &self, - ) -> Option< - ::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>, - > { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab.get::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector< - 'a, - ::flatbuffers::ForwardsUOffset, - >, - >>(MetadataHash::VT_FILES, None) - } - } - } +impl ::flatbuffers::Verifiable for MetadataHash<'_> { + #[inline] + fn run_verifier( + v: &mut ::flatbuffers::Verifier, pos: usize + ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { + v.visit_table(pos)? + .visit_field::<::flatbuffers::ForwardsUOffset<::flatbuffers::Vector<'_, ::flatbuffers::ForwardsUOffset>>>("files", Self::VT_FILES, false)? + .finish(); + Ok(()) + } +} +pub struct MetadataHashArgs<'a> { + pub files: Option<::flatbuffers::WIPOffset<::flatbuffers::Vector<'a, ::flatbuffers::ForwardsUOffset>>>>, +} +impl<'a> Default for MetadataHashArgs<'a> { + #[inline] + fn default() -> Self { + MetadataHashArgs { + files: None, + } + } +} - impl ::flatbuffers::Verifiable for MetadataHash<'_> { - #[inline] - fn run_verifier( - v: &mut ::flatbuffers::Verifier, - pos: usize, - ) -> Result<(), ::flatbuffers::InvalidFlatbuffer> { - v.visit_table(pos)? - .visit_field::<::flatbuffers::ForwardsUOffset< - ::flatbuffers::Vector< - '_, - ::flatbuffers::ForwardsUOffset, - >, - >>("files", Self::VT_FILES, false)? - .finish(); - Ok(()) - } - } - pub struct MetadataHashArgs<'a> { - pub files: Option< - ::flatbuffers::WIPOffset< - ::flatbuffers::Vector< - 'a, - ::flatbuffers::ForwardsUOffset>, - >, - >, - >, - } - impl<'a> Default for MetadataHashArgs<'a> { - #[inline] - fn default() -> Self { - MetadataHashArgs { files: None } - } - } +pub struct MetadataHashBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { + fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, +} +impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> MetadataHashBuilder<'a, 'b, A> { + #[inline] + pub fn add_files(&mut self, files: ::flatbuffers::WIPOffset<::flatbuffers::Vector<'b , ::flatbuffers::ForwardsUOffset>>>) { + self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>(MetadataHash::VT_FILES, files); + } + #[inline] + pub fn new(_fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>) -> MetadataHashBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + MetadataHashBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> ::flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + ::flatbuffers::WIPOffset::new(o.value()) + } +} - pub struct MetadataHashBuilder<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> { - fbb_: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - start_: ::flatbuffers::WIPOffset<::flatbuffers::TableUnfinishedWIPOffset>, - } - impl<'a: 'b, 'b, A: ::flatbuffers::Allocator + 'a> MetadataHashBuilder<'a, 'b, A> { - #[inline] - pub fn add_files( - &mut self, - files: ::flatbuffers::WIPOffset< - ::flatbuffers::Vector< - 'b, - ::flatbuffers::ForwardsUOffset>, - >, - >, - ) { - self.fbb_.push_slot_always::<::flatbuffers::WIPOffset<_>>( - MetadataHash::VT_FILES, - files, - ); - } - #[inline] - pub fn new( - _fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - ) -> MetadataHashBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - MetadataHashBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> ::flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - ::flatbuffers::WIPOffset::new(o.value()) - } - } +impl ::core::fmt::Debug for MetadataHash<'_> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + let mut ds = f.debug_struct("MetadataHash"); + ds.field("files", &self.files()); + ds.finish() + } +} +#[inline] +/// Verifies that a buffer of bytes contains a `MetadataHash` +/// and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_hash_unchecked`. +pub fn root_as_metadata_hash(buf: &[u8]) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::root::(buf) +} +#[inline] +/// Verifies that a buffer of bytes contains a size prefixed +/// `MetadataHash` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `size_prefixed_root_as_metadata_hash_unchecked`. +pub fn size_prefixed_root_as_metadata_hash(buf: &[u8]) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::size_prefixed_root::(buf) +} +#[inline] +/// Verifies, with the given options, that a buffer of bytes +/// contains a `MetadataHash` and returns it. +/// Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_hash_unchecked`. +pub fn root_as_metadata_hash_with_opts<'b, 'o>( + opts: &'o ::flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::root_with_opts::>(opts, buf) +} +#[inline] +/// Verifies, with the given verifier options, that a buffer of +/// bytes contains a size prefixed `MetadataHash` and returns +/// it. Note that verification is still experimental and may not +/// catch every error, or be maximally performant. For the +/// previous, unchecked, behavior use +/// `root_as_metadata_hash_unchecked`. +pub fn size_prefixed_root_as_metadata_hash_with_opts<'b, 'o>( + opts: &'o ::flatbuffers::VerifierOptions, + buf: &'b [u8], +) -> Result, ::flatbuffers::InvalidFlatbuffer> { + ::flatbuffers::size_prefixed_root_with_opts::>(opts, buf) +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a MetadataHash and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid `MetadataHash`. +pub unsafe fn root_as_metadata_hash_unchecked(buf: &[u8]) -> MetadataHash<'_> { + unsafe { ::flatbuffers::root_unchecked::(buf) } +} +#[inline] +/// Assumes, without verification, that a buffer of bytes contains a size prefixed MetadataHash and returns it. +/// # Safety +/// Callers must trust the given bytes do indeed contain a valid size prefixed `MetadataHash`. +pub unsafe fn size_prefixed_root_as_metadata_hash_unchecked(buf: &[u8]) -> MetadataHash<'_> { + unsafe { ::flatbuffers::size_prefixed_root_unchecked::(buf) } +} +#[inline] +pub fn finish_metadata_hash_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>( + fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, + root: ::flatbuffers::WIPOffset>) { + fbb.finish(root, None); +} - impl ::core::fmt::Debug for MetadataHash<'_> { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - let mut ds = f.debug_struct("MetadataHash"); - ds.field("files", &self.files()); - ds.finish() - } - } - #[inline] - /// Verifies that a buffer of bytes contains a `MetadataHash` - /// and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_hash_unchecked`. - pub fn root_as_metadata_hash( - buf: &[u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::root::(buf) - } - #[inline] - /// Verifies that a buffer of bytes contains a size prefixed - /// `MetadataHash` and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `size_prefixed_root_as_metadata_hash_unchecked`. - pub fn size_prefixed_root_as_metadata_hash( - buf: &[u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::size_prefixed_root::(buf) - } - #[inline] - /// Verifies, with the given options, that a buffer of bytes - /// contains a `MetadataHash` and returns it. - /// Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_hash_unchecked`. - pub fn root_as_metadata_hash_with_opts<'b, 'o>( - opts: &'o ::flatbuffers::VerifierOptions, - buf: &'b [u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::root_with_opts::>(opts, buf) - } - #[inline] - /// Verifies, with the given verifier options, that a buffer of - /// bytes contains a size prefixed `MetadataHash` and returns - /// it. Note that verification is still experimental and may not - /// catch every error, or be maximally performant. For the - /// previous, unchecked, behavior use - /// `root_as_metadata_hash_unchecked`. - pub fn size_prefixed_root_as_metadata_hash_with_opts<'b, 'o>( - opts: &'o ::flatbuffers::VerifierOptions, - buf: &'b [u8], - ) -> Result, ::flatbuffers::InvalidFlatbuffer> { - ::flatbuffers::size_prefixed_root_with_opts::>(opts, buf) - } - #[inline] - /// Assumes, without verification, that a buffer of bytes contains a MetadataHash and returns it. - /// # Safety - /// Callers must trust the given bytes do indeed contain a valid `MetadataHash`. - pub unsafe fn root_as_metadata_hash_unchecked(buf: &[u8]) -> MetadataHash<'_> { - unsafe { ::flatbuffers::root_unchecked::(buf) } - } - #[inline] - /// Assumes, without verification, that a buffer of bytes contains a size prefixed MetadataHash and returns it. - /// # Safety - /// Callers must trust the given bytes do indeed contain a valid size prefixed `MetadataHash`. - pub unsafe fn size_prefixed_root_as_metadata_hash_unchecked( - buf: &[u8], - ) -> MetadataHash<'_> { - unsafe { ::flatbuffers::size_prefixed_root_unchecked::(buf) } - } - #[inline] - pub fn finish_metadata_hash_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>( - fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - root: ::flatbuffers::WIPOffset>, - ) { - fbb.finish(root, None); - } +#[inline] +pub fn finish_size_prefixed_metadata_hash_buffer<'a, 'b, A: ::flatbuffers::Allocator + 'a>(fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, root: ::flatbuffers::WIPOffset>) { + fbb.finish_size_prefixed(root, None); +} +} // pub mod hash +} // pub mod verity +} // pub mod cryptpilot - #[inline] - pub fn finish_size_prefixed_metadata_hash_buffer< - 'a, - 'b, - A: ::flatbuffers::Allocator + 'a, - >( - fbb: &'b mut ::flatbuffers::FlatBufferBuilder<'a, A>, - root: ::flatbuffers::WIPOffset>, - ) { - fbb.finish_size_prefixed(root, None); - } - } // pub mod hash - } // pub mod verity -} // pub mod cryptpilot diff --git a/cryptpilot-verity/src/metadata/mod.rs b/cryptpilot-verity/src/metadata/mod.rs index 33973ee..1ca404e 100644 --- a/cryptpilot-verity/src/metadata/mod.rs +++ b/cryptpilot-verity/src/metadata/mod.rs @@ -1,9 +1,13 @@ // FlatBuffers serialization and deserialization utilities -#![allow(clippy::all)] -#![allow(warnings)] +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(warnings)] #[allow(unused_imports, dead_code)] mod metadata_generated; +#[rustfmt::skip] +#[allow(clippy::all)] +#[allow(warnings)] #[allow(unused_imports, dead_code)] mod metadata_hash_generated; @@ -16,11 +20,11 @@ pub use metadata_hash_generated::cryptpilot::verity::hash::{ }; use anyhow::{bail, Result}; -use flatbuffers::{FlatBufferBuilder, WIPOffset}; +use flatbuffers::FlatBufferBuilder; use sha2::digest::typenum::Unsigned; use sha2::{digest::OutputSizeUser, Digest, Sha256}; use std::collections::BTreeMap; -use verity_core::digest::{FsVeritySha256, InnerHash}; +use verity_core::digest::FsVeritySha256; use verity_core::tree::MerkleTree; use verity_fuse::file_verifier::file_verity_info::FileVerityInfo; @@ -189,18 +193,18 @@ pub fn deserialize_metadata(data: &[u8]) -> Result { // convert merkle_tree_level1 to Vec> let merkle_tree_level1: Vec> = { - const chunk_size: usize = ::OutputSize::USIZE; - let iter = merkle_tree_level1.chunks_exact(chunk_size); + const CHUNK_SIZE: usize = ::OutputSize::USIZE; + let iter = merkle_tree_level1.chunks_exact(CHUNK_SIZE); if !iter.remainder().is_empty() { bail!( "Broken merkle tree for {}: level 1 length is {} not a multiple of the hash size {}", path, merkle_tree_level1.len(), - chunk_size + CHUNK_SIZE ); } iter.map(|chunk| { - let array: [u8; chunk_size] = chunk.try_into().unwrap(); // this should never fail + let array: [u8; CHUNK_SIZE] = chunk.try_into().unwrap(); // this should never fail From::from(array) }) .collect() diff --git a/verity-go/README.md b/verity-go/README.md new file mode 100644 index 0000000..f25cf49 --- /dev/null +++ b/verity-go/README.md @@ -0,0 +1,131 @@ +# verity-go + +A Go implementation of Linux fs-verity, providing file integrity verification compatible with the kernel's fs-verity mechanism. + +This is the Go counterpart of [cryptpilot-verity](https://github.com/openanolis/cryptpilot), sharing the same FlatBuffers schema via symlinks. + +## Installation + +```bash +go get github.com/openanolis/cryptpilot/verity-go +``` + +Or reference a specific commit: + +```bash +go get github.com/openanolis/cryptpilot/verity-go@master +``` + +## Usage + +### 1. Serialize directory metadata + +The `metadata` package serializes a directory tree's integrity data: each file's +fs-verity descriptor, Merkle tree hashes, and a root hash for the entire directory. + +#### Option A: Rust CLI (recommended) + +The Rust `cryptpilot-verity` CLI handles the full pipeline — walk the directory, +compute fs-verity for each file, build the Merkle tree, and write the FlatBuffers +metadata file: + +```bash +cargo run -p cryptpilot-verity -- format /path/to/dir --label env=prod --hash-output metadata.fb +``` + +#### Option B: Go implementation + +For programmatic control, compute per-file fs-verity and serialize in Go: + +```go +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/openanolis/cryptpilot/verity-go/metadata" +) + +func main() { + data, _ := os.ReadFile("somefile.txt") + desc, tree := metadata.CalculateFsVerityHash(data) + + fileInfos := []metadata.FileVerityInfo{ + { + Path: "somefile.txt", + Descriptor: desc, + MerkleTree: tree, + DescriptorHash: hex.EncodeToString(desc.ToDescriptorHash()), + }, + } + + labels := map[string]string{"env": "prod"} + fb, _ := metadata.SerializeMetadata(fileInfos, labels) + os.WriteFile("metadata.fb", fb, 0644) +} +``` + +### 2. Deserialize and verify + +Load the metadata bytes and run integrity checks. Three levels of verification +are available: + +```go +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/openanolis/cryptpilot/verity-go/metadata" +) + +func main() { + data, _ := os.ReadFile("metadata.fb") + info, _ := metadata.DeserializeMetadata(data) + + // (a) Metadata root hash — compare against a trusted reference value + rootHash, _ := metadata.CalculateMetadataHash(data) + expectedRootHash := "..." // trusted hash from build time or signing + if rootHash != expectedRootHash { + panic("metadata root hash mismatch!") + } + + // (b) Self-verification — each file's descriptor hash and Merkle root + for _, fi := range info.FileInfos { + if err := fi.VerifySelf(); err != nil { + fmt.Printf("%s: verification failed: %v\n", fi.Path, err) + } + } + + // (c) Per-block verification — verify a specific data block against the tree + fi := info.FileInfos[0] + blockSize := fi.Descriptor.BlockSize() + fileData, _ := os.ReadFile(fi.Path) + for blockIndex := 0; blockIndex*blockSize < len(fileData); blockIndex++ { + start := blockIndex * blockSize + end := start + blockSize + if end > len(fileData) { + end = len(fileData) + } + block := fileData[start:end] + if !fi.MerkleTree.VerifyDataBlock(blockIndex, blockSize, block) { + fmt.Printf("%s: block %d is corrupted!\n", fi.Path, blockIndex) + } + } +} +``` + +## Packages + +| Package | Description | +|---------|-------------| +| `verity` | Core fs-verity: streaming hasher, Merkle tree, descriptor | +| `metadata` | FlatBuffers serialization/deserialization of directory integrity metadata | + +## License + +Same as the parent cryptpilot project. See the root `LICENSE` file. diff --git a/verity-go/README_CN.md b/verity-go/README_CN.md new file mode 100644 index 0000000..7e8e47f --- /dev/null +++ b/verity-go/README_CN.md @@ -0,0 +1,129 @@ +# verity-go + +Linux fs-verity 的 Go 实现,提供与内核 fs-verity 机制兼容的文件完整性校验。 + +这是 [cryptpilot-verity](https://github.com/openanolis/cryptpilot) Rust 实现的 Go 对应版本,通过 symlink 共享相同的 FlatBuffers schema。 + +## 安装 + +```bash +go get github.com/openanolis/cryptpilot/verity-go +``` + +或者引用特定提交: + +```bash +go get github.com/openanolis/cryptpilot/verity-go@master +``` + +## 使用方法 + +### 1. 序列化目录元数据 + +`metadata` 包用于将整个目录树的完整性数据序列化:包含每个文件的 +fs-verity 描述符、Merkle 树哈希,以及整个目录的根哈希。 + +#### 方式 A:Rust CLI(推荐) + +Rust `cryptpilot-verity` CLI 可自动完成完整流程 — 遍历目录、 +计算每个文件的 fs-verity、构建 Merkle 树、输出 FlatBuffers 元数据文件: + +```bash +cargo run -p cryptpilot-verity -- format /path/to/dir --label env=prod --hash-output metadata.fb +``` + +#### 方式 B:Go 代码实现 + +如需程序化控制,可在 Go 中逐文件计算 fs-verity 并序列化: + +```go +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/openanolis/cryptpilot/verity-go/metadata" +) + +func main() { + data, _ := os.ReadFile("somefile.txt") + desc, tree := metadata.CalculateFsVerityHash(data) + + fileInfos := []metadata.FileVerityInfo{ + { + Path: "somefile.txt", + Descriptor: desc, + MerkleTree: tree, + DescriptorHash: hex.EncodeToString(desc.ToDescriptorHash()), + }, + } + + labels := map[string]string{"env": "prod"} + fb, _ := metadata.SerializeMetadata(fileInfos, labels) + os.WriteFile("metadata.fb", fb, 0644) +} +``` + +### 2. 反序列化与验证 + +加载元数据字节后,可执行三级完整性校验: + +```go +package main + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/openanolis/cryptpilot/verity-go/metadata" +) + +func main() { + data, _ := os.ReadFile("metadata.fb") + info, _ := metadata.DeserializeMetadata(data) + + // (a) 元数据根哈希 — 与构建期或签名时的可信值比对 + rootHash, _ := metadata.CalculateMetadataHash(data) + expectedRootHash := "..." // 构建或签名时记录的可信哈希 + if rootHash != expectedRootHash { + panic("元数据根哈希不匹配!") + } + + // (b) 自验证 — 每个文件的描述符哈希和 Merkle 根哈希一致性 + for _, fi := range info.FileInfos { + if err := fi.VerifySelf(); err != nil { + fmt.Printf("%s: 验证失败: %v\n", fi.Path, err) + } + } + + // (c) 逐块验证 — 校验文件的指定数据块 + fi := info.FileInfos[0] + blockSize := fi.Descriptor.BlockSize() + fileData, _ := os.ReadFile(fi.Path) + for blockIndex := 0; blockIndex*blockSize < len(fileData); blockIndex++ { + start := blockIndex * blockSize + end := start + blockSize + if end > len(fileData) { + end = len(fileData) + } + block := fileData[start:end] + if !fi.MerkleTree.VerifyDataBlock(blockIndex, blockSize, block) { + fmt.Printf("%s: 块 %d 已被篡改!\n", fi.Path, blockIndex) + } + } +} +``` + +## 包说明 + +| 包 | 说明 | +|----|------| +| `verity` | 核心 fs-verity:流式 hasher、Merkle tree、descriptor | +| `metadata` | FlatBuffers 序列化/反序列化目录完整性元数据 | + +## 许可证 + +与父项目 cryptpilot 相同。见根目录下的 `LICENSE` 文件。 diff --git a/verity-go/go.mod b/verity-go/go.mod new file mode 100644 index 0000000..eb19100 --- /dev/null +++ b/verity-go/go.mod @@ -0,0 +1,5 @@ +module cryptpilot-verity-go + +go 1.24 + +require github.com/google/flatbuffers v25.12.19+incompatible diff --git a/verity-go/go.sum b/verity-go/go.sum new file mode 100644 index 0000000..799c59f --- /dev/null +++ b/verity-go/go.sum @@ -0,0 +1,2 @@ +github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs= +github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= diff --git a/verity-go/metadata/gen_fixture.sh b/verity-go/metadata/gen_fixture.sh new file mode 100755 index 0000000..df63089 --- /dev/null +++ b/verity-go/metadata/gen_fixture.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Regenerate the Rust metadata fixture for Go interop tests. +# Run from the repo root: bash verity-go/metadata/gen_fixture.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +echo "=== Generating testfiles ===" +cd "$REPO_ROOT/verity-core" +python3 make_testfiles.py + +echo "=== Running Rust format ===" +cd "$REPO_ROOT" +cargo run -p cryptpilot-verity -- format verity-core/testfiles --hash-output - --label env=prod --force + +echo "=== Copying fixture ===" +cp verity-core/testfiles/cryptpilot-verity.metadata.fb verity-go/metadata/testdata/rust.metadata.fb +echo "Fixture updated: verity-go/metadata/testdata/rust.metadata.fb" diff --git a/verity-go/metadata/generated/metadata_generated.go b/verity-go/metadata/generated/metadata_generated.go new file mode 100644 index 0000000..e61e0bb --- /dev/null +++ b/verity-go/metadata/generated/metadata_generated.go @@ -0,0 +1,490 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package generated + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type FsVerityDescriptor struct { + _tab flatbuffers.Table +} + +func GetRootAsFsVerityDescriptor(buf []byte, offset flatbuffers.UOffsetT) *FsVerityDescriptor { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &FsVerityDescriptor{} + x.Init(buf, n+offset) + return x +} + +func FinishFsVerityDescriptorBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsFsVerityDescriptor(buf []byte, offset flatbuffers.UOffsetT) *FsVerityDescriptor { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &FsVerityDescriptor{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedFsVerityDescriptorBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *FsVerityDescriptor) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *FsVerityDescriptor) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *FsVerityDescriptor) Version() byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.GetByte(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *FsVerityDescriptor) MutateVersion(n byte) bool { + return rcv._tab.MutateByteSlot(4, n) +} + +func (rcv *FsVerityDescriptor) HashAlgorithm() byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetByte(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *FsVerityDescriptor) MutateHashAlgorithm(n byte) bool { + return rcv._tab.MutateByteSlot(6, n) +} + +func (rcv *FsVerityDescriptor) LogBlocksize() byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetByte(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *FsVerityDescriptor) MutateLogBlocksize(n byte) bool { + return rcv._tab.MutateByteSlot(8, n) +} + +func (rcv *FsVerityDescriptor) DataSize() uint64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.GetUint64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *FsVerityDescriptor) MutateDataSize(n uint64) bool { + return rcv._tab.MutateUint64Slot(10, n) +} + +func (rcv *FsVerityDescriptor) RootHash(j int) byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + } + return 0 +} + +func (rcv *FsVerityDescriptor) RootHashLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *FsVerityDescriptor) RootHashBytes() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *FsVerityDescriptor) MutateRootHash(j int, n byte) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + } + return false +} + +func (rcv *FsVerityDescriptor) Salt(j int) byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + } + return 0 +} + +func (rcv *FsVerityDescriptor) SaltLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *FsVerityDescriptor) SaltBytes() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *FsVerityDescriptor) MutateSalt(j int, n byte) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + } + return false +} + +func FsVerityDescriptorStart(builder *flatbuffers.Builder) { + builder.StartObject(6) +} +func FsVerityDescriptorAddVersion(builder *flatbuffers.Builder, version byte) { + builder.PrependByteSlot(0, version, 0) +} +func FsVerityDescriptorAddHashAlgorithm(builder *flatbuffers.Builder, hashAlgorithm byte) { + builder.PrependByteSlot(1, hashAlgorithm, 0) +} +func FsVerityDescriptorAddLogBlocksize(builder *flatbuffers.Builder, logBlocksize byte) { + builder.PrependByteSlot(2, logBlocksize, 0) +} +func FsVerityDescriptorAddDataSize(builder *flatbuffers.Builder, dataSize uint64) { + builder.PrependUint64Slot(3, dataSize, 0) +} +func FsVerityDescriptorAddRootHash(builder *flatbuffers.Builder, rootHash flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(4, flatbuffers.UOffsetT(rootHash), 0) +} +func FsVerityDescriptorStartRootHashVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(1, numElems, 1) +} +func FsVerityDescriptorAddSalt(builder *flatbuffers.Builder, salt flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(5, flatbuffers.UOffsetT(salt), 0) +} +func FsVerityDescriptorStartSaltVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(1, numElems, 1) +} +func FsVerityDescriptorEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type FileInfo struct { + _tab flatbuffers.Table +} + +func GetRootAsFileInfo(buf []byte, offset flatbuffers.UOffsetT) *FileInfo { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &FileInfo{} + x.Init(buf, n+offset) + return x +} + +func FinishFileInfoBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsFileInfo(buf []byte, offset flatbuffers.UOffsetT) *FileInfo { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &FileInfo{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedFileInfoBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *FileInfo) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *FileInfo) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *FileInfo) Path() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *FileInfo) Descriptor(obj *FsVerityDescriptor) *FsVerityDescriptor { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + x := rcv._tab.Indirect(o + rcv._tab.Pos) + if obj == nil { + obj = new(FsVerityDescriptor) + } + obj.Init(rcv._tab.Bytes, x) + return obj + } + return nil +} + +func (rcv *FileInfo) MerkleTreeLevel1(j int) byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + } + return 0 +} + +func (rcv *FileInfo) MerkleTreeLevel1Length() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *FileInfo) MerkleTreeLevel1Bytes() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *FileInfo) MutateMerkleTreeLevel1(j int, n byte) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + } + return false +} + +func (rcv *FileInfo) DescriptorHash() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func FileInfoStart(builder *flatbuffers.Builder) { + builder.StartObject(4) +} +func FileInfoAddPath(builder *flatbuffers.Builder, path flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(path), 0) +} +func FileInfoAddDescriptor(builder *flatbuffers.Builder, descriptor flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(descriptor), 0) +} +func FileInfoAddMerkleTreeLevel1(builder *flatbuffers.Builder, merkleTreeLevel1 flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(merkleTreeLevel1), 0) +} +func FileInfoStartMerkleTreeLevel1Vector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(1, numElems, 1) +} +func FileInfoAddDescriptorHash(builder *flatbuffers.Builder, descriptorHash flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(descriptorHash), 0) +} +func FileInfoEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type KeyValue struct { + _tab flatbuffers.Table +} + +func GetRootAsKeyValue(buf []byte, offset flatbuffers.UOffsetT) *KeyValue { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &KeyValue{} + x.Init(buf, n+offset) + return x +} + +func FinishKeyValueBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsKeyValue(buf []byte, offset flatbuffers.UOffsetT) *KeyValue { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &KeyValue{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedKeyValueBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *KeyValue) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *KeyValue) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *KeyValue) Key() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *KeyValue) Value() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func KeyValueStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func KeyValueAddKey(builder *flatbuffers.Builder, key flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(key), 0) +} +func KeyValueAddValue(builder *flatbuffers.Builder, value flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(value), 0) +} +func KeyValueEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type Metadata struct { + _tab flatbuffers.Table +} + +func GetRootAsMetadata(buf []byte, offset flatbuffers.UOffsetT) *Metadata { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &Metadata{} + x.Init(buf, n+offset) + return x +} + +func FinishMetadataBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMetadata(buf []byte, offset flatbuffers.UOffsetT) *Metadata { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &Metadata{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMetadataBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *Metadata) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *Metadata) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *Metadata) Version() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 1 +} + +func (rcv *Metadata) MutateVersion(n uint32) bool { + return rcv._tab.MutateUint32Slot(4, n) +} + +func (rcv *Metadata) Files(obj *FileInfo, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *Metadata) FilesLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *Metadata) Labels(obj *KeyValue, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *Metadata) LabelsLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func MetadataStart(builder *flatbuffers.Builder) { + builder.StartObject(3) +} +func MetadataAddVersion(builder *flatbuffers.Builder, version uint32) { + builder.PrependUint32Slot(0, version, 1) +} +func MetadataAddFiles(builder *flatbuffers.Builder, files flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(files), 0) +} +func MetadataStartFilesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func MetadataAddLabels(builder *flatbuffers.Builder, labels flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(labels), 0) +} +func MetadataStartLabelsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func MetadataEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/verity-go/metadata/generated/metadata_hash_generated.go b/verity-go/metadata/generated/metadata_hash_generated.go new file mode 100644 index 0000000..fb4f852 --- /dev/null +++ b/verity-go/metadata/generated/metadata_hash_generated.go @@ -0,0 +1,143 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package generated + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type FileHashEntry struct { + _tab flatbuffers.Table +} + +func GetRootAsFileHashEntry(buf []byte, offset flatbuffers.UOffsetT) *FileHashEntry { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &FileHashEntry{} + x.Init(buf, n+offset) + return x +} + +func FinishFileHashEntryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsFileHashEntry(buf []byte, offset flatbuffers.UOffsetT) *FileHashEntry { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &FileHashEntry{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedFileHashEntryBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *FileHashEntry) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *FileHashEntry) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *FileHashEntry) Path() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *FileHashEntry) DescriptorHash() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func FileHashEntryStart(builder *flatbuffers.Builder) { + builder.StartObject(2) +} +func FileHashEntryAddPath(builder *flatbuffers.Builder, path flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(path), 0) +} +func FileHashEntryAddDescriptorHash(builder *flatbuffers.Builder, descriptorHash flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(descriptorHash), 0) +} +func FileHashEntryEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} + +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +type MetadataHash struct { + _tab flatbuffers.Table +} + +func GetRootAsMetadataHash(buf []byte, offset flatbuffers.UOffsetT) *MetadataHash { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &MetadataHash{} + x.Init(buf, n+offset) + return x +} + +func FinishMetadataHashBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.Finish(offset) +} + +func GetSizePrefixedRootAsMetadataHash(buf []byte, offset flatbuffers.UOffsetT) *MetadataHash { + n := flatbuffers.GetUOffsetT(buf[offset+flatbuffers.SizeUint32:]) + x := &MetadataHash{} + x.Init(buf, n+offset+flatbuffers.SizeUint32) + return x +} + +func FinishSizePrefixedMetadataHashBuffer(builder *flatbuffers.Builder, offset flatbuffers.UOffsetT) { + builder.FinishSizePrefixed(offset) +} + +func (rcv *MetadataHash) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *MetadataHash) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *MetadataHash) Files(obj *FileHashEntry, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *MetadataHash) FilesLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func MetadataHashStart(builder *flatbuffers.Builder) { + builder.StartObject(1) +} +func MetadataHashAddFiles(builder *flatbuffers.Builder, files flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(files), 0) +} +func MetadataHashStartFilesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func MetadataHashEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/verity-go/metadata/interop_test.go b/verity-go/metadata/interop_test.go new file mode 100644 index 0000000..5e87c87 --- /dev/null +++ b/verity-go/metadata/interop_test.go @@ -0,0 +1,164 @@ +// verity-go/metadata/interop_test.go +package metadata + +import ( + "bytes" + "encoding/hex" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "cryptpilot-verity-go/verity" +) + +// expectedDescriptorHashes maps test file name to expected SHA-256 descriptor hash. +// Copied from verity-core/src/digest.rs tests (lines 538-551). +var expectedDescriptorHashes = map[string]string{ + "empty": "3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95", + "onebyte": "9845e616f7d2f7a1cd6742f0546a36d2e74d4eb8ae7d9bdc0b0df982c27861b7", + "oneblock": "3fd7a78101899a79cd337b1b4e5414be8bcb376b133370156ef6e65026d930ed", + "oneblockplusonebyte": "c0b9455d545b6b1ee5e7b227bd1ed463aaa530a4840dcd93465163a2b3aff0da", + "hashblock_-1_0": "c4b519068d8c8c68fd5e362fc3526c5b11e15f8eb72d4678017906f9e7f2d137", + "hashblock_-1_-1": "7aa0bb537c623562f898386ac88acd319267e4ab3200f3fd1cf648cfdb4a0379", + "hashblock_-1_1": "f804e9777f91d3697ca015303c23251ad3d80205184cfa3d1066ab28cb906330", + "hashblock_0_0": "f5c2b9ded1595acfe8a996795264d488dd6140531f6a01f8f8086a83fd835935", + "hashblock_0_-1": "5c00a54bd1d8341d7bbad060ff1b8e88ed2646d7bb38db6e752cd1cff66c0a78", + "hashblock_0_1": "a7abb76568871169a79104d00679fae6521dfdb2a2648e380c02b10e96e217ff", + "hashblock_1_0": "09510d2dbb55fa16f2768165c42d19c4da43301dfaa05705b2ecb4aaa4a5686a", + "hashblock_1_-1": "26159b4fc68c63881c25c33b23f2583ffaa64fee411af33c3b03238eea56755c", + "hashblock_1_1": "57bed0934bf3ab4610d54938f03cff27bd0d9d76c9a77e283f9fb2b7e29c5ab8", +} + +// expectedPaths is the sorted list of file names from make_testfiles.py. +var expectedPaths = []string{ + "empty", + "hashblock_-1_-1", + "hashblock_-1_0", + "hashblock_-1_1", + "hashblock_0_-1", + "hashblock_0_0", + "hashblock_0_1", + "hashblock_1_-1", + "hashblock_1_0", + "hashblock_1_1", + "oneblock", + "oneblockplusonebyte", + "onebyte", +} + +// fixtureDir returns the directory containing test fixtures. +func fixtureDir() string { + _, filename, _, _ := runtime.Caller(0) + return filepath.Join(filepath.Dir(filename), "testdata") +} + +// TestInterop_DeserializeRustMetadata loads a metadata file generated by the Rust CLI +// and verifies all fields using Go's implementation. +func TestInterop_DeserializeRustMetadata(t *testing.T) { + fbPath := filepath.Join(fixtureDir(), "rust.metadata.fb") + data, err := os.ReadFile(fbPath) + if err != nil { + if os.IsNotExist(err) { + t.Skip("fixture not found — run `make gen-interop-fixture` first") + } + t.Fatalf("read fixture: %v", err) + } + + info, err := DeserializeMetadata(data) + if err != nil { + t.Fatalf("deserialize: %v", err) + } + + // Verify file count + if len(info.FileInfos) != len(expectedPaths) { + t.Fatalf("expected %d files, got %d", len(expectedPaths), len(info.FileInfos)) + } + + // Verify sorted paths + for i, fi := range info.FileInfos { + if fi.Path != expectedPaths[i] { + t.Errorf("file[%d].path: expected %q, got %q", i, expectedPaths[i], fi.Path) + } + } + + // Verify labels + if info.Labels["env"] != "prod" { + t.Errorf("labels[env]: expected prod, got %q", info.Labels["env"]) + } + + // Verify descriptor hashes against Rust reference + for _, fi := range info.FileInfos { + expectedHash, ok := expectedDescriptorHashes[fi.Path] + if !ok { + t.Errorf("unexpected file in metadata: %s", fi.Path) + continue + } + if fi.DescriptorHash != expectedHash { + t.Errorf("%s: descriptor hash mismatch\nexpected: %s\ngot: %s", + fi.Path, expectedHash, fi.DescriptorHash) + } + if err := fi.VerifySelf(); err != nil { + t.Errorf("%s: VerifySelf failed: %v", fi.Path, err) + } + } +} + +// TestInterop_BlockLevelRecalculation re-reads the original test files from the fixture +// directory (if present) and recalculates descriptor hashes to ensure consistency. +func TestInterop_BlockLevelRecalculation(t *testing.T) { + fbPath := filepath.Join(fixtureDir(), "rust.metadata.fb") + data, err := os.ReadFile(fbPath) + if err != nil { + t.Skip("fixture not found — run `make gen-interop-fixture` first") + } + + info, err := DeserializeMetadata(data) + if err != nil { + t.Fatalf("deserialize: %v", err) + } + + // Check if verity-core/testfiles exists (only in the repo, not in CI testdata) + testfilesDir := findTestfilesDir(t) + if testfilesDir == "" { + t.Skip("testfiles directory not found — this test requires the repo source tree") + } + + for _, fi := range info.FileInfos { + filePath := filepath.Join(testfilesDir, fi.Path) + content, err := os.ReadFile(filePath) + if err != nil { + t.Fatalf("read testfile %s: %v", fi.Path, err) + } + + d := verity.NewFsVerity(verity.HashSHA256) + d.Write(content) + desc, tree := d.Finalize() + + recalcHash := hex.EncodeToString(desc.ToDescriptorHash()) + if recalcHash != fi.DescriptorHash { + t.Errorf("block-level verify failed for %s: expected %s, got %s", + fi.Path, fi.DescriptorHash, recalcHash) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("root hash mismatch for %s", fi.Path) + } + } +} + +// findTestfilesDir looks for verity-core/testfiles relative to the repo root. +func findTestfilesDir(t *testing.T) string { + out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() + if err != nil { + return "" + } + repoRoot := string(bytes.TrimSpace(out)) + dir := filepath.Join(repoRoot, "verity-core", "testfiles") + if _, err := os.Stat(dir); err == nil { + return dir + } + return "" +} diff --git a/verity-go/metadata/metadata.fbs b/verity-go/metadata/metadata.fbs new file mode 120000 index 0000000..265a2fd --- /dev/null +++ b/verity-go/metadata/metadata.fbs @@ -0,0 +1 @@ +../../cryptpilot-verity/src/metadata/metadata.fbs \ No newline at end of file diff --git a/verity-go/metadata/metadata.go b/verity-go/metadata/metadata.go new file mode 100644 index 0000000..8e868ea --- /dev/null +++ b/verity-go/metadata/metadata.go @@ -0,0 +1,278 @@ +// verity-go/metadata/metadata.go +package metadata + +import ( + "encoding/hex" + "fmt" + "sort" + + "cryptpilot-verity-go/metadata/generated" + "cryptpilot-verity-go/verity" + + flatbuffers "github.com/google/flatbuffers/go" +) + +// FileVerityInfo holds fs-verity data for a single file. +type FileVerityInfo struct { + Path string + Descriptor verity.FsVerityDescriptor + MerkleTree *verity.MerkleTree + DescriptorHash string // hex-encoded +} + +// VerifySelf checks that the descriptor hash matches and the Merkle tree is consistent. +func (f *FileVerityInfo) VerifySelf() error { + calculated := hex.EncodeToString(f.Descriptor.ToDescriptorHash()) + if calculated != f.DescriptorHash { + return &VerificationError{ + Path: f.Path, + Field: "descriptor_hash", + Expected: f.DescriptorHash, + Got: calculated, + } + } + rebuiltRoot := f.MerkleTree.RebuildRootHash(f.Descriptor.Salt, f.Descriptor.BlockSize()) + if !equalBytes(rebuiltRoot, f.Descriptor.RootHash) { + return &VerificationError{ + Path: f.Path, + Field: "root_hash", + Expected: hex.EncodeToString(f.Descriptor.RootHash), + Got: hex.EncodeToString(rebuiltRoot), + } + } + return nil +} + +// MetadataInfo holds deserialized metadata with file info and labels. +type MetadataInfo struct { + FileInfos []FileVerityInfo + Labels map[string]string +} + +// VerificationError describes an integrity check failure. +type VerificationError struct { + Path string + Field string + Expected string + Got string +} + +func (e *VerificationError) Error() string { + return fmt.Sprintf("verification failed for %s: %s mismatch, expected %s, got %s", + e.Path, e.Field, e.Expected, e.Got) +} + +// MetadataVersionError is returned when the metadata format version is unsupported. +type MetadataVersionError struct { + Version uint32 +} + +func (e *MetadataVersionError) Error() string { + return fmt.Sprintf("unsupported metadata version: %d, expected version 1", e.Version) +} + +// ParseError wraps FlatBuffers parsing failures. +type ParseError struct { + Message string +} + +func (e *ParseError) Error() string { + return fmt.Sprintf("failed to parse metadata: %s", e.Message) +} + +// SerializeMetadata serializes file info and labels to FlatBuffers bytes. +// Files are sorted by path for deterministic output. +func SerializeMetadata(fileInfos []FileVerityInfo, labels map[string]string) ([]byte, error) { + builder := flatbuffers.NewBuilder(0) + + // Sort by path for stable output + sorted := make([]*FileVerityInfo, len(fileInfos)) + for i := range fileInfos { + sorted[i] = &fileInfos[i] + } + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Path < sorted[j].Path + }) + + fileOffsets := make([]flatbuffers.UOffsetT, len(sorted)) + for i, info := range sorted { + pathOff := builder.CreateString(info.Path) + hashOff := builder.CreateString(info.DescriptorHash) + + rootHashVec := builder.CreateByteVector(info.Descriptor.RootHash) + saltVec := builder.CreateByteVector(info.Descriptor.Salt) + + generated.FsVerityDescriptorStart(builder) + generated.FsVerityDescriptorAddVersion(builder, info.Descriptor.Version) + generated.FsVerityDescriptorAddHashAlgorithm(builder, uint8(info.Descriptor.HashAlgorithm)) + generated.FsVerityDescriptorAddLogBlocksize(builder, info.Descriptor.LogBlocksize) + generated.FsVerityDescriptorAddDataSize(builder, info.Descriptor.DataSize) + generated.FsVerityDescriptorAddRootHash(builder, rootHashVec) + generated.FsVerityDescriptorAddSalt(builder, saltVec) + descOff := generated.FsVerityDescriptorEnd(builder) + + merkleVec := builder.CreateByteVector(info.MerkleTree.Level1AsBytes()) + + generated.FileInfoStart(builder) + generated.FileInfoAddPath(builder, pathOff) + generated.FileInfoAddDescriptor(builder, descOff) + generated.FileInfoAddMerkleTreeLevel1(builder, merkleVec) + generated.FileInfoAddDescriptorHash(builder, hashOff) + fileOffsets[i] = generated.FileInfoEnd(builder) + } + + filesVec := builder.CreateVectorOfTables(fileOffsets) + + // Build labels vector (sorted by key) + labelKeys := make([]string, 0, len(labels)) + for k := range labels { + labelKeys = append(labelKeys, k) + } + sort.Strings(labelKeys) + + labelOffsets := make([]flatbuffers.UOffsetT, len(labelKeys)) + for i, k := range labelKeys { + keyOff := builder.CreateString(k) + valOff := builder.CreateString(labels[k]) + generated.KeyValueStart(builder) + generated.KeyValueAddKey(builder, keyOff) + generated.KeyValueAddValue(builder, valOff) + labelOffsets[i] = generated.KeyValueEnd(builder) + } + + var labelsVec flatbuffers.UOffsetT + if len(labelOffsets) > 0 { + labelsVec = builder.CreateVectorOfTables(labelOffsets) + } + + generated.MetadataStart(builder) + generated.MetadataAddVersion(builder, 1) + generated.MetadataAddFiles(builder, filesVec) + if len(labelOffsets) > 0 { + generated.MetadataAddLabels(builder, labelsVec) + } + metadataOff := generated.MetadataEnd(builder) + + builder.Finish(metadataOff) + return builder.FinishedBytes(), nil +} + +// DeserializeMetadata reads FlatBuffers metadata and returns structured data. +func DeserializeMetadata(data []byte) (result *MetadataInfo, err error) { + defer func() { + if r := recover(); r != nil { + result = nil + err = &ParseError{Message: fmt.Sprintf("invalid flatbuffers data: %v", r)} + } + }() + + md := generated.GetRootAsMetadata(data, 0) + + version := md.Version() + if version != 1 { + return nil, &MetadataVersionError{Version: version} + } + + var fileInfos []FileVerityInfo + if filesLen := md.FilesLength(); filesLen > 0 { + fileInfos = make([]FileVerityInfo, filesLen) + var fi generated.FileInfo + for i := 0; i < filesLen; i++ { + if !md.Files(&fi, i) { + return nil, &ParseError{Message: fmt.Sprintf("missing FileInfo at index %d", i)} + } + + path := string(fi.Path()) + descriptorHash := string(fi.DescriptorHash()) + + var fbDesc generated.FsVerityDescriptor + if fi.Descriptor(&fbDesc) == nil { + return nil, &ParseError{Message: "missing descriptor for " + path} + } + + rootHash := fbDesc.RootHashBytes() + if rootHash == nil { + return nil, &ParseError{Message: "missing root_hash in descriptor for " + path} + } + + salt := fbDesc.SaltBytes() + if salt == nil { + salt = []byte{} + } + + descriptor := verity.FsVerityDescriptor{ + Version: fbDesc.Version(), + HashAlgorithm: verity.HashAlgorithm(fbDesc.HashAlgorithm()), + LogBlocksize: fbDesc.LogBlocksize(), + DataSize: fbDesc.DataSize(), + RootHash: rootHash, + Salt: salt, + } + + merkleLevel1 := fi.MerkleTreeLevel1Bytes() + if merkleLevel1 == nil { + merkleLevel1 = []byte{} + } + + digestSize := descriptor.HashAlgorithm.DigestSize() + if len(merkleLevel1)%digestSize != 0 { + return nil, &ParseError{ + Message: fmt.Sprintf("broken merkle tree for %s: level 1 length %d not a multiple of hash size %d", + path, len(merkleLevel1), digestSize), + } + } + + nHashes := len(merkleLevel1) / digestSize + hashes := make([][]byte, nHashes) + for j := 0; j < nHashes; j++ { + h := make([]byte, digestSize) + copy(h, merkleLevel1[j*digestSize:(j+1)*digestSize]) + hashes[j] = h + } + + merkleTree := verity.NewMerkleTree(hashes, descriptor.HashAlgorithm) + + fileInfos[i] = FileVerityInfo{ + Path: path, + Descriptor: descriptor, + MerkleTree: merkleTree, + DescriptorHash: descriptorHash, + } + } + } + + labels := make(map[string]string) + if labelsLen := md.LabelsLength(); labelsLen > 0 { + var kv generated.KeyValue + for i := 0; i < labelsLen; i++ { + if !md.Labels(&kv, i) { + return nil, &ParseError{Message: fmt.Sprintf("missing KeyValue at index %d", i)} + } + labels[string(kv.Key())] = string(kv.Value()) + } + } + + return &MetadataInfo{ + FileInfos: fileInfos, + Labels: labels, + }, nil +} + +// CalculateFsVerityHash computes the fs-verity descriptor hash for raw file data. +func CalculateFsVerityHash(data []byte) (verity.FsVerityDescriptor, *verity.MerkleTree) { + d := verity.NewFsVerity(verity.HashSHA256) + d.Write(data) + return d.Finalize() +} + +func equalBytes(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/verity-go/metadata/metadata_hash.fbs b/verity-go/metadata/metadata_hash.fbs new file mode 120000 index 0000000..d21b7f6 --- /dev/null +++ b/verity-go/metadata/metadata_hash.fbs @@ -0,0 +1 @@ +../../cryptpilot-verity/src/metadata/metadata_hash.fbs \ No newline at end of file diff --git a/verity-go/metadata/metadata_hash.go b/verity-go/metadata/metadata_hash.go new file mode 100644 index 0000000..6fd56dd --- /dev/null +++ b/verity-go/metadata/metadata_hash.go @@ -0,0 +1,51 @@ +// verity-go/metadata/metadata_hash.go +package metadata + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + + "cryptpilot-verity-go/metadata/generated" + + flatbuffers "github.com/google/flatbuffers/go" +) + +// CalculateMetadataHash extracts path+descriptor_hash from metadata, serializes +// to the minimal MetadataHash format, and returns its SHA-256 digest (hex-encoded). +func CalculateMetadataHash(metadataBytes []byte) (string, error) { + md := generated.GetRootAsMetadata(metadataBytes, 0) + + builder := flatbuffers.NewBuilder(0) + + filesLen := md.FilesLength() + var filesVec flatbuffers.UOffsetT + + if filesLen > 0 { + fileOffsets := make([]flatbuffers.UOffsetT, filesLen) + var fi generated.FileInfo + for i := 0; i < filesLen; i++ { + if !md.Files(&fi, i) { + return "", &ParseError{Message: fmt.Sprintf("missing FileInfo at index %d", i)} + } + pathOff := builder.CreateString(string(fi.Path())) + hashOff := builder.CreateString(string(fi.DescriptorHash())) + generated.FileHashEntryStart(builder) + generated.FileHashEntryAddPath(builder, pathOff) + generated.FileHashEntryAddDescriptorHash(builder, hashOff) + fileOffsets[i] = generated.FileHashEntryEnd(builder) + } + filesVec = builder.CreateVectorOfTables(fileOffsets) + } + + generated.MetadataHashStart(builder) + if filesLen > 0 { + generated.MetadataHashAddFiles(builder, filesVec) + } + hashOff := generated.MetadataHashEnd(builder) + builder.Finish(hashOff) + + h := sha256.New() + h.Write(builder.FinishedBytes()) + return hex.EncodeToString(h.Sum(nil)), nil +} diff --git a/verity-go/metadata/metadata_test.go b/verity-go/metadata/metadata_test.go new file mode 100644 index 0000000..ce66900 --- /dev/null +++ b/verity-go/metadata/metadata_test.go @@ -0,0 +1,207 @@ +// verity-go/metadata/metadata_test.go +package metadata + +import ( + "bytes" + "encoding/hex" + "testing" + + "cryptpilot-verity-go/verity" +) + +func makeTestFileVerityInfo(path string, data []byte) FileVerityInfo { + d := verity.NewFsVerity(verity.HashSHA256) + d.Write(data) + desc, tree := d.Finalize() + return FileVerityInfo{ + Path: path, + Descriptor: desc, + MerkleTree: tree, + DescriptorHash: hex.EncodeToString(desc.ToDescriptorHash()), + } +} + +func TestSerializeDeserializeRoundTrip(t *testing.T) { + info := makeTestFileVerityInfo("test.txt", []byte("test file content")) + info.VerifySelf() // ensure test data is valid + + labels := map[string]string{"env": "prod"} + serialized, err := SerializeMetadata([]FileVerityInfo{info}, labels) + if err != nil { + t.Fatalf("SerializeMetadata: %v", err) + } + + deserialized, err := DeserializeMetadata(serialized) + if err != nil { + t.Fatalf("DeserializeMetadata: %v", err) + } + + if len(deserialized.FileInfos) != 1 { + t.Fatalf("expected 1 file, got %d", len(deserialized.FileInfos)) + } + + if deserialized.FileInfos[0].Path != "test.txt" { + t.Errorf("path: expected test.txt, got %s", deserialized.FileInfos[0].Path) + } + if deserialized.FileInfos[0].DescriptorHash != info.DescriptorHash { + t.Errorf("descriptor hash mismatch") + } + if deserialized.Labels["env"] != "prod" { + t.Errorf("labels: expected env=prod, got %v", deserialized.Labels) + } +} + +func TestSerializeDeserializeEmptyLabels(t *testing.T) { + info := makeTestFileVerityInfo("test.txt", []byte("test file content")) + serialized, err := SerializeMetadata([]FileVerityInfo{info}, map[string]string{}) + if err != nil { + t.Fatalf("SerializeMetadata: %v", err) + } + + deserialized, err := DeserializeMetadata(serialized) + if err != nil { + t.Fatalf("DeserializeMetadata: %v", err) + } + + if len(deserialized.Labels) != 0 { + t.Errorf("expected empty labels, got %v", deserialized.Labels) + } +} + +func TestSerializeDeserializeMultipleFiles(t *testing.T) { + infos := []FileVerityInfo{ + makeTestFileVerityInfo("b.txt", []byte("content b")), + makeTestFileVerityInfo("a.txt", []byte("content a")), + makeTestFileVerityInfo("c.txt", []byte("content c")), + } + labels := map[string]string{"key1": "val1", "key2": "val2"} + + serialized, err := SerializeMetadata(infos, labels) + if err != nil { + t.Fatalf("SerializeMetadata: %v", err) + } + + deserialized, err := DeserializeMetadata(serialized) + if err != nil { + t.Fatalf("DeserializeMetadata: %v", err) + } + + if len(deserialized.FileInfos) != 3 { + t.Fatalf("expected 3 files, got %d", len(deserialized.FileInfos)) + } + + // Verify sorted order by path + paths := []string{"a.txt", "b.txt", "c.txt"} + for i, fi := range deserialized.FileInfos { + if fi.Path != paths[i] { + t.Errorf("file[%d]: expected path %s, got %s", i, paths[i], fi.Path) + } + } +} + +func TestVerifySelf(t *testing.T) { + info := makeTestFileVerityInfo("test.txt", []byte("test file content")) + + err := info.VerifySelf() + if err != nil { + t.Fatalf("VerifySelf: %v", err) + } + + // Tamper with descriptor hash + tampered := info + tampered.DescriptorHash = "deadbeef" + err = tampered.VerifySelf() + if err == nil { + t.Fatal("VerifySelf should fail with tampered descriptor hash") + } + if _, ok := err.(*VerificationError); !ok { + t.Errorf("expected VerificationError, got %T", err) + } +} + +func TestVerifySelfRootHash(t *testing.T) { + info := makeTestFileVerityInfo("test.txt", []byte("test file content")) + + // Tamper with root hash in descriptor + tampered := info + tampered.Descriptor.RootHash = bytes.Repeat([]byte{0xFF}, 32) + err := tampered.VerifySelf() + if err == nil { + t.Fatal("VerifySelf should fail with tampered root hash") + } +} + +func TestMetadataVersionError(t *testing.T) { + // The serialized data has version 1. If we manually tamper with it... + // We can't easily tamper with FlatBuffers version field without low-level manipulation, + // so just test the error type string representation. + err := &MetadataVersionError{Version: 2} + expected := "unsupported metadata version: 2, expected version 1" + if err.Error() != expected { + t.Errorf("error string: expected %q, got %q", expected, err.Error()) + } +} + +func TestMetadataHashDeterminism(t *testing.T) { + infos := []FileVerityInfo{ + makeTestFileVerityInfo("b.txt", []byte("content b")), + makeTestFileVerityInfo("a.txt", []byte("content a")), + } + + serialized, _ := SerializeMetadata(infos, map[string]string{"label": "value"}) + + hash1, err := CalculateMetadataHash(serialized) + if err != nil { + t.Fatalf("CalculateMetadataHash: %v", err) + } + + // Deserialize and re-serialize to verify hash is deterministic + deserialized, _ := DeserializeMetadata(serialized) + serialized2, _ := SerializeMetadata(deserialized.FileInfos, deserialized.Labels) + + hash2, err := CalculateMetadataHash(serialized2) + if err != nil { + t.Fatalf("CalculateMetadataHash (2): %v", err) + } + + if hash1 != hash2 { + t.Errorf("metadata hash not deterministic: first=%s second=%s", hash1, hash2) + } +} + +func TestMetadataHashEmpty(t *testing.T) { + // Empty metadata: no files + serialized, err := SerializeMetadata([]FileVerityInfo{}, map[string]string{}) + if err != nil { + t.Fatalf("SerializeMetadata: %v", err) + } + + hash, err := CalculateMetadataHash(serialized) + if err != nil { + t.Fatalf("CalculateMetadataHash: %v", err) + } + if len(hash) != 64 { // SHA-256 hex = 64 chars + t.Errorf("hash length: expected 64, got %d", len(hash)) + } +} + +func TestDeserializeError(t *testing.T) { + _, err := DeserializeMetadata([]byte("not valid flatbuffers")) + if err == nil { + t.Fatal("expected error from invalid data") + } + if _, ok := err.(*ParseError); !ok { + t.Errorf("expected ParseError, got %T", err) + } +} + +func TestCalculateFsVerityHash(t *testing.T) { + desc, tree := CalculateFsVerityHash([]byte("test")) + + if desc.HashAlgorithm != verity.HashSHA256 { + t.Errorf("expected SHA256 algorithm") + } + if tree == nil { + t.Fatal("tree should not be nil") + } +} diff --git a/verity-go/metadata/testdata/rust.metadata.fb b/verity-go/metadata/testdata/rust.metadata.fb new file mode 100644 index 0000000..e6cbf7a Binary files /dev/null and b/verity-go/metadata/testdata/rust.metadata.fb differ diff --git a/verity-go/verity/digest.go b/verity-go/verity/digest.go new file mode 100644 index 0000000..b7542ca --- /dev/null +++ b/verity-go/verity/digest.go @@ -0,0 +1,226 @@ +// verity-go/verity/digest.go +package verity + +import "hash" + +// fixedSizeBlock tracks a hash being filled to block_size bytes. +type fixedSizeBlock struct { + h hash.Hash + remaining int + algo HashAlgorithm + salt []byte + blockSize int +} + +func newFixedSizeBlock(algo HashAlgorithm, salt []byte, blockSize int) fixedSizeBlock { + return fixedSizeBlock{ + h: newSaltedHash(algo, salt), + remaining: blockSize, + algo: algo, + salt: salt, + blockSize: blockSize, + } +} + +// newSaltedHash creates a hash.Hash initialized with the salt. +func newSaltedHash(algo HashAlgorithm, salt []byte) hash.Hash { + h := algo.newHash() + blockSize := algo.BlockSize() + for i := 0; i < len(salt); i += blockSize { + end := i + blockSize + if end > len(salt) { + end = len(salt) + } + h.Write(salt[i:end]) + padding := blockSize - (end - i) + if padding < blockSize { + h.Write(make([]byte, padding)) + } + } + return h +} + +// append writes data to the block. +func (b *fixedSizeBlock) append(data []byte) { + b.h.Write(data) + b.remaining -= len(data) +} + +// fillToEnd pads with zeros to block_size and returns the hash. +func (b *fixedSizeBlock) fillToEnd() []byte { + if b.remaining > 0 { + b.h.Write(make([]byte, b.remaining)) + b.remaining = 0 + } + return b.h.Sum(nil) +} + +// overflowingAppend appends as much as possible, returning the overflow. +func (b *fixedSizeBlock) overflowingAppend(data []byte) []byte { + n := b.remaining + if len(data) < n { + n = len(data) + } + b.append(data[:n]) + return data[n:] +} + +// finalizeAndReset computes the hash and resets to a fresh salted state. +func (b *fixedSizeBlock) finalizeAndReset() []byte { + out := b.fillToEnd() + b.h = newSaltedHash(b.algo, b.salt) + b.remaining = b.blockSize + return out +} + +// FsVerityConfig holds the parameters for fs-verity hashing. +type FsVerityConfig struct { + blockSize int + salt []byte + algo HashAlgorithm +} + +// FsVerityDigest is a streaming fs-verity hasher. +type FsVerityDigest struct { + config FsVerityConfig + levels []fixedSizeBlock + merkleTree *MerkleTree +} + +// NewFsVerity creates a new fs-verity hasher with empty salt. +func NewFsVerity(algo HashAlgorithm) *FsVerityDigest { + return NewFsVerityWithSaltAndBlockSize(algo, nil, DefaultBlockSize) +} + +// NewFsVerityWithSalt creates a new fs-verity hasher with the given salt. +func NewFsVerityWithSalt(algo HashAlgorithm, salt []byte) *FsVerityDigest { + return NewFsVerityWithSaltAndBlockSize(algo, salt, DefaultBlockSize) +} + +// NewFsVerityWithSaltAndBlockSize creates a new fs-verity hasher with custom parameters. +func NewFsVerityWithSaltAndBlockSize(algo HashAlgorithm, salt []byte, blockSize int) *FsVerityDigest { + return &FsVerityDigest{ + config: FsVerityConfig{ + blockSize: blockSize, + salt: salt, + algo: algo, + }, + levels: make([]fixedSizeBlock, 0), + merkleTree: &MerkleTree{algo: algo}, + } +} + +// Write implements io.Writer. +func (d *FsVerityDigest) Write(data []byte) (int, error) { + d.update(data) + return len(data), nil +} + +// update processes data in block_size chunks, matching the Rust implementation. +func (d *FsVerityDigest) update(data []byte) { + digestSize := d.config.algo.DigestSize() + + for chunkStart := 0; chunkStart < len(data); chunkStart += d.config.blockSize { + chunkEnd := chunkStart + d.config.blockSize + if chunkEnd > len(data) { + chunkEnd = len(data) + } + overflow := data[chunkStart:chunkEnd] + + keepSpace := false + for levelIdx := range d.levels { + level := &d.levels[levelIdx] + overflow = level.overflowingAppend(overflow) + + if keepSpace { + if level.remaining >= digestSize { + if len(overflow) == 0 { + break + } + // overflow but not enough room — finalize this level + } + } else { + if len(overflow) == 0 { + break + } + } + + hash := level.finalizeAndReset() + if levelIdx == 0 { + d.merkleTree.level1 = append(d.merkleTree.level1, hash) + } + // Append the overflow to the freshly reset level (matches Rust behavior) + level.append(overflow) + overflow = hash + keepSpace = true + } + + if len(overflow) > 0 { + level := newFixedSizeBlock(d.config.algo, d.config.salt, d.config.blockSize) + level.append(overflow) + d.levels = append(d.levels, level) + } + } +} + +// Finalize flushes all levels and returns the descriptor and merkle tree. +func (d *FsVerityDigest) Finalize() (FsVerityDescriptor, *MerkleTree) { + digestSize := d.config.algo.DigestSize() + compressionFactor := d.config.blockSize / digestSize + + var totalSize int + scale := 1 + + // Flush all levels from bottom to top + var lastHash []byte + overflow := lastHash // initially empty (zeros) + + for levelIdx, level := range d.levels { + totalSize += scale * (d.config.blockSize - level.remaining) + level.append(overflow) + lastHash = level.fillToEnd() + if levelIdx == 0 { + d.merkleTree.level1 = append(d.merkleTree.level1, lastHash) + } + overflow = lastHash + scale *= compressionFactor + } + + if lastHash == nil { + // Empty file: root hash is all zeros + lastHash = make([]byte, digestSize) + } + + desc := FsVerityDescriptor{ + Version: 1, + HashAlgorithm: d.config.algo, + LogBlocksize: uint8(trailingZeros(uint(d.config.blockSize))), + DataSize: uint64(totalSize), + RootHash: lastHash, + Salt: append([]byte(nil), d.config.salt...), + } + + // Return merkleTree reference for caller + tree := d.merkleTree + d.merkleTree = &MerkleTree{algo: d.config.algo} + + return desc, tree +} + +// InnerHashAlgorithm returns the hash algorithm in use. +func (d *FsVerityDigest) InnerHashAlgorithm() HashAlgorithm { + return d.config.algo +} + +// trailingZeros counts trailing zeros in an unsigned integer. +func trailingZeros(n uint) int { + if n == 0 { + return 0 + } + count := 0 + for n&1 == 0 { + count++ + n >>= 1 + } + return count +} diff --git a/verity-go/verity/golden_test.go b/verity-go/verity/golden_test.go new file mode 100644 index 0000000..dc82a4f --- /dev/null +++ b/verity-go/verity/golden_test.go @@ -0,0 +1,82 @@ +// verity-go/verity/golden_test.go +package verity + +import ( + "bytes" + "encoding/hex" + "os/exec" + "path/filepath" + "testing" +) + +// expectedDescriptorHashes maps test file name to expected SHA-256 descriptor hash. +// These values come from verity-core/src/digest.rs tests (lines 538-551), +// which are the authoritative reference implementation. +var expectedDescriptorHashes = map[string]string{ + "empty": "3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95", + "onebyte": "9845e616f7d2f7a1cd6742f0546a36d2e74d4eb8ae7d9bdc0b0df982c27861b7", + "oneblock": "3fd7a78101899a79cd337b1b4e5414be8bcb376b133370156ef6e65026d930ed", + "oneblockplusonebyte": "c0b9455d545b6b1ee5e7b227bd1ed463aaa530a4840dcd93465163a2b3aff0da", + "hashblock_-1_0": "c4b519068d8c8c68fd5e362fc3526c5b11e15f8eb72d4678017906f9e7f2d137", + "hashblock_-1_-1": "7aa0bb537c623562f898386ac88acd319267e4ab3200f3fd1cf648cfdb4a0379", + "hashblock_-1_1": "f804e9777f91d3697ca015303c23251ad3d80205184cfa3d1066ab28cb906330", + "hashblock_0_0": "f5c2b9ded1595acfe8a996795264d488dd6140531f6a01f8f8086a83fd835935", + "hashblock_0_-1": "5c00a54bd1d8341d7bbad060ff1b8e88ed2646d7bb38db6e752cd1cff66c0a78", + "hashblock_0_1": "a7abb76568871169a79104d00679fae6521dfdb2a2648e380c02b10e96e217ff", + "hashblock_1_0": "09510d2dbb55fa16f2768165c42d19c4da43301dfaa05705b2ecb4aaa4a5686a", + "hashblock_1_-1": "26159b4fc68c63881c25c33b23f2583ffaa64fee411af33c3b03238eea56755c", + "hashblock_1_1": "57bed0934bf3ab4610d54938f03cff27bd0d9d76c9a77e283f9fb2b7e29c5ab8", +} + +func TestVerityAgainstRustReference(t *testing.T) { + // Find repo root (verity-go is a subdirectory of the repo root) + repoRoot, err := findRepoRoot() + if err != nil { + t.Skipf("cannot find repo root: %v", err) + } + + // Run make_testfiles.py to generate testfiles/ + script := filepath.Join(repoRoot, "verity-core", "make_testfiles.py") + cmd := exec.Command("python3", script) + cmd.Dir = repoRoot + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("make_testfiles.py failed: %v\n%s", err, out) + } + + testfilesDir := filepath.Join(repoRoot, "verity-core", "testfiles") + + for name, expectedHash := range expectedDescriptorHashes { + t.Run(name, func(t *testing.T) { + data, err := readFile(filepath.Join(testfilesDir, name)) + if err != nil { + t.Fatalf("read testfile: %v", err) + } + + d := NewFsVerity(HashSHA256) + d.Write(data) + desc, tree := d.Finalize() + + actualHash := hex.EncodeToString(desc.ToDescriptorHash()) + if actualHash != expectedHash { + t.Errorf("descriptor hash mismatch\nexpected: %s\ngot: %s", expectedHash, actualHash) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !bytes.Equal(rebuiltRoot, desc.RootHash) { + t.Errorf("root hash mismatch after rebuild") + } + }) + } +} + +func findRepoRoot() (string, error) { + out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput() + if err != nil { + return "", err + } + return string(bytes.TrimSpace(out)), nil +} + +func readFile(path string) ([]byte, error) { + return exec.Command("cat", path).Output() +} diff --git a/verity-go/verity/tree.go b/verity-go/verity/tree.go new file mode 100644 index 0000000..07671a6 --- /dev/null +++ b/verity-go/verity/tree.go @@ -0,0 +1,85 @@ +// verity-go/verity/tree.go +package verity + +// MerkleTree stores level-1 hashes (one per data block). +type MerkleTree struct { + level1 [][]byte + algo HashAlgorithm +} + +// NewMerkleTree creates a tree from level-1 hashes. +func NewMerkleTree(hashes [][]byte, algo HashAlgorithm) *MerkleTree { + return &MerkleTree{level1: hashes, algo: algo} +} + +// Level1AsBytes returns the level-1 hashes as concatenated bytes. +func (t *MerkleTree) Level1AsBytes() []byte { + digestSize := t.algo.DigestSize() + out := make([]byte, 0, len(t.level1)*digestSize) + for _, h := range t.level1 { + out = append(out, h...) + } + return out +} + +// RebuildRootHash reconstructs the root hash from level-1 hashes. +// Used when loading a tree from serialized metadata. +// When there are more level-1 hashes than fit in one block, this builds +// intermediate tree levels using the full FsVerityDigest pipeline. +func (t *MerkleTree) RebuildRootHash(salt []byte, blockSize int) []byte { + if len(t.level1) == 0 { + // Empty file: root hash is all zeros + return make([]byte, t.algo.DigestSize()) + } + if len(t.level1) == 1 { + return t.level1[0] + } + + // Use a full FsVerityDigest to process the level-1 hashes, + // matching the Rust implementation's rebuild_root_hash. + d := NewFsVerityWithSaltAndBlockSize(t.algo, salt, blockSize) + for _, hash := range t.level1 { + d.Write(hash) + } + // Pad to block boundary + totalBytes := len(t.level1) * t.algo.DigestSize() + padding := (blockSize - totalBytes%blockSize) % blockSize + if padding > 0 { + d.Write(make([]byte, padding)) + } + desc, _ := d.Finalize() + return desc.RootHash +} + +// VerifyDataBlock verifies a single data block against the merkle tree. +func (t *MerkleTree) VerifyDataBlock(blockIndex int, blockSize int, data []byte) bool { + if len(data) > blockSize { + return false + } + if blockIndex >= len(t.level1) { + return false + } + expected := t.level1[blockIndex] + + h := t.algo.newHash() + h.Write(data) + // Pad to block size + if len(data) < blockSize { + h.Write(make([]byte, blockSize-len(data))) + } + actual := h.Sum(nil) + + return equalBytes(actual, expected) +} + +func equalBytes(a, b []byte) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/verity-go/verity/verity.go b/verity-go/verity/verity.go new file mode 100644 index 0000000..a6a34b9 --- /dev/null +++ b/verity-go/verity/verity.go @@ -0,0 +1,139 @@ +// verity-go/verity/verity.go +package verity + +import ( + "crypto/sha256" + "crypto/sha512" + "hash" +) + +const ( + MaxDigestSize = 64 + MaxSaltSize = 32 + MaxLevels = 8 // FS_VERITY_MAX_LEVELS + DefaultBlockSize = 4096 +) + +// HashAlgorithm matches kernel FS_VERITY_HASH_ALG_* values. +type HashAlgorithm uint8 + +const ( + HashSHA256 HashAlgorithm = 1 + HashSHA512 HashAlgorithm = 2 +) + +func (h HashAlgorithm) String() string { + switch h { + case HashSHA256: + return "sha256" + case HashSHA512: + return "sha512" + default: + return "unknown" + } +} + +func (h HashAlgorithm) DigestSize() int { + switch h { + case HashSHA256: + return sha256.Size + case HashSHA512: + return sha512.Size + default: + return 0 + } +} + +func (h HashAlgorithm) BlockSize() int { + switch h { + case HashSHA256: + return sha256.BlockSize + case HashSHA512: + return sha512.BlockSize + default: + return 0 + } +} + +func (h HashAlgorithm) newHash() hash.Hash { + switch h { + case HashSHA256: + return sha256.New() + case HashSHA512: + return sha512.New() + default: + return nil + } +} + +// FsVerityDescriptor is a kernel-compatible fs-verity descriptor. +type FsVerityDescriptor struct { + Version uint8 + HashAlgorithm HashAlgorithm + LogBlocksize uint8 + DataSize uint64 + RootHash []byte // len = digestSize (32 for SHA-256, 64 for SHA-512) + Salt []byte // 0..32 bytes +} + +// BlockSize returns the Merkle tree block size (1 << LogBlocksize). +func (d *FsVerityDescriptor) BlockSize() int { + return 1 << d.LogBlocksize +} + +// ToDescriptorHash computes the salted hash of the descriptor (kernel format). +// See: https://www.kernel.org/doc/html/latest/filesystems/fsverity.html#fs-verity-descriptor +func (d *FsVerityDescriptor) ToDescriptorHash() []byte { + h := saltToDigest(d.HashAlgorithm, d.Salt) + h.Write([]byte{d.Version}) + h.Write([]byte{uint8(d.HashAlgorithm)}) + h.Write([]byte{d.LogBlocksize}) + h.Write([]byte{uint8(len(d.Salt))}) + h.Write(make([]byte, 4)) // __reserved_0x04 + h.Write(d.DataSizeToBytes()) + hashPadded(h, d.RootHash, 64) + hashPadded(h, d.Salt, 32) + h.Write(make([]byte, 144)) // __reserved[144] + return h.Sum(nil) +} + +// DataSizeToBytes returns data_size as little-endian bytes. +func (d *FsVerityDescriptor) DataSizeToBytes() []byte { + b := make([]byte, 8) + b[0] = byte(d.DataSize) + b[1] = byte(d.DataSize >> 8) + b[2] = byte(d.DataSize >> 16) + b[3] = byte(d.DataSize >> 24) + b[4] = byte(d.DataSize >> 32) + b[5] = byte(d.DataSize >> 40) + b[6] = byte(d.DataSize >> 48) + b[7] = byte(d.DataSize >> 56) + return b +} + +// saltToDigest creates a hash.Hash initialized with the salt padded to block boundaries. +func saltToDigest(algo HashAlgorithm, salt []byte) hash.Hash { + h := algo.newHash() + blockSize := algo.BlockSize() + for i := 0; i < len(salt); i += blockSize { + end := i + blockSize + if end > len(salt) { + end = len(salt) + } + h.Write(salt[i:end]) + // Pad this chunk to block size + padding := blockSize - (end - i) + if padding < blockSize { + h.Write(make([]byte, padding)) + } + } + return h +} + +// hashPadded writes data to the hash, padding with zeros to the target size. +func hashPadded(h hash.Hash, data []byte, targetSize int) { + h.Write(data) + if len(data) < targetSize { + h.Write(make([]byte, targetSize-len(data))) + } +} diff --git a/verity-go/verity/verity_test.go b/verity-go/verity/verity_test.go new file mode 100644 index 0000000..a54adf6 --- /dev/null +++ b/verity-go/verity/verity_test.go @@ -0,0 +1,145 @@ +// verity-go/verity/verity_test.go +package verity + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" +) + +func TestEmptyFile(t *testing.T) { + d := NewFsVerity(HashSHA256) + desc, tree := d.Finalize() + + // sha256:3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95 + expected := "3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95" + actual := hex.EncodeToString(desc.ToDescriptorHash()) + if actual != expected { + t.Errorf("empty: expected %s, got %s", expected, actual) + } + + // Root hash should be all zeros for empty file + expectedRoot := make([]byte, 32) + if !equalBytes(desc.RootHash, expectedRoot) { + t.Errorf("empty: root hash should be all zeros, got %s", hex.EncodeToString(desc.RootHash)) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("empty: root hash mismatch: desc=%s rebuilt=%s", + hex.EncodeToString(desc.RootHash), hex.EncodeToString(rebuiltRoot)) + } +} + +func TestOneByte(t *testing.T) { + d := NewFsVerity(HashSHA256) + d.Write([]byte{'A'}) // matches Python: b'A' + desc, tree := d.Finalize() + + expected := "9845e616f7d2f7a1cd6742f0546a36d2e74d4eb8ae7d9bdc0b0df982c27861b7" + actual := hex.EncodeToString(desc.ToDescriptorHash()) + if actual != expected { + t.Errorf("onebyte: expected %s, got %s", expected, actual) + } + + // Verify root hash + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("onebyte: root hash mismatch") + } + + // Verify data block + block := make([]byte, desc.BlockSize()) + block[0] = 'A' + if !tree.VerifyDataBlock(0, desc.BlockSize(), block[:1]) { + t.Errorf("onebyte: data block verification failed") + } +} + +func TestOneBlock(t *testing.T) { + // Exactly 4096 bytes of 'A' + data := make([]byte, DefaultBlockSize) + for i := range data { + data[i] = 'A' + } + d := NewFsVerity(HashSHA256) + d.Write(data) + desc, tree := d.Finalize() + + expected := "3fd7a78101899a79cd337b1b4e5414be8bcb376b133370156ef6e65026d930ed" + actual := hex.EncodeToString(desc.ToDescriptorHash()) + if actual != expected { + t.Errorf("oneblock: expected %s, got %s", expected, actual) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("oneblock: root hash mismatch") + } +} + +func TestOneBlockPlusOneByte(t *testing.T) { + // 4096 bytes of 'A' + 1 byte 'B' + data := make([]byte, DefaultBlockSize+1) + for i := 0; i < DefaultBlockSize; i++ { + data[i] = 'A' + } + data[DefaultBlockSize] = 'B' + d := NewFsVerity(HashSHA256) + d.Write(data) + desc, tree := d.Finalize() + + expected := "c0b9455d545b6b1ee5e7b227bd1ed463aaa530a4840dcd93465163a2b3aff0da" + actual := hex.EncodeToString(desc.ToDescriptorHash()) + if actual != expected { + t.Errorf("oneblockplusonebyte: expected %s, got %s", expected, actual) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("oneblockplusonebyte: root hash mismatch") + } +} + +// hashblock test cases: block_size * (hashes_per_block + i) + j bytes of 'A' +// hashes_per_block = 4096 / 32 = 128 +func TestHashblock(t *testing.T) { + hashesPerBlock := DefaultBlockSize / sha256.Size // 128 + tests := []struct { + i, j int + expected string + }{ + {0, 0, "f5c2b9ded1595acfe8a996795264d488dd6140531f6a01f8f8086a83fd835935"}, + {0, -1, "5c00a54bd1d8341d7bbad060ff1b8e88ed2646d7bb38db6e752cd1cff66c0a78"}, + {0, 1, "a7abb76568871169a79104d00679fae6521dfdb2a2648e380c02b10e96e217ff"}, + {-1, 0, "c4b519068d8c8c68fd5e362fc3526c5b11e15f8eb72d4678017906f9e7f2d137"}, + {1, 0, "09510d2dbb55fa16f2768165c42d19c4da43301dfaa05705b2ecb4aaa4a5686a"}, + {-1, -1, "7aa0bb537c623562f898386ac88acd319267e4ab3200f3fd1cf648cfdb4a0379"}, + {-1, 1, "f804e9777f91d3697ca015303c23251ad3d80205184cfa3d1066ab28cb906330"}, + {1, -1, "26159b4fc68c63881c25c33b23f2583ffaa64fee411af33c3b03238eea56755c"}, + {1, 1, "57bed0934bf3ab4610d54938f03cff27bd0d9d76c9a77e283f9fb2b7e29c5ab8"}, + } + for _, tc := range tests { + name := fmt.Sprintf("hashblock_%d_%d", tc.i, tc.j) + size := DefaultBlockSize*(hashesPerBlock+tc.i) + tc.j + if size < 0 { + size = 0 + } + data := bytes.Repeat([]byte{'A'}, size) + d := NewFsVerity(HashSHA256) + d.Write(data) + desc, tree := d.Finalize() + + actual := hex.EncodeToString(desc.ToDescriptorHash()) + if actual != tc.expected { + t.Errorf("%s: expected %s, got %s", name, tc.expected, actual) + } + + rebuiltRoot := tree.RebuildRootHash(desc.Salt, desc.BlockSize()) + if !equalBytes(rebuiltRoot, desc.RootHash) { + t.Errorf("%s: root hash mismatch", name) + } + } +}