From ab124104c45b35f9db60667bc32a74ace55c2594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20Go=C5=82awski?= Date: Thu, 21 Dec 2023 16:11:36 +0100 Subject: [PATCH] Initial commit --- .cargo/README.md | 47 +++ .github/workflows/rust.yml | 141 +++++++++ .gitignore | 3 + CHANGELOG.md | 14 + Cargo.toml | 31 ++ LICENSE | 21 ++ README.md | 47 +++ rustfmt.toml | 17 ++ src/lib.rs | 605 +++++++++++++++++++++++++++++++++++++ src/reader.rs | 70 +++++ src/writer.rs | 62 ++++ tests/sha2_384.rs | 339 +++++++++++++++++++++ 12 files changed, 1397 insertions(+) create mode 100644 .cargo/README.md create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 rustfmt.toml create mode 100644 src/lib.rs create mode 100644 src/reader.rs create mode 100644 src/writer.rs create mode 100644 tests/sha2_384.rs diff --git a/.cargo/README.md b/.cargo/README.md new file mode 100644 index 0000000..2747187 --- /dev/null +++ b/.cargo/README.md @@ -0,0 +1,47 @@ +# chksum-sha2-384 + +[![GitHub](https://img.shields.io/badge/github-chksum--rs%2Fsha2--384-24292e?style=flat-square&logo=github "GitHub")](https://github.com/chksum-rs/sha2-384) +[![Build](https://img.shields.io/github/actions/workflow/status/chksum-rs/sha2-384/rust.yml?branch=master&style=flat-square&logo=github "Build")](https://github.com/chksum-rs/sha2-384/actions/workflows/rust.yml) +[![docs.rs](https://img.shields.io/docsrs/chksum-sha2-384?style=flat-square&logo=docsdotrs "docs.rs")](https://docs.rs/chksum-sha2-384/) +[![MSRV](https://img.shields.io/badge/MSRV-1.70.0-informational?style=flat-square "MSRV")](https://github.com/chksum-rs/sha2-384/blob/master/Cargo.toml) +[![deps.rs](https://deps.rs/crate/chksum-sha2-384/0.0.0/status.svg?style=flat-square "deps.rs")](https://deps.rs/crate/chksum-sha2-384/0.0.0) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg?style=flat-square "unsafe forbidden")](https://github.com/rust-secure-code/safety-dance) +[![LICENSE](https://img.shields.io/github/license/chksum-rs/sha2-384?style=flat-square "LICENSE")](https://github.com/chksum-rs/sha2-384/blob/master/LICENSE) + +An implementation of the SHA-2 384 hash function with a straightforward interface for computing digests of bytes, files, directories, and more. + +## Setup + +To use this crate, add the following entry to your `Cargo.toml` file in the `dependencies` section: + +```toml +[dependencies] +chksum-sha2-384 = "0.0.0" +``` + +Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: + +```shell +cargo add chksum-sha2-384 +``` + +## Usage + +Use the `chksum` function to calcualate digest of file, directory and so on. + +```rust +use chksum_sha2_384 as sha2_384; + +let file = File::open(path)?; +let digest = sha2_384::chksum(file)?; +assert_eq!( + digest.to_hex_lowercase(), + "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +); +``` + +For more usage examples, refer to the documentation available at [docs.rs](https://docs.rs/chksum-sha2-384/). + +## License + +This crate is licensed under the MIT License. diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..1b8e198 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,141 @@ +name: Rust + +env: + CARGO_TERM_COLOR: always + +permissions: + contents: read + +on: + push: + branches: + - master + paths: + - ".github/workflows/*.yml" + - "Cargo.toml" + - "src/**.rs" + - "tests/**.rs" + pull_request: + branches: + - master + paths: + - ".github/workflows/*.yml" + - "Cargo.toml" + - "src/**.rs" + - "tests/**.rs" + +jobs: + lint: + runs-on: ubuntu-latest + name: Lint + permissions: + checks: write + contents: write + pull-requests: write + steps: + - name: Repository checkout + uses: actions/checkout@v3 + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + default: true + profile: minimal + components: rustfmt, clippy + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --check --verbose + - name: Run cargo clippy + uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features -- --deny clippy::cargo + + build-and-test-linux: + needs: + - lint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + toolchain: [1.70.0, stable, nightly] + name: "Build and test (OS: Linux, Toolchain: ${{ matrix.toolchain }})" + steps: + - name: Repository checkout + uses: actions/checkout@v3 + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + default: true + profile: minimal + - name: Run cargo build + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features --verbose + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --verbose + + build-and-test-macos: + needs: + - lint + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + toolchain: [1.70.0, stable, nightly] + name: "Build and test (OS: MacOS, Toolchain: ${{ matrix.toolchain }})" + steps: + - name: Repository checkout + uses: actions/checkout@v3 + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + default: true + profile: minimal + - name: Run cargo build + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features --verbose + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --verbose + + build-and-test-windows: + needs: + - lint + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + toolchain: [1.70.0, stable, nightly] + name: "Build and test (OS: Windows, Toolchain: ${{ matrix.toolchain }})" + steps: + - name: Repository checkout + uses: actions/checkout@v3 + - name: Setup Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + default: true + profile: minimal + - name: Run cargo build + uses: actions-rs/cargo@v1 + with: + command: build + args: --all-features --verbose + - name: Run cargo test + uses: actions-rs/cargo@v1 + with: + command: test + args: --all-features --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e725f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Cargo +Cargo.lock +target/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2b0ad66 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.0.0] - 2023-12-21 + +### Added + +- Initial release. + +[0.0.0]: https://github.com/chksum-rs/sha2-384/releases/tag/v0.0.0 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7716d12 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "chksum-sha2-384" +version = "0.0.0" +authors = ["Konrad Goławski "] +edition = "2021" +rust-version = "1.70.0" +description = "An implementation of the SHA-2 384 hash function with a straightforward interface for computing digests of bytes, files, directories, and more." +readme = ".cargo/README.md" +repository = "https://github.com/chksum-rs/sha2-384" +license = "MIT" +keywords = ["checksum", "digest", "hash", "sha384", "sha2-384"] +categories = ["algorithms", "cryptography", "filesystem"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +chksum-core = "0.0.0" +chksum-hash-sha2-384 = "0.0.0" +chksum-reader = { version = "0.0.0", optional = true } +chksum-writer = { version = "0.0.0", optional = true } + +[dev-dependencies] +assert_fs = { version = "1.0.13", features = ["color-auto"] } +thiserror = "1.0.51" + +[features] +default = [] +reader = ["chksum-reader"] +writer = ["chksum-writer"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..10c2349 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Konrad Goławski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b48648 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# chksum-sha2-384 + +[![crates.io](https://img.shields.io/crates/v/chksum-sha2-384?style=flat-square&logo=rust "crates.io")](https://crates.io/crates/chksum-sha2-384) +[![Build](https://img.shields.io/github/actions/workflow/status/chksum-rs/sha2-384/rust.yml?branch=master&style=flat-square&logo=github "Build")](https://github.com/chksum-rs/sha2-384/actions/workflows/rust.yml) +[![docs.rs](https://img.shields.io/docsrs/chksum-sha2-384?style=flat-square&logo=docsdotrs "docs.rs")](https://docs.rs/chksum-sha2-384/) +[![MSRV](https://img.shields.io/badge/MSRV-1.70.0-informational?style=flat-square "MSRV")](https://github.com/chksum-rs/sha2-384/blob/master/Cargo.toml) +[![deps.rs](https://deps.rs/crate/chksum-sha2-384/0.0.0/status.svg?style=flat-square "deps.rs")](https://deps.rs/crate/chksum-sha2-384/0.0.0) +[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg?style=flat-square "unsafe forbidden")](https://github.com/rust-secure-code/safety-dance) +[![LICENSE](https://img.shields.io/github/license/chksum-rs/sha2-384?style=flat-square "LICENSE")](https://github.com/chksum-rs/sha2-384/blob/master/LICENSE) + +An implementation of the SHA-2 384 hash function with a straightforward interface for computing digests of bytes, files, directories, and more. + +## Setup + +To use this crate, add the following entry to your `Cargo.toml` file in the `dependencies` section: + +```toml +[dependencies] +chksum-sha2-384 = "0.0.0" +``` + +Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: + +```shell +cargo add chksum-sha2-384 +``` + +## Usage + +Use the `chksum` function to calcualate digest of file, directory and so on. + +```rust +use chksum_sha2_384 as sha2_384; + +let file = File::open(path)?; +let digest = sha2_384::chksum(file)?; +assert_eq!( + digest.to_hex_lowercase(), + "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +); +``` + +For more usage examples, refer to the documentation available at [docs.rs](https://docs.rs/chksum-sha2-384/). + +## License + +This crate is licensed under the MIT License. diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7a65644 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,17 @@ +combine_control_expr = false +edition = "2021" +force_multiline_blocks = true +format_code_in_doc_comments = true +format_generated_files = true +format_strings = true +group_imports = "StdExternalCrate" +hex_literal_case = "Upper" +imports_granularity = "Module" +imports_layout = "HorizontalVertical" +match_block_trailing_comma = true +max_width = 120 +normalize_comments = true +normalize_doc_attributes = true +reorder_impl_items = true +use_field_init_shorthand = true +use_try_shorthand = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b8a57ee --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,605 @@ +//! This crate provides an implementation of the SHA-2 384 hash function with a straightforward interface for computing digests of bytes, files, directories, and more. +//! +//! For a low-level interface, you can explore the [`chksum_hash_sha2_384`] crate. +//! +//! # Setup +//! +//! To use this crate, add the following entry to your `Cargo.toml` file in the `dependencies` section: +//! +//! ```toml +//! [dependencies] +//! chksum-sha2-384 = "0.0.0" +//! ``` +//! +//! Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: +//! +//! ```sh +//! cargo add chksum-sha2-384 +//! ``` +//! +//! # Usage +//! +//! Use the [`chksum`] function to calcualate digest of file, directory and so on. +//! +//! ```rust +//! # use std::path::Path; +//! use std::fs::File; +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let file = File::open(path)?; +//! let digest = sha2_384::chksum(file)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Input Types +//! +//! ## Bytes +//! +//! ### Array +//! +//! ```rust +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let data = [0, 1, 2, 3]; +//! let digest = sha2_384::chksum(data)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ### Vec +//! +//! ```rust +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let data = vec![0, 1, 2, 3]; +//! let digest = sha2_384::chksum(data)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ### Slice +//! +//! ```rust +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let data = &[0, 1, 2, 3]; +//! let digest = sha2_384::chksum(data)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Strings +//! +//! ### str +//! +//! ```rust +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let data = "&str"; +//! let digest = sha2_384::chksum(data)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ### String +//! +//! ```rust +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let data = String::from("String"); +//! let digest = sha2_384::chksum(data)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## File +//! +//! ```rust +//! # use std::path::Path; +//! use std::fs::File; +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let file = File::open(path)?; +//! let digest = sha2_384::chksum(file)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Directory +//! +//! ```rust +//! # use std::path::Path; +//! use std::fs::read_dir; +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let readdir = read_dir(path)?; +//! let digest = sha2_384::chksum(readdir)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Path +//! +//! ```rust +//! # use std::path::Path; +//! use std::path::PathBuf; +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let path = PathBuf::from(path); +//! let digest = sha2_384::chksum(path)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Standard Input +//! +//! ```rust +//! use std::io::stdin; +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper() -> Result<()> { +//! let stdin = stdin(); +//! let digest = sha2_384::chksum(stdin)?; +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Features +//! +//! Cargo features are utilized to enable extra options. +//! +//! * `reader` enables the [`reader`] module with the [`Reader`] struct. +//! * `writer` enables the [`writer`] module with the [`Writer`] struct. +//! +//! By default, neither of these features is enabled. +//! +//! To customize your setup, disable the default features and enable only those that you need in your `Cargo.toml` file: +//! +//! ```toml +//! [dependencies] +//! chksum-sha2-384 = { version = "0.0.0", features = ["reader", "writer"] } +//! ``` +//! +//! Alternatively, you can use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: +//! +//! ```shell +//! cargo add chksum-sha2-384 --features reader,writer +//! ``` +//! +//! # License +//! +//! This crate is licensed under the MIT License. + +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![forbid(unsafe_code)] + +#[cfg(feature = "reader")] +pub mod reader; +#[cfg(feature = "writer")] +pub mod writer; + +use std::fmt::{self, Display, Formatter, LowerHex, UpperHex}; + +use chksum_core as core; +#[doc(no_inline)] +pub use chksum_core::{Chksumable, Error, Hash, Hashable, Result}; +#[doc(no_inline)] +pub use chksum_hash_sha2_384 as hash; + +#[cfg(feature = "reader")] +#[doc(inline)] +pub use crate::reader::Reader; +#[cfg(feature = "writer")] +#[doc(inline)] +pub use crate::writer::Writer; + +/// Creates a new hash. +/// +/// # Example +/// +/// ```rust +/// use chksum_sha2_384 as sha2_384; +/// +/// let mut hash = sha2_384::new(); +/// hash.update(b"example data"); +/// let digest = hash.digest(); +/// assert_eq!( +/// digest.to_hex_lowercase(), +/// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +/// ); +/// ``` +#[must_use] +pub fn new() -> SHA2_384 { + SHA2_384::new() +} + +/// Creates a default hash. +/// +/// # Example +/// +/// ```rust +/// use chksum_sha2_384 as sha2_384; +/// +/// let mut hash = sha2_384::default(); +/// hash.update(b"example data"); +/// let digest = hash.digest(); +/// assert_eq!( +/// digest.to_hex_lowercase(), +/// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +/// ); +/// ``` +#[must_use] +pub fn default() -> SHA2_384 { + core::default() +} + +/// Computes the hash of the given input. +/// +/// # Example +/// +/// ```rust +/// use chksum_sha2_384 as sha2_384; +/// +/// let data = b"example data"; +/// let digest = sha2_384::hash(data); +/// assert_eq!( +/// digest.to_hex_lowercase(), +/// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +/// ); +/// ``` +pub fn hash(data: impl core::Hashable) -> Digest { + core::hash::(data) +} + +/// Computes the hash of the given input. +/// +/// # Example +/// +/// ```rust +/// use chksum_sha2_384 as sha2_384; +/// +/// let data = b"example data"; +/// if let Ok(digest) = sha2_384::chksum(data) { +/// assert_eq!( +/// digest.to_hex_lowercase(), +/// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +/// ); +/// } +/// ``` +pub fn chksum(data: impl core::Chksumable) -> Result { + core::chksum::(data) +} + +/// The SHA-2 384 hash instance. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct SHA2_384 { + inner: hash::Update, +} + +impl SHA2_384 { + /// Calculates the hash digest of an input data. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384::SHA2_384; + /// + /// let data = b"example data"; + /// let digest = SHA2_384::hash(data); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" + /// ); + /// ``` + #[must_use] + pub fn hash(data: T) -> Digest + where + T: AsRef<[u8]>, + { + let mut hash = Self::new(); + hash.update(data); + hash.digest() + } + + /// Creates a new hash. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384::SHA2_384; + /// + /// let mut hash = SHA2_384::new(); + /// hash.update(b"example data"); + /// let digest = hash.digest(); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" + /// ); + /// ``` + #[must_use] + pub fn new() -> Self { + let inner = hash::Update::new(); + Self { inner } + } + + /// Updates the hash state with an input data. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384::SHA2_384; + /// + /// let mut hash = SHA2_384::new(); + /// hash.update(b"example"); + /// hash.update(" "); + /// hash.update("data"); + /// let digest = hash.digest(); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" + /// ); + /// ``` + pub fn update(&mut self, data: T) + where + T: AsRef<[u8]>, + { + self.inner.update(data); + } + + /// Resets the hash state to its initial state. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384::SHA2_384; + /// + /// let mut hash = SHA2_384::new(); + /// hash.update(b"example data"); + /// hash.reset(); + /// let digest = hash.digest(); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + /// ); + /// ``` + pub fn reset(&mut self) { + self.inner.reset(); + } + + /// Produces the hash digest. + /// + /// # Example + /// + /// ``` + /// use chksum_sha2_384::SHA2_384; + /// + /// let mut hash = SHA2_384::new(); + /// let digest = hash.digest(); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + /// ); + /// ``` + #[must_use] + pub fn digest(&self) -> Digest { + self.inner.digest().into() + } +} + +impl core::Hash for SHA2_384 { + type Digest = Digest; + + fn update(&mut self, data: T) + where + T: AsRef<[u8]>, + { + self.update(data); + } + + fn reset(&mut self) { + self.reset(); + } + + fn digest(&self) -> Self::Digest { + self.digest() + } +} + +/// A hash digest. +pub struct Digest(hash::Digest); + +impl Digest { + /// Creates a new digest. + #[must_use] + pub const fn new(digest: [u8; hash::DIGEST_LENGTH_BYTES]) -> Self { + let inner = hash::Digest::new(digest); + Self(inner) + } + + /// Returns a byte slice of the digest's contents. + #[must_use] + pub const fn as_bytes(&self) -> &[u8] { + let Self(inner) = self; + inner.as_bytes() + } + + /// Consumes the digest, returning the digest bytes. + #[must_use] + pub fn into_inner(self) -> [u8; hash::DIGEST_LENGTH_BYTES] { + let Self(inner) = self; + inner.into_inner() + } + + /// Returns a string in the lowercase hexadecimal representation. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384 as sha2_384; + /// + /// let digest = [ + /// 0x38, 0xB0, 0x60, 0xA7, + /// 0x51, 0xAC, 0x96, 0x38, + /// 0x4C, 0xD9, 0x32, 0x7E, + /// 0xB1, 0xB1, 0xE3, 0x6A, + /// 0x21, 0xFD, 0xB7, 0x11, + /// 0x14, 0xBE, 0x07, 0x43, + /// 0x4C, 0x0C, 0xC7, 0xBF, + /// 0x63, 0xF6, 0xE1, 0xDA, + /// 0x27, 0x4E, 0xDE, 0xBF, + /// 0xE7, 0x6F, 0x65, 0xFB, + /// 0xD5, 0x1A, 0xD2, 0xF1, + /// 0x48, 0x98, 0xB9, 0x5B, + /// ]; + /// let digest = sha2_384::Digest::new(digest); + /// assert_eq!( + /// digest.to_hex_lowercase(), + /// "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + /// ); + /// ``` + #[must_use] + pub fn to_hex_lowercase(&self) -> String { + let Self(inner) = self; + inner.to_hex_lowercase() + } + + /// Returns a string in the uppercase hexadecimal representation. + /// + /// # Example + /// + /// ```rust + /// use chksum_sha2_384 as sha2_384; + /// + /// let digest = [ + /// 0x38, 0xB0, 0x60, 0xA7, + /// 0x51, 0xAC, 0x96, 0x38, + /// 0x4C, 0xD9, 0x32, 0x7E, + /// 0xB1, 0xB1, 0xE3, 0x6A, + /// 0x21, 0xFD, 0xB7, 0x11, + /// 0x14, 0xBE, 0x07, 0x43, + /// 0x4C, 0x0C, 0xC7, 0xBF, + /// 0x63, 0xF6, 0xE1, 0xDA, + /// 0x27, 0x4E, 0xDE, 0xBF, + /// 0xE7, 0x6F, 0x65, 0xFB, + /// 0xD5, 0x1A, 0xD2, 0xF1, + /// 0x48, 0x98, 0xB9, 0x5B, + /// ]; + /// let digest = sha2_384::Digest::new(digest); + /// assert_eq!( + /// digest.to_hex_uppercase(), + /// "38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B" + /// ); + /// ``` + #[must_use] + pub fn to_hex_uppercase(&self) -> String { + let Self(inner) = self; + inner.to_hex_uppercase() + } +} + +impl core::Digest for Digest {} + +impl AsRef<[u8]> for Digest { + fn as_ref(&self) -> &[u8] { + let Self(inner) = self; + inner.as_bytes() + } +} + +impl Display for Digest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + Display::fmt(inner, f) + } +} + +impl LowerHex for Digest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + LowerHex::fmt(inner, f) + } +} + +impl UpperHex for Digest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + UpperHex::fmt(inner, f) + } +} + +impl From<[u8; hash::DIGEST_LENGTH_BYTES]> for Digest { + fn from(digest: [u8; hash::DIGEST_LENGTH_BYTES]) -> Self { + Self::new(digest) + } +} + +impl From for Digest { + fn from(digest: hash::Digest) -> Self { + Self(digest) + } +} diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..47a119e --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,70 @@ +//! This module is optional and can be enabled using the `reader` Cargo feature. +//! +//! The [`Reader`] allows on-the-fly calculation of the digest while reading the data. +//! +//! # Enabling +//! +//! Add the following entry to your `Cargo.toml` file to enable the `reader` feature: +//! +//! ```toml +//! [dependencies] +//! chksum-sha2-384 = { version = "0.0.0", features = ["reader"] } +//! ``` +//! +//! Alternatively, use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: +//! +//! ```shell +//! cargo add chksum-sha2-384 --features reader +//! ``` +//! +//! # Example +//! +//! ```rust +//! # use std::path::Path; +//! use std::fs::File; +//! use std::io::Read; // required by reader +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let file = File::open(path)?; +//! let mut reader = sha2_384::reader::new(file); +//! +//! let mut buffer = Vec::new(); +//! reader.read_to_end(&mut buffer)?; +//! assert_eq!(buffer, b"example data"); +//! +//! let digest = reader.digest(); +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` + +use std::io::Read; + +use chksum_reader as reader; + +use crate::SHA2_384; + +/// A specialized [`Reader`](reader::Reader) type with the [`SHA2_384`] hash algorithm. +pub type Reader = reader::Reader; + +/// Creates new [`Reader`]. +pub fn new(inner: R) -> Reader +where + R: Read, +{ + reader::new(inner) +} + +/// Creates new [`Reader`] with provided hash. +pub fn with_hash(inner: R, hash: SHA2_384) -> Reader +where + R: Read, +{ + reader::with_hash(inner, hash) +} diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..b2339d6 --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,62 @@ +//! This module is optional and can be enabled using the `writer` Cargo feature. +//! +//! The [`Writer`] allows on-the-fly calculation of the digest while writing the data. +//! +//! # Enabling +//! +//! Add the following entry to your `Cargo.toml` file to enable the `writer` feature: +//! +//! ```toml +//! [dependencies] +//! chksum-sha2-384 = { version = "0.0.0", features = ["writer"] } +//! ``` +//! +//! Alternatively, use the [`cargo add`](https://doc.rust-lang.org/cargo/commands/cargo-add.html) subcommand: +//! +//! ```shell +//! cargo add chksum-sha2-384 --features writer +//! ``` +//! +//! # Example +//! +//! ```rust +//! # use std::path::Path; +//! use std::fs::File; +//! use std::io::Write; // required by writer +//! +//! # use chksum_sha2_384::Result; +//! use chksum_sha2_384 as sha2_384; +//! +//! # fn wrapper(path: &Path) -> Result<()> { +//! let file = File::open(path)?; +//! let mut writer = sha2_384::writer::new(file); +//! +//! writer.write_all(b"example data")?; +//! +//! let digest = writer.digest(); +//! assert_eq!( +//! digest.to_hex_lowercase(), +//! "12ecdfd463a85a301b7c29a43bf4b19cdfc6e5e86a5f40396aa6ae3368a7e5b0ed31f3bef2eb3071577ba610b4ed1cb8" +//! ); +//! # Ok(()) +//! # } +//! ``` + +use std::io::Write; + +use chksum_writer as writer; + +use crate::SHA2_384; + +/// A specialized [`Writer`](writer::Writer) type with the [`SHA2_384`] hash algorithm. +pub type Writer = writer::Writer; + +/// Creates new [`Writer`]. +pub fn new(inner: impl Write) -> Writer { + writer::new(inner) +} + +/// Creates new [`Writer`] with provided hash. +pub fn with_hash(inner: impl Write, hash: SHA2_384) -> Writer { + writer::with_hash(inner, hash) +} diff --git a/tests/sha2_384.rs b/tests/sha2_384.rs new file mode 100644 index 0000000..8ff21b2 --- /dev/null +++ b/tests/sha2_384.rs @@ -0,0 +1,339 @@ +use std::fs::{read_dir, File}; +use std::io::Error as IoError; + +use assert_fs::fixture::FixtureError; +use assert_fs::prelude::{FileTouch, FileWriteBin, PathChild}; +use assert_fs::TempDir; +use chksum_sha2_384::{chksum, Error as ChksumError}; + +#[derive(Debug, thiserror::Error)] +enum Error { + #[error(transparent)] + ChksumError(#[from] ChksumError), + #[error(transparent)] + FixtureError(#[from] FixtureError), + #[error(transparent)] + IoError(#[from] IoError), +} + +#[test] +fn empty_directory_as_path() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + + let dir = temp_dir.path(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn empty_directory_as_pathbuf() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + + let dir = temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + let dir = &temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn empty_directory_as_readdir() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + + let dir = read_dir(temp_dir.path())?; + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_empty_file_as_path() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + temp_dir.child("file.txt").touch()?; + temp_dir + }; + + let dir = temp_dir.path(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_empty_file_as_pathbuf() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + temp_dir.child("file.txt").touch()?; + temp_dir + }; + + let dir = temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + let dir = &temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_empty_file_as_readdir() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + temp_dir.child("file.txt").touch()?; + temp_dir + }; + + let dir = read_dir(temp_dir.path())?; + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_non_empty_file_as_path() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + temp_dir + }; + + let dir = temp_dir.path(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_non_empty_file_as_pathbuf() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + temp_dir + }; + + let dir = temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + let dir = &temp_dir.to_path_buf(); + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +} + +#[test] +fn non_empty_directory_with_non_empty_file_as_readdir() -> Result<(), Error> { + let temp_dir = { + let temp_dir = TempDir::new()?; + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + temp_dir + }; + + let dir = read_dir(temp_dir.path())?; + let digest = chksum(dir)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +} + +#[test] +fn empty_file_as_path() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file + }; + + let file = child.path(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn empty_file_as_pathbuf() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file + }; + + let file = child.to_path_buf(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + let file = &child.to_path_buf(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn empty_file_as_file() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file + }; + + let file = File::open(child.path())?; + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + let file = &File::open(child.path())?; + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + ); + + Ok(()) +} + +#[test] +fn non_empty_file_as_path() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + file + }; + + let file = child.path(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +} + +#[test] +fn non_empty_file_as_pathbuf() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + file + }; + + let file = child.to_path_buf(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + let file = &child.to_path_buf(); + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +} + +#[test] +fn non_empty_file_as_file() -> Result<(), Error> { + let temp_dir = TempDir::new()?; + let child = { + let file = temp_dir.child("file.txt"); + file.touch()?; + file.write_binary(b"data")?; + file + }; + + let file = File::open(child.path())?; + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + let file = &File::open(child.path())?; + let digest = chksum(file)?.to_hex_lowercase(); + assert_eq!( + digest, + "2039e0f0b92728499fb88e23ebc3cfd0554b28400b0ed7b753055c88b5865c3c2aa72c6a1a9ae0a755d87900a4a6ff41" + ); + + Ok(()) +}