76 Commits
1.4.1 ... main

Author SHA1 Message Date
Matt Nadareski
a334ffc25a Slightly more cleanup and clarification 2026-01-27 09:13:30 -05:00
Matt Nadareski
3211f1a218 Use new null checking syntax 2026-01-25 17:12:44 -05:00
Matt Nadareski
3c451b8570 Add editorconfig, fix issues 2026-01-25 17:11:44 -05:00
Matt Nadareski
ecd52b1a0e Add issue templates 2026-01-11 09:25:38 -05:00
Matt Nadareski
0075b9b1ad Add CRC-32/DVD-ROM-EDC (fixes #4) 2026-01-11 09:20:15 -05:00
Matt Nadareski
214b449e5e Format GHA definitions 2025-11-17 08:38:13 -05:00
Matt Nadareski
0bb3e541be Fix one property group 2025-11-13 20:38:04 -05:00
Matt Nadareski
d91220a2e4 Bump version 2025-11-13 07:25:14 -05:00
Matt Nadareski
e60a1fbe3c These can be static 2025-11-12 21:41:40 -05:00
Matt Nadareski
978a3847de Add support for .NET 10 2025-11-12 20:00:11 -05:00
Matt Nadareski
48da4cd88e Update rolling tag 2025-10-26 20:21:42 -04:00
Matt Nadareski
458499eea4 Bump version 2025-10-07 09:06:58 -04:00
Matt Nadareski
47cb600f3f Fix accidental cutoff 2025-10-07 09:02:11 -04:00
Matt Nadareski
d42af9b95d Consistency matching cleanup 2025-10-07 09:01:12 -04:00
Matt Nadareski
8672fea581 Used mixed features and inputs 2025-10-06 11:31:54 -04:00
Matt Nadareski
a5b36a8329 Update readme to match help text 2025-10-06 09:12:58 -04:00
Matt Nadareski
b83cafebf1 Reorganize based on lessons from other implementations 2025-10-06 09:12:48 -04:00
Matt Nadareski
2b7f09f06f Update CommandLine to 1.3.2 2025-10-06 07:29:48 -04:00
Matt Nadareski
2a37f8c03a Set text formatting in readme 2025-10-05 22:08:02 -04:00
Matt Nadareski
51c3ecec4b Fully use CommandLine modelling 2025-10-05 20:31:15 -04:00
Matt Nadareski
97a1338cab Update CommandLine to 1.3.1 2025-10-05 20:11:35 -04:00
Matt Nadareski
a808c9ba3c Use CommandLine library for executable 2025-10-05 19:47:02 -04:00
Matt Nadareski
53e28df7f8 Make MekaCrc official 2025-09-25 07:51:44 -04:00
Matt Nadareski
bdde3bad16 Use BitConverter instead of Convert 2025-09-25 07:39:57 -04:00
Matt Nadareski
32491ccaae Add "mekacrc", not hooked up or validated 2025-09-24 23:20:30 -04:00
Matt Nadareski
ae3fc9ef56 Fix typo introduced by cleanup 2025-09-24 22:38:38 -04:00
Matt Nadareski
8d52187a03 Minor typing cleanup 2025-09-24 22:36:45 -04:00
Matt Nadareski
720a3a4c2b Slightly improve HasCommonSubstring 2025-09-24 22:35:40 -04:00
Matt Nadareski
0872d8c927 Slight reorganization 2025-09-24 22:20:31 -04:00
Matt Nadareski
1619046d11 Rewrite comparison code to fit C# better, add tests 2025-09-24 22:18:02 -04:00
Matt Nadareski
4fda55d4d1 Move static method higher in the class 2025-09-24 21:58:12 -04:00
Matt Nadareski
349a414ff3 Slight cleanup based on preferred style and naming 2025-09-24 21:55:20 -04:00
Matt Nadareski
05617c5c7e Slight test cleanup 2025-09-24 21:41:26 -04:00
HeroponRikiBestest
cce8a18b03 Add SpamSum fuzzy compare (#3)
* Pre-cleaned drop in fuzzycompare

* Finish cleaning up code.

* Figure out why i can't debug my unit tests

* Fix stupid mistake

* First round of changes

* Extra tests

* Revert TestHelper.cs

* Roll back TestHelper.cs correctly.

* Roll back Options.cs and Program.cs
2025-09-24 21:32:11 -04:00
Matt Nadareski
cbaa79b284 Minor tweaks 2025-09-23 14:18:50 -04:00
Matt Nadareski
32cd0e73ed Add hashing tool implementation, for fun 2025-09-23 14:15:27 -04:00
Matt Nadareski
95589df904 There 2025-09-10 21:52:28 -04:00
Matt Nadareski
fd612f939a Bump version 2025-07-24 08:44:08 -04:00
Matt Nadareski
8e3a0f77e9 Be consistent about end-of-file newlines 2025-07-24 08:41:37 -04:00
Matt Nadareski
40cfa78be4 Add .NET Standard 2.0 and 2.1 2025-07-24 08:36:34 -04:00
Matt Nadareski
4d92c7cd23 Update nuget packages 2025-07-24 08:34:59 -04:00
Matt Nadareski
acf1e3ec71 Reduce target frameworks for test project 2025-02-25 21:28:49 -05:00
Matt Nadareski
10027f78e3 Fix how conditions are used for references 2025-02-25 21:24:38 -05:00
Matt Nadareski
187932b091 Bump version 2025-01-06 09:58:33 -05:00
Matt Nadareski
89dbe0460f Simplify apparent usings in wrapper class 2025-01-06 00:53:27 -05:00
Matt Nadareski
365ee9019f Use expression bodies for properties 2025-01-06 00:46:12 -05:00
Matt Nadareski
e117892f37 Fix modern .NET builds 2025-01-06 00:40:11 -05:00
Matt Nadareski
1e11e9abb8 Reduce complexity of CurrentHash properties 2025-01-06 00:38:37 -05:00
Matt Nadareski
1d2985023d Remove now-obsolete remark 2025-01-06 00:29:40 -05:00
Matt Nadareski
cfddb3dab4 Maintain consistency between supported structures 2025-01-06 00:15:11 -05:00
Matt Nadareski
08512abc59 Maintain consistency between supported structures 2025-01-06 00:12:32 -05:00
Matt Nadareski
71a330cf68 Better HashTool documentation 2025-01-06 00:00:30 -05:00
Matt Nadareski
ad8d119905 Fix stream hash and size signatures 2025-01-05 23:56:05 -05:00
Matt Nadareski
c82a6dc39b Add stream GetStandardHashes variant 2025-01-05 23:39:00 -05:00
Matt Nadareski
414759cbd2 Without size variants are all thin wrappers 2025-01-05 23:33:29 -05:00
Matt Nadareski
142ca6f327 Replace FileInfo calls 2025-01-05 23:28:50 -05:00
Matt Nadareski
8dee2e2501 Add tests around length additions 2025-01-05 23:10:50 -05:00
Matt Nadareski
240098dd03 Create calculate-length-on-read HashTool helpers 2025-01-05 23:04:41 -05:00
Matt Nadareski
15a022eca5 Reduce unnecessary complexity 2025-01-02 22:43:41 -05:00
Matt Nadareski
0ede92a5d9 Fix SSDEEP URL 2025-01-02 22:25:17 -05:00
Matt Nadareski
0c3815e17c Replace SpamSum implementation with more complete one 2025-01-02 22:21:26 -05:00
Matt Nadareski
8f5bff0375 Add extremely basic SpamSum implementation 2025-01-02 20:02:08 -05:00
Matt Nadareski
dc3cb0be5d Simplify namespace usage 2025-01-02 16:00:56 -05:00
Matt Nadareski
f7346b20e1 Simplify namespace usage 2025-01-02 15:53:51 -05:00
Matt Nadareski
f971fcf5c8 Add HashSize property for all implementations 2025-01-02 15:37:20 -05:00
Matt Nadareski
4e0da77cb4 Message digests are cryptographic hashes 2025-01-02 15:29:06 -05:00
Matt Nadareski
7776112ec6 xxHash are non-cryptographic hashes 2025-01-02 15:23:13 -05:00
Matt Nadareski
c65184689d FNV are non-cryptographic hashes 2025-01-02 15:15:53 -05:00
Matt Nadareski
704e08b5ed Make xxHash 32/64 implement HashAlgorithm 2025-01-02 15:08:32 -05:00
Matt Nadareski
99f770ce81 Make checksums implement HashAlgorithm 2025-01-02 15:03:33 -05:00
Matt Nadareski
e5fea69815 Rename checksum methods based on HashAlgorithm 2025-01-02 14:35:40 -05:00
Matt Nadareski
80448302e8 Make message digests implement HashAlgorithm 2025-01-02 13:58:36 -05:00
Matt Nadareski
434a10d3db Update copyright 2024-12-30 21:22:58 -05:00
Matt Nadareski
4ea5f95b5e Remove unnecessary action step 2024-12-30 21:22:45 -05:00
Matt Nadareski
381dffccf9 Ensure .NET versions are installed for testing 2024-12-19 10:51:03 -05:00
Matt Nadareski
d4885d389d Ensure .NET versions are installed for testing 2024-12-19 10:48:52 -05:00
93 changed files with 3852 additions and 1453 deletions

167
.editorconfig Normal file
View File

@@ -0,0 +1,167 @@
# top-most EditorConfig file
root = true
# C# files
[*.cs]
# Indentation and spacing
charset = utf-8
indent_size = 4
indent_style = space
tab_width = 4
trim_trailing_whitespace = true
# New line preferences
end_of_line = lf
insert_final_newline = true
max_line_length = unset
# using directive preferences
csharp_using_directive_placement = outside_namespace
dotnet_diagnostic.IDE0005.severity = error
# Code-block preferences
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_top_level_statements = false
# Expression-level preferences
csharp_prefer_simple_default_expression = true
csharp_style_inlined_variable_declaration = true
csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable
dotnet_diagnostic.IDE0001.severity = warning
dotnet_diagnostic.IDE0002.severity = warning
dotnet_diagnostic.IDE0004.severity = warning
dotnet_diagnostic.IDE0010.severity = error
dotnet_diagnostic.IDE0051.severity = warning
dotnet_diagnostic.IDE0052.severity = warning
dotnet_diagnostic.IDE0072.severity = warning
dotnet_diagnostic.IDE0080.severity = warning
dotnet_diagnostic.IDE0100.severity = error
dotnet_diagnostic.IDE0110.severity = error
dotnet_diagnostic.IDE0120.severity = warning
dotnet_diagnostic.IDE0121.severity = warning
dotnet_diagnostic.IDE0240.severity = error
dotnet_diagnostic.IDE0241.severity = error
dotnet_style_coalesce_expression = true
dotnet_style_namespace_match_folder = false
dotnet_style_null_propagation = true
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_compound_assignment = true
csharp_style_prefer_simple_property_accessors = true
dotnet_style_prefer_simplified_interpolation = true
dotnet_style_prefer_simplified_boolean_expressions = true
csharp_style_prefer_unbound_generic_type_in_nameof = true
# Field preferences
dotnet_diagnostic.IDE0044.severity = warning
dotnet_style_readonly_field = true
# Language keyword vs. framework types preferences
dotnet_diagnostic.IDE0049.severity = error
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Modifier preferences
csharp_prefer_static_local_function = true
csharp_style_prefer_readonly_struct = true
dotnet_diagnostic.IDE0036.severity = warning
dotnet_diagnostic.IDE0040.severity = error
dotnet_diagnostic.IDE0380.severity = error
dotnet_style_require_accessibility_modifiers = always
# New-line preferences
dotnet_diagnostic.IDE2000.severity = warning
dotnet_diagnostic.IDE2002.severity = warning
dotnet_diagnostic.IDE2003.severity = warning
dotnet_diagnostic.IDE2004.severity = warning
dotnet_diagnostic.IDE2005.severity = warning
dotnet_diagnostic.IDE2006.severity = warning
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
dotnet_style_allow_multiple_blank_lines_experimental = false
dotnet_style_allow_statement_immediately_after_block_experimental = false
# Null-checking preferences
csharp_style_conditional_delegate_call = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
dotnet_diagnostic.IDE0280.severity = error
# Parentheses preferences
dotnet_diagnostic.IDE0047.severity = warning
dotnet_diagnostic.IDE0048.severity = warning
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = always_for_clarity
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Pattern-matching preferences
dotnet_diagnostic.IDE0019.severity = warning
dotnet_diagnostic.IDE0020.severity = warning
dotnet_diagnostic.IDE0038.severity = warning
dotnet_diagnostic.IDE0066.severity = none
dotnet_diagnostic.IDE0083.severity = warning
dotnet_diagnostic.IDE0260.severity = warning
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# var preferences
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = true
# .NET formatting options
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = true
# C# formatting options
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

View File

@@ -0,0 +1,27 @@
---
name: Feature Request
about: For when you know better than me what you want
title: "[Request]"
labels: enhancement
assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://github.com/SabreTools/SabreTools.Hashing/releases/tag/rolling) to see if the feature already exists.
- Check [previous issues](https://github.com/SabreTools/SabreTools.Hashing/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

21
.github/ISSUE_TEMPLATE/informational.md vendored Normal file
View File

@@ -0,0 +1,21 @@
---
name: Info
about: Something you need to tell me
title: "[Info]"
labels: question
assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://github.com/SabreTools/SabreTools.Hashing/releases/tag/rolling) to see if the feature already exists.
- Check [previous issues](https://github.com/SabreTools/SabreTools.Hashing/issues) to see if any of those are related to what you're about to ask for.
If none of those apply, then continue...
**Is your information related to one of the checksums/hashes supported or something that isn't a bug in the code? Please describe.**
A clear and concise description of what the information is. Ex. With the latest build of Hasher, it [...]
**Additional context**
Add any other context or screenshots about the information here.

45
.github/ISSUE_TEMPLATE/issue-report.md vendored Normal file
View File

@@ -0,0 +1,45 @@
---
name: Issue Report
about: Tell me what's wrong, seriously
title: "[Problem]"
labels: bug
assignees: mnadareski
---
**Before You Submit**
- Remember to try the [latest WIP build](https://github.com/SabreTools/SabreTools.Hashing/releases/tag/rolling) to see if the issue has already been addressed.
- Check multiple inputs to help narrow down the issue
If all of those fail, then continue...
**Version**
What version are you using?
- [ ] Stable release (version here)
- [ ] WIP release (version here)
**Build**
What runtime version are you using?
- [ ] .NET 10 running on (Operating System)
**Describe the issue**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Create the '...' hasher
2. Run the hasher with '....'
3. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,43 +1,48 @@
name: Build and Test
on:
push:
branches: [ "main" ]
push:
branches: ["main"]
jobs:
build:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Run tests
run: dotnet test
- name: Run publish script
run: ./publish-nix.sh
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: 'Nuget Package'
path: "*.nupkg,*.snupkg"
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Upload to rolling
uses: ncipollo/release-action@v1.14.0
with:
allowUpdates: True
artifacts: "*.nupkg,*.snupkg"
body: 'Last built commit: ${{ github.sha }}'
name: 'Rolling Release'
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True
- name: Run tests
run: dotnet test
- name: Run publish script
run: ./publish-nix.sh -d
- name: Update rolling tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -f rolling
git push origin :refs/tags/rolling || true
git push origin rolling --force
- name: Upload to rolling
uses: ncipollo/release-action@v1.20.0
with:
allowUpdates: True
artifacts: "*.nupkg,*.snupkg,*.zip"
body: "Last built commit: ${{ github.sha }}"
name: "Rolling Release"
prerelease: True
replacesArtifacts: True
tag: "rolling"
updateOnlyUnreleased: True

View File

@@ -3,18 +3,21 @@ name: Build PR
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Build
run: dotnet build
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Run tests
run: dotnet test
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Build
run: dotnet build
- name: Run tests
run: dotnet test

28
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Hasher/bin/Debug/net10.0/Hasher.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false,
"justMyCode": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach",
"processId": "${command:pickProcess}"
}
]
}

View File

@@ -0,0 +1,52 @@
using System;
using SabreTools.CommandLine;
using SabreTools.Hashing;
namespace Hasher.Features
{
internal sealed class ListFeature : Feature
{
#region Feature Definition
public const string DisplayName = "list";
private static readonly string[] _flags = ["--list"];
private const string _description = "List all available hashes and quit";
#endregion
public ListFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = false;
}
/// <inheritdoc/>
/// TODO: Print all supported variants of names?
public override bool Execute()
{
Console.WriteLine("Hash Name Parameter Name ");
Console.WriteLine("--------------------------------------------------------------");
var hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
foreach (var hashType in hashTypes)
{
// Derive the parameter name
string paramName = $"{hashType}";
paramName = paramName.Replace("-", string.Empty);
paramName = paramName.Replace(" ", string.Empty);
paramName = paramName.Replace("/", "_");
paramName = paramName.Replace("\\", "_");
paramName = paramName.ToLowerInvariant();
Console.WriteLine($"{hashType.GetHashName()?.PadRight(39, ' ')} {paramName}");
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
}
}

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Inputs;
using SabreTools.Hashing;
namespace Hasher.Features
{
internal sealed class MainFeature : Feature
{
#region Feature Definition
public const string DisplayName = "main";
/// <remarks>Flags are unused</remarks>
private static readonly string[] _flags = [];
/// <remarks>Description is unused</remarks>
private const string _description = "";
#endregion
#region Inputs
private const string _debugName = "debug";
internal readonly FlagInput DebugInput = new(_debugName, ["-d", "--debug"], "Enable debug mode");
private const string _typeName = "type";
internal readonly StringListInput TypeInput = new(_typeName, ["-t", "--type"], "Select included hashes");
#endregion
public MainFeature()
: base(DisplayName, _flags, _description)
{
RequiresInputs = true;
Add(DebugInput);
Add(TypeInput);
}
/// <inheritdoc/>
public override bool Execute()
{
// Get the required variables
bool debug = GetBoolean(_debugName);
List<HashType> hashTypes = GetHashTypes(GetStringList(_typeName));
// Loop through all of the input files
for (int i = 0; i < Inputs.Count; i++)
{
string arg = Inputs[i];
PrintPathHashes(arg, hashTypes, debug);
}
return true;
}
/// <inheritdoc/>
public override bool VerifyInputs() => true;
/// <summary>
/// Derive a list of hash types from a list of strings
/// </summary>
private static List<HashType> GetHashTypes(List<string> types)
{
List<HashType> hashTypes = [];
if (types.Count == 0)
{
hashTypes.Add(HashType.CRC32);
hashTypes.Add(HashType.MD5);
hashTypes.Add(HashType.SHA1);
hashTypes.Add(HashType.SHA256);
}
else if (types.Contains("all"))
{
hashTypes = [.. (HashType[])Enum.GetValues(typeof(HashType))];
}
else
{
foreach (string typeString in types)
{
HashType? hashType = typeString.GetHashType();
if (hashType is not null && !hashTypes.Contains(hashType.Value))
hashTypes.Add(item: hashType.Value);
}
}
return hashTypes;
}
/// <summary>
/// Wrapper to print hashes for a single path
/// </summary>
/// <param name="path">File or directory path</param>
/// <param name="hashTypes">Set of hashes to retrieve</param>
/// <param name="debug">Enable debug output</param>
private static void PrintPathHashes(string path, List<HashType> hashTypes, bool debug)
{
Console.WriteLine($"Checking possible path: {path}");
// Check if the file or directory exists
if (File.Exists(path))
{
PrintFileHashes(path, hashTypes, debug);
}
else if (Directory.Exists(path))
{
foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories))
{
PrintFileHashes(file, hashTypes, debug);
}
}
else
{
Console.WriteLine($"{path} does not exist, skipping...");
}
}
/// <summary>
/// Print information for a single file, if possible
/// </summary>
/// <param name="file">File path</param>
/// <param name="hashTypes">Set of hashes to retrieve</param>
/// <param name="debug">Enable debug output</param>
private static void PrintFileHashes(string file, List<HashType> hashTypes, bool debug)
{
Console.WriteLine($"Attempting to hash {file}, this may take a while...");
Console.WriteLine();
// If the file doesn't exist
if (!File.Exists(file))
{
Console.WriteLine($"{file} does not exist, skipping...");
return;
}
try
{
// Get all file hashes for flexibility
var hashes = HashTool.GetFileHashes(file);
if (hashes is null)
{
if (debug) Console.WriteLine($"Hashes for {file} could not be retrieved");
return;
}
// Output subset of available hashes
var builder = new StringBuilder();
foreach (HashType hashType in hashTypes)
{
// TODO: Make helper to pretty-print hash type names
if (hashes.TryGetValue(hashType, out string? hash) && hash is not null)
builder.AppendLine($"{hashType}: {hash}");
}
// Create and print the output data
string hashData = builder.ToString();
Console.WriteLine(hashData);
}
catch (Exception ex)
{
Console.WriteLine(debug ? ex : "[Exception opening file, please try again]");
return;
}
}
}
}

37
Hasher/Hasher.csproj Normal file
View File

@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.6.0</Version>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Hashing\SabreTools.Hashing.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.CommandLine" Version="[1.4.0]" />
</ItemGroup>
</Project>

87
Hasher/Program.cs Normal file
View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using Hasher.Features;
using SabreTools.CommandLine;
using SabreTools.CommandLine.Features;
namespace Hasher
{
public static class Program
{
public static void Main(string[] args)
{
// Create the command set
var mainFeature = new MainFeature();
var commandSet = CreateCommands(mainFeature);
// If we have no args, show the help and quit
if (args is null || args.Length == 0)
{
commandSet.OutputAllHelp();
return;
}
// Cache the first argument and starting index
string featureName = args[0];
// Try processing the standalone arguments
var topLevel = commandSet.GetTopLevel(featureName);
switch (topLevel)
{
// Standalone Options
case Help help: help.ProcessArgs(args, 0, commandSet); return;
case ListFeature lf: lf.Execute(); return;
// Default Behavior
default:
if (!mainFeature.ProcessArgs(args, 0))
{
commandSet.OutputAllHelp();
return;
}
else if (!mainFeature.VerifyInputs())
{
Console.Error.WriteLine("At least one input is required");
commandSet.OutputAllHelp();
return;
}
mainFeature.Execute();
break;
}
}
/// <summary>
/// Create the command set for the program
/// </summary>
private static CommandSet CreateCommands(MainFeature mainFeature)
{
List<string> header = [
"File Hashing Program",
string.Empty,
"Hasher <options> file|directory ...",
string.Empty,
];
List<string> footer = [
string.Empty,
"If no hash types are provided, this tool will default to",
"outputting CRC-32, MD5, SHA-1, and SHA-256.",
"Optionally, all supported hashes can be output by",
"specifying a value of 'all'.",
];
var commandSet = new CommandSet(header, footer);
// Standalone Options
commandSet.Add(new Help(["-?", "-h", "--help"]));
commandSet.Add(new ListFeature());
// Hasher Options
commandSet.Add(mainFeature.DebugInput);
commandSet.Add(mainFeature.TypeInput);
return commandSet;
}
}
}

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright (c) 2018-2025 Matt Nadareski
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 (including the next paragraph) 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.

View File

@@ -12,6 +12,25 @@ For the most recent stable build, download the latest release here: [Releases Pa
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/SabreTools.Hashing/releases/rolling)
## Hasher
**Hasher** is a reference implementation for hashing and checksumming features of the library, packaged as a standalone executable for all supported platforms. It will attempt to select the correct hashing types based on input and then print the calculated values to standard output.
```text
Hasher <options> file|directory ...
Available options:
-?, -h, --help Show this help
--list List all available hashes and quit
-d, --debug Enable debug mode
-t=, --type= Select included hashes
If no hash types are provided, this tool will default to
outputting CRC-32, MD5, SHA-1, and SHA-256.
Optionally, all supported hashes can be output by
specifying a value of 'all'.
```
## Internal Implementations
All hash and checksum types here have been written to ensure compatibility across all .NET versions. Some may have been adapted to ensure this compatibility. These can be treated as reference implementations, not always optimized.
@@ -24,6 +43,7 @@ All hash and checksum types here have been written to ensure compatibility acros
| FNV | 32-, and 64-bit variants; 0, 1, and 1a algorithms |
| Message Digest | MD2 and MD4 only |
| RIPEMD | 128-, 160-, 256-, and 320-bit variants |
| SpamSum | Based on the [SSDEEP source code](https://github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c) |
| Tiger | 128-, 160-, and 192-bit variants; 3- and 4-pass; `0x01` and `0x80` (Tiger2) pad-initialized |
| xxHash | xxHash-32 and xxHash-64 only |
@@ -33,7 +53,6 @@ External implementations of hash and checksum types may not be compatible with a
| Source | Hash / Checksum Types | Notes |
| --- | --- | --- |
| [Aaru.Checksums](https://github.com/aaru-dps/Aaru.Checksums) | SpamSum | Some code tweaks made to support older .NET versions |
| [Blake3.NET](https://github.com/xoofx/Blake3.NET) | BLAKE3 | Used in `net7.0` and above |
| [System.IO.Hashing](https://www.nuget.org/packages/System.IO.Hashing) | XXH3, XXH128 | Used in `net462` and above |
| [System.Security.Cryptography](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography) | MD5, SHA-1, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-384, SHA3-512, SHAKE128, SHAKE256 | Built-in library; SHA3-256, SHA3-384, SHA3-512, SHAKE128, and SHAKE256 are `net8.0` and above only for [supported platforms](https://learn.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography) |

View File

@@ -29,7 +29,8 @@ namespace SabreTools.Hashing.Test
public void GetSingleGzipStreamHashesTest()
{
var gzipStream = new GZipStream(File.OpenRead(_singleGzipFilePath), CompressionMode.Decompress);
var hashDict = HashTool.GetStreamHashes(gzipStream);
var hashDict = HashTool.GetStreamHashesAndSize(gzipStream, out long actualSize);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHashes(hashDict);
}
@@ -38,7 +39,8 @@ namespace SabreTools.Hashing.Test
{
var zipFile = ZipFile.OpenRead(_singleZipFilePath);
var fileStream = zipFile.Entries[0].Open();
var hashDict = HashTool.GetStreamHashes(fileStream);
var hashDict = HashTool.GetStreamHashesAndSize(fileStream, out long actualSize);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHashes(hashDict);
}
@@ -50,9 +52,10 @@ namespace SabreTools.Hashing.Test
for (int i = 0; i < zipFile.Entries.Count; i++)
{
var fileStream = zipFile.Entries[i].Open();
var hashDict = HashTool.GetStreamHashes(fileStream);
var hashDict = HashTool.GetStreamHashesAndSize(fileStream, out long actualSize);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHashes(hashDict);
}
}
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
@@ -16,15 +15,15 @@ namespace SabreTools.Hashing.Test
/// <summary>
/// Get an array of all hash types
/// </summary>
public static List<object[]> AllHashTypes
public static TheoryData<HashType> AllHashTypes
{
get
{
var values = Enum.GetValues(typeof(HashType));
var set = new List<object[]>();
var values = Enum.GetValues<HashType>();
var set = new TheoryData<HashType>();
foreach (var value in values)
{
set.Add([value]);
set.Add(value);
}
return set;
@@ -32,7 +31,7 @@ namespace SabreTools.Hashing.Test
}
[Fact]
public void GetStandardHashesTest()
public void GetStandardHashesFileTest()
{
bool gotHashes = HashTool.GetStandardHashes(_hashFilePath, out long actualSize, out string? crc32, out string? md5, out string? sha1);
@@ -43,6 +42,32 @@ namespace SabreTools.Hashing.Test
TestHelper.ValidateHash(HashType.SHA1, sha1);
}
[Fact]
public void GetStandardHashesArrayTest()
{
byte[] fileBytes = File.ReadAllBytes(_hashFilePath);
bool gotHashes = HashTool.GetStandardHashes(fileBytes, out long actualSize, out string? crc32, out string? md5, out string? sha1);
Assert.True(gotHashes);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHash(HashType.CRC32, crc32);
TestHelper.ValidateHash(HashType.MD5, md5);
TestHelper.ValidateHash(HashType.SHA1, sha1);
}
[Fact]
public void GetStandardHashesStreamTest()
{
var fileStream = File.OpenRead(_hashFilePath);
bool gotHashes = HashTool.GetStandardHashes(fileStream, out long actualSize, out string? crc32, out string? md5, out string? sha1);
Assert.True(gotHashes);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHash(HashType.CRC32, crc32);
TestHelper.ValidateHash(HashType.MD5, md5);
TestHelper.ValidateHash(HashType.SHA1, sha1);
}
[Fact]
public void GetFileHashesParallelTest()
{
@@ -74,6 +99,15 @@ namespace SabreTools.Hashing.Test
TestHelper.ValidateHashes(hashDict);
}
[Fact]
public void GetByteArrayHashesAndSizeTest()
{
byte[] fileBytes = File.ReadAllBytes(_hashFilePath);
var hashDict = HashTool.GetByteArrayHashesAndSize(fileBytes, out long actualSize);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHashes(hashDict);
}
[Fact]
public void GetStreamHashesTest()
{
@@ -81,5 +115,14 @@ namespace SabreTools.Hashing.Test
var hashDict = HashTool.GetStreamHashes(fileStream);
TestHelper.ValidateHashes(hashDict);
}
[Fact]
public void GetStreamHashesAndSizeTest()
{
var fileStream = File.OpenRead(_hashFilePath);
var hashDict = HashTool.GetStreamHashesAndSize(fileStream, out long actualSize);
TestHelper.ValidateSize(actualSize);
TestHelper.ValidateHashes(hashDict);
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net462;net472;net48;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
@@ -20,14 +20,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="System.IO.Compression" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -0,0 +1,45 @@
using Xunit;
namespace SabreTools.Hashing.Test
{
public class SpamSumTests
{
[Theory]
// Invalid inputs
[InlineData(null, null, -1)]
[InlineData(null, "3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", -1)]
[InlineData("3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", null, -1)]
[InlineData("", "", -1)]
[InlineData("3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", "", -1)]
[InlineData("", "3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", -1)]
// Small data
[InlineData("6:l+lq/MtlM8pJ0gt6lXWogE61UlT1Uqj1akMD5n:l+l6Mtl/n0gtOXmEuUl5UqpakM9n", "6:mTj3qJskr+V+1o21+n0rtD2noPWKlAyjllZmMt6120EK+wlsS6T1oLwXuk4tk7:m/bk/1oQrJL3jTu20EK+wlsp5oO4tk7", 0)]
[InlineData("6:mTj3qJskr+V+1o21+n0rtD2noPWKlAyjllZmMt6120EK+wlsS6T1oLwXuk4tk7:m/bk/1oQrJL3jTu20EK+wlsp5oO4tk7", "6:l+lq/MtlM8pJ0gt6lXWogE61UlT1Uqj1akMD5n:l+l6Mtl/n0gtOXmEuUl5UqpakM9n", 0)]
[InlineData("3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", "3:hMCERJAFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:huRJdxZENFs+rPSromekL", 41)]
[InlineData("3:hMCERJAFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:huRJdxZENFs+rPSromekL", "3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", 41)]
[InlineData("12:Y+VH/3Ckg3xqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMn:xHqVwMMMMMMMMMMMMMMMMMMMMMMMMMM0", "12:Oqkg3xqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMu:OqVwMMMMMMMMMMMMMMMMMMMMMMMMMMMd", 44)]
[InlineData("12:Oqkg3xqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMu:OqVwMMMMMMMMMMMMMMMMMMMMMMMMMMMd", "12:Y+VH/3Ckg3xqMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMn:xHqVwMMMMMMMMMMMMMMMMMMMMMMMMMM0", 44)]
// Large data
[InlineData("196608:Gbxf3F4OQK3IuUGM8Ylv1kqCLuDKeo5cRld6iZL6HAGpX7g08WCWDc4NNgs4NEv:qcgxU+UxR2gl5qAGpXjHDcCNgs4N", "196608:EqKRzGWxtDOadbDCbZStQxNy+fox3UgOYorlhjolL0K1WJj5lYA:EbNf76db9xNVox3MRlh+sf", 0)]
[InlineData("196608:EqKRzGWxtDOadbDCbZStQxNy+fox3UgOYorlhjolL0K1WJj5lYA:EbNf76db9xNVox3MRlh+sf", "196608:Gbxf3F4OQK3IuUGM8Ylv1kqCLuDKeo5cRld6iZL6HAGpX7g08WCWDc4NNgs4NEv:qcgxU+UxR2gl5qAGpXjHDcCNgs4N", 0)]
[InlineData("24576:p+QxhkAcV6cUdRxczoy3NmO0ne3HFVjSeQ229SVjeONr+v:YQ/q6baz5Nqe3H2eQzStBa", "24576:fCQxhkAcV6cUdRxczoyVQQFDSVRNihk24vXDj20sq:6Q/q6bazwMgRNihk24jtsq", 54)]
[InlineData("24576:fCQxhkAcV6cUdRxczoyVQQFDSVRNihk24vXDj20sq:6Q/q6bazwMgRNihk24jtsq", "24576:p+QxhkAcV6cUdRxczoy3NmO0ne3HFVjSeQ229SVjeONr+v:YQ/q6baz5Nqe3H2eQzStBa", 54)]
// Duplicate sequence truncation
[InlineData("500:AAAAAAAAAAAAAAAAAAAAAAAAyENFACBE+rW6Tj7SMQmK:4", "500:AAAyENFACBE+rW6Tj7SMQmK:4", 100)]
// Trailing data ignored
[InlineData("6:l+lq/MtlM8pJ0gt6lXWogE61UlT1Uqj1akMD5n:l+l6Mtl/n0gtOXmEuUl5UqpakM9n,ANYTHING", "6:mTj3qJskr+V+1o21+n0rtD2noPWKlAyjllZmMt6120EK+wlsS6T1oLwXuk4tk7:m/bk/1oQrJL3jTu20EK+wlsp5oO4tk7,NOTHING", 0)]
[InlineData("6:mTj3qJskr+V+1o21+n0rtD2noPWKlAyjllZmMt6120EK+wlsS6T1oLwXuk4tk7:m/bk/1oQrJL3jTu20EK+wlsp5oO4tk7,NOTHING", "6:l+lq/MtlM8pJ0gt6lXWogE61UlT1Uqj1akMD5n:l+l6Mtl/n0gtOXmEuUl5UqpakM9n,ANYTHING", 0)]
// Rolling window - larger than 7
[InlineData("500:7SMQmKa:3", "500:7SMQmKr:3", 0)]
// Rolling window - smaller than 7
[InlineData("500:7QmKa:3", "500:7QmKr:3", 0)]
// Blocksize differences
[InlineData("9287:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", "5893:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", 0)]
[InlineData("3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", "3:hMCPQCE6AFQxWyENFACBE+rW6Tj7SMQmKozr9MVERkL:hZRdxZENFs+rPSromekL", 100)]
public void FuzzyCompareTest(string? stringOne, string? stringTwo, int expected)
{
var result = SpamSum.SpamSum.FuzzyCompare(stringOne, stringTwo);
Assert.Equal(expected, result);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace SabreTools.Hashing.Test
/// <summary>
/// Helper class for tests
/// </summary>
/// CRC values confirmed with <see href="https://emn178.github.io/online-tools/crc/"/>
/// CRC values confirmed with <see href="https://emn178.github.io/online-tools/crc/"/>
internal static class TestHelper
{
#region Known File Information
@@ -143,6 +143,7 @@ namespace SabreTools.Hashing.Test
{HashType.CRC32_BZIP2, "18aa4603"},
{HashType.CRC32_CDROMEDC, "b8ced467"},
{HashType.CRC32_CKSUM, "f27b3c27"},
{HashType.CRC32_DVDROMEDC, "b538afc0"},
{HashType.CRC32_ISCSI, "544d37db"},
{HashType.CRC32_ISOHDLC, "ba02a660"},
{HashType.CRC32_JAMCRC, "45fd599f"},
@@ -172,6 +173,8 @@ namespace SabreTools.Hashing.Test
{HashType.FNV1a_32, "9086769b"},
{HashType.FNV1a_64, "399dd1cd965b73db"},
{HashType.MekaCrc, "0a0a0b1174052f22"},
{HashType.MD2, "362e1a6931668e6a9de5c159c52c71b5"},
{HashType.MD4, "61bef59d7a754874fccbd67b4ec2fb10"},
{HashType.MD5, "b722871eaa950016296184d026c5dec9"},
@@ -242,4 +245,4 @@ namespace SabreTools.Hashing.Test
public static void ValidateSize(long fileSize)
=> Assert.Equal(_hashFileSize, fileSize);
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
@@ -10,15 +9,15 @@ namespace SabreTools.Hashing.Test
/// <summary>
/// Get an array of all hash types
/// </summary>
public static List<object[]> AllHashTypes
public static TheoryData<HashType> AllHashTypes
{
get
{
var values = Enum.GetValues(typeof(HashType));
var set = new List<object[]>();
var values = Enum.GetValues<HashType>();
var set = new TheoryData<HashType>();
foreach (var value in values)
{
set.Add([value]);
set.Add(value);
}
return set;
@@ -46,4 +45,4 @@ namespace SabreTools.Hashing.Test
Assert.Equal(expected, actual);
}
}
}
}

View File

@@ -7,22 +7,56 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Hashing", "Sabre
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Hashing.Test", "SabreTools.Hashing.Test\SabreTools.Hashing.Test.csproj", "{A2BCBFDE-685B-4817-B724-050A99E02601}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hasher", "Hasher\Hasher.csproj", "{5DAC74F2-22AB-409B-B828-2FD3851586A3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|x64.ActiveCfg = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|x64.Build.0 = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|x86.ActiveCfg = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Debug|x86.Build.0 = Debug|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|Any CPU.Build.0 = Release|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|x64.ActiveCfg = Release|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|x64.Build.0 = Release|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|x86.ActiveCfg = Release|Any CPU
{F7E34528-080E-4E60-B9D1-8ADF70A24BB0}.Release|x86.Build.0 = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|x64.ActiveCfg = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|x64.Build.0 = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|x86.ActiveCfg = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Debug|x86.Build.0 = Debug|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|Any CPU.Build.0 = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|x64.ActiveCfg = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|x64.Build.0 = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|x86.ActiveCfg = Release|Any CPU
{A2BCBFDE-685B-4817-B724-050A99E02601}.Release|x86.Build.0 = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|x64.ActiveCfg = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|x64.Build.0 = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|x86.ActiveCfg = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Debug|x86.Build.0 = Debug|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|Any CPU.Build.0 = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|x64.ActiveCfg = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|x64.Build.0 = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|x86.ActiveCfg = Release|Any CPU
{5DAC74F2-22AB-409B-B828-2FD3851586A3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,579 +0,0 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : SpamSumContext.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Implements the SpamSum fuzzy hashing algorithm.
//
// --[ License ] --------------------------------------------------------------
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2023 Natalia Portillo
// ****************************************************************************/
// Based on ssdeep
// Copyright (C) 2002 Andrew Tridgell <tridge@samba.org>
// Copyright (C) 2006 ManTech International Corporation
// Copyright (C) 2013 Helmut Grohne <helmut@subdivi.de>
//
// Earlier versions of this code were named fuzzy.c and can be found at:
// http://www.samba.org/ftp/unpacked/junkcode/spamsum/
// http://ssdeep.sf.net/
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
using Aaru.CommonTypes.Interfaces;
namespace Aaru.Checksums;
/// <inheritdoc />
/// <summary>Implements the SpamSum fuzzy hashing algorithm.</summary>
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "OutParameterValueIsAlwaysDiscarded.Global")]
internal sealed class SpamSumContext : IChecksum
{
const uint ROLLING_WINDOW = 7;
const uint MIN_BLOCKSIZE = 3;
const uint HASH_PRIME = 0x01000193;
const uint HASH_INIT = 0x28021967;
const uint NUM_BLOCKHASHES = 31;
const uint SPAMSUM_LENGTH = 64;
const uint FUZZY_MAX_RESULT = 2 * SPAMSUM_LENGTH + 20;
//"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#if NET20 || NET35 || NET40 || NET452
readonly byte[] _b64 = Encoding.UTF8.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
#else
readonly byte[] _b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"u8.ToArray();
#endif
FuzzyState _self;
/// <summary>Initializes the SpamSum structures</summary>
public SpamSumContext()
{
_self = new FuzzyState
{
Bh = new BlockhashContext[NUM_BLOCKHASHES]
};
for (var i = 0; i < NUM_BLOCKHASHES; i++)
_self.Bh[i].Digest = new byte[SPAMSUM_LENGTH];
_self.Bhstart = 0;
_self.Bhend = 1;
_self.Bh[0].H = HASH_INIT;
_self.Bh[0].Halfh = HASH_INIT;
_self.Bh[0].Digest[0] = 0;
_self.Bh[0].Halfdigest = 0;
_self.Bh[0].Dlen = 0;
_self.TotalSize = 0;
roll_init();
}
#region IChecksum Members
/// <inheritdoc />
public string Name => "SpamSum";
/// <inheritdoc />
public Guid Id => new("DA692981-3291-47D8-B8B9-A87F0605F6E9");
/// <inheritdoc />
public string Author => "Natalia Portillo";
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
public void Update(byte[] data, uint len)
{
_self.TotalSize += len;
for (var i = 0; i < len; i++)
fuzzy_engine_step(data[i]);
}
/// <inheritdoc />
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
public void Update(byte[] data) => Update(data, (uint)data.Length);
/// <inheritdoc />
/// <summary>Returns a byte array of the hash value.</summary>
public byte[] Final()
{
FuzzyDigest(out byte[] result);
return result;
}
/// <inheritdoc />
/// <summary>Returns a base64 representation of the hash value.</summary>
public string End()
{
FuzzyDigest(out byte[] result);
return CToString(result);
}
#endregion
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void roll_init() => _self.Roll = new RollState
{
Window = new byte[ROLLING_WINDOW]
};
/*
* a rolling hash, based on the Adler checksum. By using a rolling hash
* we can perform auto resynchronisation after inserts/deletes
* internally, h1 is the sum of the bytes in the window and h2
* is the sum of the bytes times the index
* h3 is a shift/xor based rolling hash, and is mostly needed to ensure that
* we can cope with large blocksize values
*/
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void roll_hash(byte c)
{
_self.Roll.H2 -= _self.Roll.H1;
_self.Roll.H2 += ROLLING_WINDOW * c;
_self.Roll.H1 += c;
_self.Roll.H1 -= _self.Roll.Window[_self.Roll.N % ROLLING_WINDOW];
_self.Roll.Window[_self.Roll.N % ROLLING_WINDOW] = c;
_self.Roll.N++;
/* The original spamsum AND'ed this value with 0xFFFFFFFF which
* in theory should have no effect. This AND has been removed
* for performance (jk) */
_self.Roll.H3 <<= 5;
_self.Roll.H3 ^= c;
}
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
uint roll_sum() => _self.Roll.H1 + _self.Roll.H2 + _self.Roll.H3;
/* A simple non-rolling hash, based on the FNV hash. */
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static uint sum_hash(byte c, uint h) => h * HASH_PRIME ^ c;
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static uint SSDEEP_BS(uint index) => MIN_BLOCKSIZE << (int)index;
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void fuzzy_try_fork_blockhash()
{
switch (_self.Bhend)
{
case >= NUM_BLOCKHASHES:
return;
// assert
case 0:
throw new Exception("Assertion failed");
}
uint obh = _self.Bhend - 1;
uint nbh = _self.Bhend;
_self.Bh[nbh].H = _self.Bh[obh].H;
_self.Bh[nbh].Halfh = _self.Bh[obh].Halfh;
_self.Bh[nbh].Digest[0] = 0;
_self.Bh[nbh].Halfdigest = 0;
_self.Bh[nbh].Dlen = 0;
++_self.Bhend;
}
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void fuzzy_try_reduce_blockhash()
{
if (_self.Bhstart >= _self.Bhend)
throw new Exception("Assertion failed");
if (_self.Bhend - _self.Bhstart < 2)
/* Need at least two working hashes. */
return;
if ((ulong)SSDEEP_BS(_self.Bhstart) * SPAMSUM_LENGTH >= _self.TotalSize)
/* Initial blocksize estimate would select this or a smaller
* blocksize. */
return;
if (_self.Bh[_self.Bhstart + 1].Dlen < SPAMSUM_LENGTH / 2)
/* Estimate adjustment would select this blocksize. */
return;
/* At this point we are clearly no longer interested in the
* start_blocksize. Get rid of it. */
++_self.Bhstart;
}
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void fuzzy_engine_step(byte c)
{
uint i;
/* At each character we update the rolling hash and the normal hashes.
* When the rolling hash hits a reset value then we emit a normal hash
* as a element of the signature and reset the normal hash. */
roll_hash(c);
ulong h = roll_sum();
for (i = _self.Bhstart; i < _self.Bhend; ++i)
{
_self.Bh[i].H = sum_hash(c, _self.Bh[i].H);
_self.Bh[i].Halfh = sum_hash(c, _self.Bh[i].Halfh);
}
for (i = _self.Bhstart; i < _self.Bhend; ++i)
{
/* With growing blocksize almost no runs fail the next test. */
if (h % SSDEEP_BS(i) != SSDEEP_BS(i) - 1)
/* Once this condition is false for one bs, it is
* automatically false for all further bs. I.e. if
* h === -1 (mod 2*bs) then h === -1 (mod bs). */
break;
/* We have hit a reset point. We now emit hashes which are
* based on all characters in the piece of the message between
* the last reset point and this one */
if (0 == _self.Bh[i].Dlen)
fuzzy_try_fork_blockhash();
_self.Bh[i].Digest[_self.Bh[i].Dlen] = _b64[_self.Bh[i].H % 64];
_self.Bh[i].Halfdigest = _b64[_self.Bh[i].Halfh % 64];
if (_self.Bh[i].Dlen < SPAMSUM_LENGTH - 1)
{
/* We can have a problem with the tail overflowing. The
* easiest way to cope with this is to only reset the
* normal hash if we have room for more characters in
* our signature. This has the effect of combining the
* last few pieces of the message into a single piece
* */
_self.Bh[i].Digest[++_self.Bh[i].Dlen] = 0;
_self.Bh[i].H = HASH_INIT;
if (_self.Bh[i].Dlen >= SPAMSUM_LENGTH / 2)
continue;
_self.Bh[i].Halfh = HASH_INIT;
_self.Bh[i].Halfdigest = 0;
}
else
fuzzy_try_reduce_blockhash();
}
}
// CLAUNIA: Flags seems to never be used in ssdeep, so I just removed it for code simplicity
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
void FuzzyDigest(out byte[] result)
{
var sb = new StringBuilder();
uint bi = _self.Bhstart;
uint h = roll_sum();
var remain = (int)(FUZZY_MAX_RESULT - 1); /* Exclude terminating '\0'. */
result = new byte[FUZZY_MAX_RESULT];
/* Verify that our elimination was not overeager. */
if (!(bi == 0 || (ulong)SSDEEP_BS(bi) / 2 * SPAMSUM_LENGTH < _self.TotalSize))
throw new Exception("Assertion failed");
/* Initial blocksize guess. */
while ((ulong)SSDEEP_BS(bi) * SPAMSUM_LENGTH < _self.TotalSize)
{
++bi;
if (bi >= NUM_BLOCKHASHES)
throw new OverflowException("The input exceeds data types");
}
/* Adapt blocksize guess to actual digest length. */
while (bi >= _self.Bhend)
--bi;
while (bi > _self.Bhstart && _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2)
--bi;
if (bi > 0 && _self.Bh[bi].Dlen < SPAMSUM_LENGTH / 2)
throw new Exception("Assertion failed");
sb.Append($"{SSDEEP_BS(bi)}:");
int i = Encoding.ASCII.GetBytes(sb.ToString()).Length;
if (i <= 0)
/* Maybe snprintf has set errno here? */
throw new OverflowException("The input exceeds data types");
if (i >= remain)
throw new Exception("Assertion failed");
remain -= i;
Array.Copy(Encoding.ASCII.GetBytes(sb.ToString()), 0, result, 0, i);
int resultOff = i;
i = (int)_self.Bh[bi].Dlen;
if (i > remain)
throw new Exception("Assertion failed");
Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i);
resultOff += i;
remain -= i;
if (h != 0)
{
if (remain <= 0)
throw new Exception("Assertion failed");
result[resultOff] = _b64[_self.Bh[bi].H % 64];
if (i < 3 ||
result[resultOff] != result[resultOff - 1] ||
result[resultOff] != result[resultOff - 2] ||
result[resultOff] != result[resultOff - 3])
{
++resultOff;
--remain;
}
}
else if (_self.Bh[bi].Digest[i] != 0)
{
if (remain <= 0)
throw new Exception("Assertion failed");
result[resultOff] = _self.Bh[bi].Digest[i];
if (i < 3 ||
result[resultOff] != result[resultOff - 1] ||
result[resultOff] != result[resultOff - 2] ||
result[resultOff] != result[resultOff - 3])
{
++resultOff;
--remain;
}
}
if (remain <= 0)
throw new Exception("Assertion failed");
result[resultOff++] = 0x3A; // ':'
--remain;
if (bi < _self.Bhend - 1)
{
++bi;
i = (int)_self.Bh[bi].Dlen;
if (i > remain)
throw new Exception("Assertion failed");
Array.Copy(_self.Bh[bi].Digest, 0, result, resultOff, i);
resultOff += i;
remain -= i;
if (h != 0)
{
if (remain <= 0)
throw new Exception("Assertion failed");
h = _self.Bh[bi].Halfh;
result[resultOff] = _b64[h % 64];
if (i < 3 ||
result[resultOff] != result[resultOff - 1] ||
result[resultOff] != result[resultOff - 2] ||
result[resultOff] != result[resultOff - 3])
{
++resultOff;
--remain;
}
}
else
{
i = _self.Bh[bi].Halfdigest;
if (i != 0)
{
if (remain <= 0)
throw new Exception("Assertion failed");
result[resultOff] = (byte)i;
if (i < 3 ||
result[resultOff] != result[resultOff - 1] ||
result[resultOff] != result[resultOff - 2] ||
result[resultOff] != result[resultOff - 3])
{
++resultOff;
--remain;
}
}
}
}
else if (h != 0)
{
if (_self.Bh[bi].Dlen != 0)
throw new Exception("Assertion failed");
if (remain <= 0)
throw new Exception("Assertion failed");
result[resultOff++] = _b64[_self.Bh[bi].H % 64];
/* No need to bother with FUZZY_FLAG_ELIMSEQ, because this
* digest has length 1. */
--remain;
}
result[resultOff] = 0;
}
/// <summary>Gets the hash of a file</summary>
/// <param name="filename">File path.</param>
public static byte[] File(string filename) =>
throw new NotImplementedException("SpamSum does not have a binary representation.");
/// <summary>Gets the hash of a file in hexadecimal and as a byte array.</summary>
/// <param name="filename">File path.</param>
/// <param name="hash">Byte array of the hash value.</param>
public static string File(string filename, out byte[] hash) =>
throw new NotImplementedException("Not yet implemented.");
/// <summary>Gets the hash of the specified data buffer.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of the data buffer to hash.</param>
/// <param name="hash">null</param>
/// <returns>Base64 representation of SpamSum $blocksize:$hash:$hash</returns>
public static string Data(byte[] data, uint len, out byte[]? hash)
{
var fuzzyContext = new SpamSumContext();
fuzzyContext.Update(data, len);
hash = null;
return fuzzyContext.End();
}
/// <summary>Gets the hash of the specified data buffer.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="hash">null</param>
/// <returns>Base64 representation of SpamSum $blocksize:$hash:$hash</returns>
public static string Data(byte[] data, out byte[]? hash) => Data(data, (uint)data.Length, out hash);
// Converts an ASCII null-terminated string to .NET string
#if NET452_OR_GREATER || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
static string CToString(byte[] cString)
{
var count = 0;
// ReSharper disable once LoopCanBeConvertedToQuery
// LINQ is six times slower
foreach (byte c in cString)
{
if (c == 0)
break;
count++;
}
return Encoding.ASCII.GetString(cString, 0, count);
}
#region Nested type: BlockhashContext
/* A blockhash contains a signature state for a specific (implicit) blocksize.
* The blocksize is given by SSDEEP_BS(index). The h and halfh members are the
* FNV hashes, where halfh stops to be reset after digest is SPAMSUM_LENGTH/2
* long. The halfh hash is needed be able to truncate digest for the second
* output hash to stay compatible with ssdeep output. */
struct BlockhashContext
{
public uint H;
public uint Halfh;
public byte[] Digest;
// SPAMSUM_LENGTH
public byte Halfdigest;
public uint Dlen;
}
#endregion
#region Nested type: FuzzyState
struct FuzzyState
{
public uint Bhstart;
public uint Bhend;
public BlockhashContext[] Bh;
//NUM_BLOCKHASHES
public ulong TotalSize;
public RollState Roll;
}
#endregion
#region Nested type: RollState
struct RollState
{
public byte[] Window;
// ROLLING_WINDOW
public uint H1;
public uint H2;
public uint H3;
public uint N;
}
#endregion
}

View File

@@ -1,69 +0,0 @@
// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename : IChecksum.cs
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// Component : Checksums.
//
// --[ Description ] ----------------------------------------------------------
//
// Provides an interface for implementing checksums and hashes.
//
// --[ License ] --------------------------------------------------------------
//
// 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.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2023 Natalia Portillo
// ****************************************************************************/
using System;
namespace Aaru.CommonTypes.Interfaces;
/// <summary>Defines the interface to implement a checksum or hashing algorithm</summary>
internal interface IChecksum
{
/// <summary>Plugin author</summary>
string Author { get; }
/// <summary>Plugin name.</summary>
string Name { get; }
/// <summary>Plugin UUID.</summary>
Guid Id { get; }
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
/// <param name="len">Length of buffer to hash.</param>
void Update(byte[] data, uint len);
/// <summary>Updates the hash with data.</summary>
/// <param name="data">Data buffer.</param>
void Update(byte[] data);
/// <summary>Returns a byte array of the hash value.</summary>
byte[] Final();
/// <summary>Returns a hexadecimal representation of the hash value.</summary>
string End();
}

View File

@@ -3,24 +3,27 @@ using static SabreTools.Hashing.Checksum.Constants;
namespace SabreTools.Hashing.Checksum
{
/// <see href="https://github.com/madler/zlib/blob/v1.2.11/adler32.c"/>
/// <see href="https://github.com/madler/zlib/blob/v1.2.11/adler32.c"/>
public class Adler32 : ChecksumBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 32;
public Adler32()
{
Reset();
Initialize();
}
/// <summary>
/// Reset the internal hashing state
/// </summary>
public override void Reset()
public override void Initialize()
{
_hash = 1;
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Split Adler-32 into component sums
uint sum2 = (_hash >> 16) & 0xffff;
@@ -131,9 +134,11 @@ namespace SabreTools.Hashing.Checksum
}
/// <inheritdoc/>
public override byte[] Finalize()
protected override byte[] HashFinal()
{
return BitConverter.GetBytes(_hash);
byte[] hashArr = BitConverter.GetBytes(_hash);
Array.Reverse(hashArr);
return hashArr;
}
}
}
}

View File

@@ -19,4 +19,4 @@ namespace SabreTools.Hashing.Checksum
return bytes;
}
}
}
}

View File

@@ -5,20 +5,9 @@ namespace SabreTools.Hashing.Checksum
/// <summary>
/// Common base class for Fletcher checksums
/// </summary>
public abstract class ChecksumBase
public abstract class ChecksumBase : System.Security.Cryptography.HashAlgorithm
{
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="offset">Offset in the byte array to include</param>
/// <param name="length">Length of the data to hash</param>
public abstract void TransformBlock(byte[] data, int offset, int length);
/// <summary>
/// Finalize the hash and return as a byte array
/// </summary>
public abstract byte[] Finalize();
// No common, untyped functionality
}
/// <summary>
@@ -31,18 +20,16 @@ namespace SabreTools.Hashing.Checksum
/// </summary>
protected T _hash;
/// <summary>
/// Reset the internal hashing state
/// </summary>
public virtual void Reset()
/// <inheritdoc/>
public override void Initialize()
{
_hash = default;
}
/// <inheritdoc/>
public override byte[] Finalize()
protected override byte[] HashFinal()
{
return _hash switch
byte[] hashArr = _hash switch
{
short s => BitConverter.GetBytes(s),
ushort s => BitConverter.GetBytes(s),
@@ -55,6 +42,9 @@ namespace SabreTools.Hashing.Checksum
_ => [],
};
Array.Reverse(hashArr);
return hashArr;
}
}
}
}

View File

@@ -2,7 +2,7 @@ namespace SabreTools.Hashing.Checksum
{
internal static class Constants
{
#region Adler-32 / Fletcher-32
#region Adler-32
/// <summary>
/// Largest prime smaller than 65536
@@ -14,6 +14,10 @@ namespace SabreTools.Hashing.Checksum
/// </summary>
public const ushort A32NMAX = 5552;
#endregion
#region Fletcher-32
/// <summary>
/// Max value for a single half of a Fletcher-32 checksum
/// </summary>
@@ -25,15 +29,5 @@ namespace SabreTools.Hashing.Checksum
public const uint F64BASE = 0xffffffff;
#endregion
#region FNV
public const uint FNV32Basis = 0x811c9dc5;
public const ulong FNV64Basis = 0xcbf29ce484222325;
public const uint FNV32Prime = 0x01000193;
public const ulong FNV64Prime = 0x00000100000001b3;
#endregion
}
}
}

View File

@@ -5,6 +5,9 @@ namespace SabreTools.Hashing.Checksum
{
public class Crc : ChecksumBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => Def.Width;
/// <summary>
/// Definition used to create the runner
/// </summary>
@@ -27,17 +30,17 @@ namespace SabreTools.Hashing.Checksum
}
/// <inheritdoc/>
public override void Reset()
public override void Initialize()
{
_hash = Def.Init;
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
=> _table.TransformBlock(ref _hash, data, offset, length);
/// <inheritdoc/>
public override byte[] Finalize()
protected override byte[] HashFinal()
{
// Create a copy of the hash
ulong localHash = _hash;
@@ -53,4 +56,4 @@ namespace SabreTools.Hashing.Checksum
return BitOperations.ClampValueToBytes(localHash, Def.Width);
}
}
}
}

View File

@@ -62,4 +62,4 @@ namespace SabreTools.Hashing.Checksum
/// </summary>
public ulong XorOut { get; set; }
}
}
}

View File

@@ -204,17 +204,17 @@ namespace SabreTools.Hashing.Checksum
while (offset < end)
{
ulong low = local ^ (uint)(
(data[offset + 0])
data[offset + 0]
+ (data[offset + 1] << 8)
+ (data[offset + 2] << 16)
+ (data[offset + 3] << 24));
offset += 4;
local = _table[3, (byte)(low)]
local = _table[3, (byte)low]
^ _table[2, (byte)(low >> 8)]
^ _table[1, (byte)(low >> 16)]
^ _table[0, (byte)(low >> 24)]
^ local >> 32;
^ (local >> 32);
}
}
@@ -246,26 +246,26 @@ namespace SabreTools.Hashing.Checksum
while (offset < end)
{
ulong low = local ^ (uint)(
(data[offset + 0])
data[offset + 0]
+ (data[offset + 1] << 8)
+ (data[offset + 2] << 16)
+ (data[offset + 3] << 24));
ulong high = (uint)(
(data[offset + 4])
data[offset + 4]
+ (data[offset + 5] << 8)
+ (data[offset + 6] << 16)
+ (data[offset + 7] << 24));
offset += 8;
local = _table[7, (byte)(low)]
local = _table[7, (byte)low]
^ _table[6, (byte)(low >> 8)]
^ _table[5, (byte)(low >> 16)]
^ _table[4, (byte)(low >> 24)]
^ _table[3, (byte)(high)]
^ _table[3, (byte)high]
^ _table[2, (byte)(high >> 8)]
^ _table[1, (byte)(high >> 16)]
^ _table[0, (byte)(high >> 24)]
^ local >> 32;
^ (local >> 32);
}
}
@@ -297,26 +297,26 @@ namespace SabreTools.Hashing.Checksum
while (offset < end)
{
ulong low = local ^ (uint)(
(data[offset + 3])
data[offset + 3]
+ (data[offset + 2] << 8)
+ (data[offset + 1] << 16)
+ (data[offset + 0] << 24));
ulong high = (uint)(
(data[offset + 7])
data[offset + 7]
+ (data[offset + 6] << 8)
+ (data[offset + 5] << 16)
+ (data[offset + 4] << 24));
offset += 8;
local = _table[4, (byte)(low)]
local = _table[4, (byte)low]
^ _table[5, (byte)(low >> 8)]
^ _table[6, (byte)(low >> 16)]
^ _table[7, (byte)(low >> 24)]
^ _table[0, (byte)(high)]
^ _table[0, (byte)high]
^ _table[1, (byte)(high >> 8)]
^ _table[2, (byte)(high >> 16)]
^ _table[3, (byte)(high >> 24)]
^ local << 32;
^ (local << 32);
}
}
@@ -330,4 +330,4 @@ namespace SabreTools.Hashing.Checksum
hash = local;
}
}
}
}

View File

@@ -1,19 +1,22 @@
namespace SabreTools.Hashing.Checksum
{
/// <see href="https://en.wikipedia.org/wiki/Fletcher%27s_checksum#Optimizations"/>
/// <see href="https://en.wikipedia.org/wiki/Fletcher%27s_checksum#Optimizations"/>
public class Fletcher16 : ChecksumBase<ushort>
{
/// <inheritdoc/>
public override int HashSize => 16;
public Fletcher16()
{
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Split the existing hash
uint c0 = (uint)(_hash & 0x00FF);
uint c1 = (uint)(_hash << 16);
uint c0 = (uint)(_hash & 0x00ff);
uint c1 = (uint)((_hash >> 8) & 0x00ff);
// Found by solving for c1 overflow:
// n > 0 and n * (n+1) / 2 * (2^8-1) < (2^32-1).
@@ -34,7 +37,8 @@ namespace SabreTools.Hashing.Checksum
c1 %= 255;
}
_hash = (ushort)(c1 << 8 | c0);
// Return recombined sums
_hash = (ushort)((c1 << 8) | c0);
}
}
}
}

View File

@@ -2,17 +2,20 @@ using static SabreTools.Hashing.Checksum.Constants;
namespace SabreTools.Hashing.Checksum
{
/// <see href="https://en.wikipedia.org/wiki/Fletcher%27s_checksum#Optimizations"/>
/// <see href="https://en.wikipedia.org/wiki/Fletcher%27s_checksum#Optimizations"/>
/// <remarks>Uses an Adler-32-like implementation instead of the above</remarks>
public class Fletcher32 : ChecksumBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 32;
public Fletcher32()
{
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Split Fletcher-32 into component sums
uint c0 = _hash & 0xffff;
@@ -29,7 +32,7 @@ namespace SabreTools.Hashing.Checksum
if (c1 >= F32BASE)
c1 -= F32BASE;
_hash = (uint)((c1 << 16) | c0);
_hash = (c1 << 16) | c0;
return;
}
@@ -47,7 +50,7 @@ namespace SabreTools.Hashing.Checksum
// Only added so many BASE's
c1 %= F32BASE;
_hash = (uint)((c1 << 16) | c0);
_hash = (c1 << 16) | c0;
return;
}
@@ -122,4 +125,4 @@ namespace SabreTools.Hashing.Checksum
_hash = (c1 << 16) | c0;
}
}
}
}

View File

@@ -6,13 +6,16 @@ namespace SabreTools.Hashing.Checksum
/// <remarks>Uses an Adler-32-like implementation instead of the above</remarks>
public class Fletcher64 : ChecksumBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => 64;
public Fletcher64()
{
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Split Fletcher-64 into component sums
ulong c0 = _hash & 0xffffffff;
@@ -122,4 +125,4 @@ namespace SabreTools.Hashing.Checksum
_hash = (c1 << 32) | c0;
}
}
}
}

View File

@@ -1,21 +0,0 @@
namespace SabreTools.Hashing.Checksum
{
public abstract class FnvBase<T> : ChecksumBase<T> where T : struct
{
/// <summary>
/// Initial value to use
/// </summary>
protected T _basis;
/// <summary>
/// Round prime to use
/// </summary>
protected T _prime;
/// <inheritdoc/>
public override void Reset()
{
_hash = _basis;
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
namespace SabreTools.Hashing.Checksum
{
/// <see href="https://github.com/ocornut/meka/blob/master/meka/srcs/checksum.cpp"/>
public class MekaCrc : ChecksumBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => 64;
public MekaCrc()
{
Initialize();
}
/// <summary>
/// Reset the internal hashing state
/// </summary>
public override void Initialize()
{
_hash = 0;
}
/// <inheritdoc/>
/// <remarks>The original code limits the maximum processed size to 8KiB</remarks>
protected override void HashCore(byte[] data, int offset, int length)
{
// Read the current hash into a byte array
byte[] temp = BitConverter.GetBytes(_hash);
// Loop over the input and process
for (int i = 0; i < length; i++)
{
byte v = data[offset + i];
unchecked
{
temp[v & 7]++;
temp[v >> 5]++;
}
}
// Convert the hash back into a value
_hash = BitConverter.ToUInt64(temp, 0);
}
}
}

View File

@@ -1481,6 +1481,20 @@ namespace SabreTools.Hashing.Checksum
XorOut = 0xffffffff,
};
/// <summary>
/// CRC-32/DVD-ROM-EDC
/// </summary>
public static readonly CrcDefinition CRC32_DVDROMEDC = new()
{
Name = "CRC-32/DVD-ROM-EDC",
Width = 32,
Poly = 0x80000011,
Init = 0x00000000,
ReflectIn = false,
ReflectOut = false,
XorOut = 0x00000000,
};
/// <summary>
/// CRC-32/ISCSI
/// </summary>
@@ -1687,4 +1701,4 @@ namespace SabreTools.Hashing.Checksum
#endregion
}
}
}

View File

@@ -19,4 +19,4 @@ namespace SabreTools.Hashing
#endregion
}
}
}

View File

@@ -1,4 +1,4 @@
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
internal static class Constants
{
@@ -694,4 +694,4 @@ namespace SabreTools.Hashing.MessageDigest
#endregion
}
}
}

View File

@@ -1,11 +1,14 @@
using System;
using static SabreTools.Hashing.MessageDigest.Constants;
using static SabreTools.Hashing.CryptographicHash.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://datatracker.ietf.org/doc/html/rfc1115"/>
public class MD2 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 128;
/// <summary>
/// Buffer for forming digest in
/// </summary>
@@ -41,7 +44,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = 16 - _byteCount;
@@ -116,14 +119,14 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
byte padLength = (byte)(16 - _byteCount);
// Pad the block
byte[] padding = new byte[padLength];
#if NETFRAMEWORK
#if NETFRAMEWORK || NETSTANDARD2_0
for (int i = 0; i < padLength; i++)
{
padding[i] = padLength;
@@ -131,18 +134,14 @@ namespace SabreTools.Hashing.MessageDigest
#else
Array.Fill(padding, padLength);
#endif
TransformBlock(padding, 0, padLength);
TransformBlock(_checksum, 0, _checksum.Length);
}
/// <inheritdoc/>
public override byte[] GetHash()
{
// Pad the block
HashCore(padding, 0, padLength);
HashCore(_checksum, 0, _checksum.Length);
// Get the hash
var hash = new byte[16];
Array.Copy(_digest, hash, 16);
// Reset the state and return
Reset();
return hash;
}
@@ -167,4 +166,4 @@ namespace SabreTools.Hashing.MessageDigest
}
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://datatracker.ietf.org/doc/html/rfc1320"/>
public class MD4 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 128;
/// <summary>
/// Set of 4 32-bit numbers representing the hash state
/// </summary>
@@ -26,7 +29,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -81,7 +84,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -104,12 +107,9 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[16];
int hashOffset = 0;
@@ -121,8 +121,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 4;
}
// Reset the state and return
Reset();
return hash;
}
@@ -213,4 +211,4 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
private static uint H(uint x, uint y, uint z) => x ^ y ^ z;
}
}
}

View File

@@ -1,8 +1,8 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
public abstract class MessageDigestBase
public abstract class MessageDigestBase : System.Security.Cryptography.HashAlgorithm
{
/// <summary>
/// Total number of bytes processed
@@ -10,7 +10,7 @@ namespace SabreTools.Hashing.MessageDigest
protected long _totalBytes;
/// <summary>
/// Internal byte buffer to accumulate before <see cref="_block"/>
/// Internal byte buffer to accumulate before <see cref="_block"/>
/// </summary>
protected readonly byte[] _buffer = new byte[64];
@@ -19,29 +19,8 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
protected abstract void ResetImpl();
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="offset">Offset in the byte array to include</param>
/// <param name="length">Length of the data to hash</param>
public abstract void TransformBlock(byte[] data, int offset, int length);
/// <summary>
/// End the hashing process
/// </summary>
/// TODO: Combine this when the padding byte can be set by implementing classes
public abstract void Terminate();
/// <summary>
/// Get the current value of the hash
/// </summary>
/// <remarks>
/// If <see cref="Terminate"/> has not been run, this value
/// will not be accurate for the processed bytes so far.
/// </remarks>
/// TODO: Combine this when there's an easier way of passing the state
public abstract byte[] GetHash();
/// <inheritdoc/>
protected abstract override void HashCore(byte[] array, int ibStart, int cbSize);
}
public abstract class MessageDigestBase<T> : MessageDigestBase where T : struct
@@ -63,13 +42,11 @@ namespace SabreTools.Hashing.MessageDigest
else
throw new InvalidOperationException();
Reset();
Initialize();
}
/// <summary>
/// Reset the internal hashing state
/// </summary>
public void Reset()
/// <inheritdoc/>
public override void Initialize()
{
// Reset the seed values
ResetImpl();
@@ -82,4 +59,4 @@ namespace SabreTools.Hashing.MessageDigest
Array.Clear(_block, 0, _block.Length);
}
}
}
}

View File

@@ -1,13 +1,16 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://cdn.standards.iteh.ai/samples/39876/10f9f9f4bb614eaaaeba7e157e183ca3/ISO-IEC-10118-3-2004.pdf"/>
/// <see href="https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf"/>
public class RipeMD128 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 128;
/// <summary>
/// Set of 4 32-bit numbers representing the hash state
/// </summary>
@@ -27,7 +30,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -82,7 +85,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -105,12 +108,9 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[16];
int hashOffset = 0;
@@ -122,8 +122,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 4;
}
// Reset the state and return
Reset();
return hash;
}
@@ -134,7 +132,7 @@ namespace SabreTools.Hashing.MessageDigest
/// The official specification for RIPEMD-128 includes tables
/// and instructions that represent a loop. Most standard implementations
/// use the unrolled version of that loop to make it more efficient.
///
///
/// The below code started with the looped version but has been converted
/// to the more standard implementation instead.
/// </remarks>
@@ -446,4 +444,4 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
private static uint G48_63(uint x, uint y, uint z) => (x & z) | (y & ~z);
}
}
}

View File

@@ -1,13 +1,16 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://cdn.standards.iteh.ai/samples/39876/10f9f9f4bb614eaaaeba7e157e183ca3/ISO-IEC-10118-3-2004.pdf"/>
/// <see href="https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf"/>
public class RipeMD160 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 160;
/// <summary>
/// Set of 5 32-bit numbers representing the hash state
/// </summary>
@@ -28,7 +31,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -83,7 +86,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -102,16 +105,13 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 4] = (byte)((totalBitCount >> 32) & 0xff);
padding[padLength - 5] = (byte)((totalBitCount >> 24) & 0xff);
padding[padLength - 6] = (byte)((totalBitCount >> 16) & 0xff);
padding[padLength - 7] = (byte)((totalBitCount >> 8 ) & 0xff);
padding[padLength - 8] = (byte)((totalBitCount >> 0 ) & 0xff);
padding[padLength - 7] = (byte)((totalBitCount >> 8) & 0xff);
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[20];
int hashOffset = 0;
@@ -123,8 +123,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 4;
}
// Reset the state and return
Reset();
return hash;
}
@@ -135,7 +133,7 @@ namespace SabreTools.Hashing.MessageDigest
/// The official specification for RIPEMD-160 includes tables
/// and instructions that represent a loop. Most standard implementations
/// use the unrolled version of that loop to make it more efficient.
///
///
/// The below code started with the looped version but has been converted
/// to the more standard implementation instead.
/// </remarks>
@@ -682,4 +680,4 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
private static uint G64_79(uint x, uint y, uint z) => x ^ (y | ~z);
}
}
}

View File

@@ -1,13 +1,16 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://cdn.standards.iteh.ai/samples/39876/10f9f9f4bb614eaaaeba7e157e183ca3/ISO-IEC-10118-3-2004.pdf"/>
/// <see href="https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf"/>
public class RipeMD256 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 256;
/// <summary>
/// Set of 4 32-bit numbers representing the hash state
/// </summary>
@@ -31,7 +34,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -86,7 +89,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -109,12 +112,9 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[32];
int hashOffset = 0;
@@ -126,8 +126,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 4;
}
// Reset the state and return
Reset();
return hash;
}
@@ -138,7 +136,7 @@ namespace SabreTools.Hashing.MessageDigest
/// The official specification for RIPEMD-128 includes tables
/// and instructions that represent a loop. Most standard implementations
/// use the unrolled version of that loop to make it more efficient.
///
///
/// The below code started with the looped version but has been converted
/// to the more standard implementation instead.
/// </remarks>
@@ -466,4 +464,4 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
private static uint G48_63(uint x, uint y, uint z) => (x & z) | (y & ~z);
}
}
}

View File

@@ -1,13 +1,16 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://cdn.standards.iteh.ai/samples/39876/10f9f9f4bb614eaaaeba7e157e183ca3/ISO-IEC-10118-3-2004.pdf"/>
/// <see href="https://homes.esat.kuleuven.be/~bosselae/ripemd160/pdf/AB-9601/AB-9601.pdf"/>
public class RipeMD320 : MessageDigestBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 320;
/// <summary>
/// Set of 10 32-bit numbers representing the hash state
/// </summary>
@@ -33,7 +36,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -88,7 +91,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -107,16 +110,13 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 4] = (byte)((totalBitCount >> 32) & 0xff);
padding[padLength - 5] = (byte)((totalBitCount >> 24) & 0xff);
padding[padLength - 6] = (byte)((totalBitCount >> 16) & 0xff);
padding[padLength - 7] = (byte)((totalBitCount >> 8 ) & 0xff);
padding[padLength - 8] = (byte)((totalBitCount >> 0 ) & 0xff);
padding[padLength - 7] = (byte)((totalBitCount >> 8) & 0xff);
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[40];
int hashOffset = 0;
@@ -128,8 +128,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 4;
}
// Reset the state and return
Reset();
return hash;
}
@@ -140,7 +138,7 @@ namespace SabreTools.Hashing.MessageDigest
/// The official specification for RIPEMD-160 includes tables
/// and instructions that represent a loop. Most standard implementations
/// use the unrolled version of that loop to make it more efficient.
///
///
/// The below code started with the looped version but has been converted
/// to the more standard implementation instead.
/// </remarks>
@@ -707,4 +705,4 @@ namespace SabreTools.Hashing.MessageDigest
/// </summary>
private static uint G64_79(uint x, uint y, uint z) => x ^ (y | ~z);
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger-128
/// </summary>
public class Tiger128_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 128;
public Tiger128_3() : base()
{
_passes = 3;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[16];
Array.Copy(hash, trimmedHash, 16);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger-128
/// </summary>
public class Tiger128_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 128;
public Tiger128_4() : base()
{
_passes = 4;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[16];
Array.Copy(hash, trimmedHash, 16);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger-160
/// </summary>
public class Tiger160_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 160;
public Tiger160_3() : base()
{
_passes = 3;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[20];
Array.Copy(hash, trimmedHash, 20);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger-160
/// </summary>
public class Tiger160_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 160;
public Tiger160_4() : base()
{
_passes = 4;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[20];
Array.Copy(hash, trimmedHash, 20);
return trimmedHash;
}
}
}
}

View File

@@ -1,14 +1,17 @@
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger-192
/// </summary>
public class Tiger192_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 192;
public Tiger192_3() : base()
{
_passes = 3;
_padStart = 0x01;
}
}
}
}

View File

@@ -1,14 +1,17 @@
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger-192
/// </summary>
public class Tiger192_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 192;
public Tiger192_4() : base()
{
_passes = 4;
_padStart = 0x01;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger2-128
/// </summary>
public class Tiger2_128_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 128;
public Tiger2_128_3() : base()
{
_passes = 3;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[16];
Array.Copy(hash, trimmedHash, 16);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger2-128
/// </summary>
public class Tiger2_128_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 128;
public Tiger2_128_4() : base()
{
_passes = 4;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[16];
Array.Copy(hash, trimmedHash, 16);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger2-160
/// </summary>
public class Tiger2_160_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 160;
public Tiger2_160_3() : base()
{
_passes = 3;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[20];
Array.Copy(hash, trimmedHash, 20);
return trimmedHash;
}
}
}
}

View File

@@ -1,12 +1,15 @@
using System;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger2-160
/// </summary>
public class Tiger2_160_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 160;
public Tiger2_160_4() : base()
{
_passes = 4;
@@ -14,12 +17,12 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override byte[] GetHash()
protected override byte[] HashFinal()
{
byte[] hash = base.GetHash();
byte[] hash = base.HashFinal();
byte[] trimmedHash = new byte[20];
Array.Copy(hash, trimmedHash, 20);
return trimmedHash;
}
}
}
}

View File

@@ -1,14 +1,17 @@
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 3-pass variant of Tiger2-192
/// </summary>
public class Tiger2_192_3 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 192;
public Tiger2_192_3() : base()
{
_passes = 3;
_padStart = 0x80;
}
}
}
}

View File

@@ -1,14 +1,17 @@
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <summary>
/// 4-pass variant of Tiger2-192
/// </summary>
public class Tiger2_192_4 : TigerHashBase
{
/// <inheritdoc/>
public override int HashSize => 192;
public Tiger2_192_4() : base()
{
_passes = 4;
_padStart = 0x80;
}
}
}
}

View File

@@ -1,8 +1,8 @@
using System;
using static SabreTools.Hashing.CryptographicHash.Constants;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.MessageDigest.Constants;
namespace SabreTools.Hashing.MessageDigest
namespace SabreTools.Hashing.CryptographicHash
{
/// <see href="https://biham.cs.technion.ac.il/Reports/Tiger//>
public abstract class TigerHashBase : MessageDigestBase<ulong>
@@ -35,7 +35,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
// Figure out how much buffer is needed
int bufferLen = (int)(_totalBytes & 0x3f);
@@ -90,7 +90,7 @@ namespace SabreTools.Hashing.MessageDigest
}
/// <inheritdoc/>
public override void Terminate()
protected override byte[] HashFinal()
{
// Determine the pad length
int padLength = 64 - (int)(_totalBytes & 0x3f);
@@ -113,12 +113,9 @@ namespace SabreTools.Hashing.MessageDigest
padding[padLength - 8] = (byte)((totalBitCount >> 0) & 0xff);
// Pad the block
TransformBlock(padding, 0, padding.Length);
}
HashCore(padding, 0, padding.Length);
/// <inheritdoc/>
public override byte[] GetHash()
{
// Get the hash
var hash = new byte[24];
int hashOffset = 0;
@@ -130,8 +127,6 @@ namespace SabreTools.Hashing.MessageDigest
hashOffset += 8;
}
// Reset the state and return
Reset();
return hash;
}
@@ -239,4 +234,4 @@ namespace SabreTools.Hashing.MessageDigest
_block[7] -= _block[6] ^ 0x0123456789ABCDEF;
}
}
}
}

View File

@@ -0,0 +1,9 @@
#if NET20
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
internal sealed class ExtensionAttribute : Attribute {}
}
#endif

View File

@@ -0,0 +1,447 @@
namespace SabreTools.Hashing
{
public static class Extensions
{
/// <summary>
/// Get the name of a given hash type, if possible
/// </summary>
/// TODO: This should be automated instead of hardcoded
public static string? GetHashName(this HashType hashType)
{
return hashType switch
{
HashType.Adler32 => "Mark Adler's 32-bit checksum",
#if NET7_0_OR_GREATER
HashType.BLAKE3 => "BLAKE3 512-bit digest",
#endif
HashType.CRC1_ZERO => "CRC-1/ZERO [Parity bit with 0 start]",
HashType.CRC1_ONE => "CRC-1/ONE [Parity bit with 1 start]",
HashType.CRC3_GSM => "CRC-3/GSM",
HashType.CRC3_ROHC => "CRC-3/ROHC",
HashType.CRC4_G704 => "CRC-4/G-704 [CRC-4/ITU]",
HashType.CRC4_INTERLAKEN => "CRC-4/INTERLAKEN",
HashType.CRC5_EPCC1G2 => "CRC-5/EPC-C1G2 [CRC-5/EPC]",
HashType.CRC5_G704 => "CRC-5/G-704 [CRC-5/ITU]",
HashType.CRC5_USB => "CRC-5/USB",
HashType.CRC6_CDMA2000A => "CRC-6/CDMA2000-A",
HashType.CRC6_CDMA2000B => "CRC-6/CDMA2000-B",
HashType.CRC6_DARC => "CRC-6/DARC",
HashType.CRC6_G704 => "CRC-6/G-704 [CRC-6/ITU]",
HashType.CRC6_GSM => "CRC-6/GSM",
HashType.CRC7_MMC => "CRC-7/MMC [CRC-7]",
HashType.CRC7_ROHC => "CRC-7/ROHC",
HashType.CRC7_UMTS => "CRC-7/UMTS",
HashType.CRC8 => "CRC-8",
HashType.CRC8_AUTOSAR => "CRC-8/AUTOSAR",
HashType.CRC8_BLUETOOTH => "CRC-8/BLUETOOTH",
HashType.CRC8_CDMA2000 => "CRC-8/CDMA2000",
HashType.CRC8_DARC => "CRC-8/DARC",
HashType.CRC8_DVBS2 => "CRC-8/DVB-S2",
HashType.CRC8_GSMA => "CRC-8/GSM-A",
HashType.CRC8_GSMB => "CRC-8/GSM-B",
HashType.CRC8_HITAG => "CRC-8/HITAG",
HashType.CRC8_I4321 => "CRC-8/I-432-1 [CRC-8/ITU]",
HashType.CRC8_ICODE => "CRC-8/I-CODE",
HashType.CRC8_LTE => "CRC-8/LTE",
HashType.CRC8_MAXIMDOW => "CRC-8/MAXIM-DOW [CRC-8/MAXIM, DOW-CRC]",
HashType.CRC8_MIFAREMAD => "CRC-8/MIFARE-MAD",
HashType.CRC8_NRSC5 => "CRC-8/NRSC-5",
HashType.CRC8_OPENSAFETY => "CRC-8/OPENSAFETY",
HashType.CRC8_ROHC => "CRC-8/ROHC",
HashType.CRC8_SAEJ1850 => "CRC-8/SAE-J1850",
HashType.CRC8_SMBUS => "CRC-8/SMBUS [CRC-8]",
HashType.CRC8_TECH3250 => "CRC-8/TECH-3250 [CRC-8/AES, CRC-8/EBU]",
HashType.CRC8_WCDMA => "CRC-8/WCDMA",
HashType.CRC10_ATM => "CRC-10/ATM [CRC-10, CRC-10/I-610]",
HashType.CRC10_CDMA2000 => "CRC-10/CDMA2000",
HashType.CRC10_GSM => "CRC-10/GSM",
HashType.CRC11_FLEXRAY => "CRC-11/FLEXRAY [CRC-11]",
HashType.CRC11_UMTS => "CRC-11/UMTS",
HashType.CRC12_CDMA2000 => "CRC-12/CDMA2000",
HashType.CRC12_DECT => "CRC-12/DECT [X-CRC-12]",
HashType.CRC12_GSM => "CRC-12/GSM",
HashType.CRC12_UMTS => "CRC-12/UMTS [CRC-12/3GPP]",
HashType.CRC13_BBC => "CRC-13/BBC",
HashType.CRC14_DARC => "CRC-14/DARC",
HashType.CRC14_GSM => "CRC-14/GSM",
HashType.CRC15_CAN => "CRC-15/CAN [CRC-15]",
HashType.CRC15_MPT1327 => "CRC-15/MPT1327",
HashType.CRC16 => "CRC-16",
HashType.CRC16_ARC => "CRC-16/ARC [ARC, CRC-16, CRC-16/LHA, CRC-IBM]",
HashType.CRC16_CDMA2000 => "CRC-16/CDMA2000",
HashType.CRC16_CMS => "CRC-16/CMS",
HashType.CRC16_DDS110 => "CRC-16/DDS-110",
HashType.CRC16_DECTR => "CRC-16/DECT-R [R-CRC-16]",
HashType.CRC16_DECTX => "CRC-16/DECT-X [X-CRC-16]",
HashType.CRC16_DNP => "CRC-16/DNP",
HashType.CRC16_EN13757 => "CRC-16/EN-13757",
HashType.CRC16_GENIBUS => "CRC-16/GENIBUS [CRC-16/DARC, CRC-16/EPC, CRC-16/EPC-C1G2, CRC-16/I-CODE]",
HashType.CRC16_GSM => "CRC-16/GSM",
HashType.CRC16_IBM3740 => "CRC-16/IBM-3740 [CRC-16/AUTOSAR, CRC-16/CCITT-FALSE]",
HashType.CRC16_IBMSDLC => "CRC-16/IBM-SDLC [CRC-16/ISO-HDLC, CRC-16/ISO-IEC-14443-3-B, CRC-16/X-25, CRC-B, X-25]",
HashType.CRC16_ISOIEC144433A => "CRC-16/ISO-IEC-14443-3-A [CRC-A]",
HashType.CRC16_KERMIT => "CRC-16/KERMIT [CRC-16/BLUETOOTH, CRC-16/CCITT, CRC-16/CCITT-TRUE, CRC-16/V-41-LSB, CRC-CCITT, KERMIT]",
HashType.CRC16_LJ1200 => "CRC-16/LJ1200",
HashType.CRC16_M17 => "CRC-16/M17",
HashType.CRC16_MAXIMDOW => "CRC-16/MAXIM-DOW [CRC-16/MAXIM]",
HashType.CRC16_MCRF4XX => "CRC-16/MCRF4XX",
HashType.CRC16_MODBUS => "CRC-16/MODBUS [MODBUS]",
HashType.CRC16_NRSC5 => "CRC-16/NRSC-5",
HashType.CRC16_OPENSAFETYA => "CRC-16/OPENSAFETY-A",
HashType.CRC16_OPENSAFETYB => "CRC-16/OPENSAFETY-B",
HashType.CRC16_PROFIBUS => "CRC-16/PROFIBUS [CRC-16/IEC-61158-2]",
HashType.CRC16_RIELLO => "CRC-16/RIELLO",
HashType.CRC16_SPIFUJITSU => "CRC-16/SPI-FUJITSU [CRC-16/AUG-CCITT]",
HashType.CRC16_T10DIF => "CRC-16/T10-DIF",
HashType.CRC16_TELEDISK => "CRC-16/TELEDISK",
HashType.CRC16_TMS37157 => "CRC-16/TMS37157",
HashType.CRC16_UMTS => "CRC-16/UMTS [CRC-16/BUYPASS, CRC-16/VERIFONE]",
HashType.CRC16_USB => "CRC-16/USB",
HashType.CRC16_XMODEM => "CRC-16/XMODEM [CRC-16/ACORN, CRC-16/LTE, CRC-16/V-41-MSB, XMODEM, ZMODEM]",
HashType.CRC17_CANFD => "CRC-17/CAN-FD",
HashType.CRC21_CANFD => "CRC-21/CAN-FD",
HashType.CRC24_BLE => "CRC-24/BLE",
HashType.CRC24_FLEXRAYA => "CRC-24/FLEXRAY-A",
HashType.CRC24_FLEXRAYB => "CRC-24/FLEXRAY-B",
HashType.CRC24_INTERLAKEN => "CRC-24/INTERLAKEN",
HashType.CRC24_LTEA => "CRC-24/LTE-A",
HashType.CRC24_LTEB => "CRC-24/LTE-B",
HashType.CRC24_OPENPGP => "CRC-24/OPENPGP",
HashType.CRC24_OS9 => "CRC-24/OS-9",
HashType.CRC30_CDMA => "CRC-30/CDMA",
HashType.CRC31_PHILIPS => "CRC-31/PHILIPS",
HashType.CRC32 => "CRC-32",
HashType.CRC32_AIXM => "CRC-32/AIXM",
HashType.CRC32_AUTOSAR => "CRC-32/AUTOSAR",
HashType.CRC32_BASE91D => "CRC-32/BASE91-D",
HashType.CRC32_BZIP2 => "BZIP2",
HashType.CRC32_CDROMEDC => "CRC-32/CD-ROM-EDC",
HashType.CRC32_CKSUM => "CRC-32/CKSUM",
HashType.CRC32_DVDROMEDC => "CRC-32/DVD-ROM-EDC",
HashType.CRC32_ISCSI => "CRC-32/ISCSI",
HashType.CRC32_ISOHDLC => "CRC-32/ISO-HDLC",
HashType.CRC32_JAMCRC => "CRC-32/JAMCRC",
HashType.CRC32_MEF => "CRC-32/MEF",
HashType.CRC32_MPEG2 => "CRC-32/MPEG-2",
HashType.CRC32_XFER => "CRC-32/XFER",
HashType.CRC40_GSM => "CRC-40/GSM",
HashType.CRC64 => "CRC-64",
HashType.CRC64_ECMA182 => "CRC-64/ECMA-182, Microsoft implementation",
HashType.CRC64_GOISO => "CRC-64/GO-ISO",
HashType.CRC64_MS => "CRC-64/MS",
HashType.CRC64_NVME => "CRC-64/NVME",
HashType.CRC64_REDIS => "CRC-64/REDIS",
HashType.CRC64_WE => "CRC-64/WE",
HashType.CRC64_XZ => "CRC-64/XZ",
HashType.Fletcher16 => "John G. Fletcher's 16-bit checksum",
HashType.Fletcher32 => "John G. Fletcher's 32-bit checksum",
HashType.Fletcher64 => "John G. Fletcher's 64-bit checksum",
HashType.FNV0_32 => "FNV hash (Variant 0, 32-bit)",
HashType.FNV0_64 => "FNV hash (Variant 0, 64-bit)",
HashType.FNV1_32 => "FNV hash (Variant 1, 32-bit)",
HashType.FNV1_64 => "FNV hash (Variant 1, 64-bit)",
HashType.FNV1a_32 => "FNV hash (Variant 1a, 32-bit)",
HashType.FNV1a_64 => "FNV hash (Variant 1a, 64-bit)",
HashType.MekaCrc => "Custom MEKA checksum",
HashType.MD2 => "MD2 message-digest algorithm",
HashType.MD4 => "MD4 message-digest algorithm",
HashType.MD5 => "MD5 message-digest algorithm",
HashType.RIPEMD128 => "RIPEMD-128 hash",
HashType.RIPEMD160 => "RIPEMD-160 hash",
HashType.RIPEMD256 => "RIPEMD-256 hash",
HashType.RIPEMD320 => "RIPEMD-320 hash",
HashType.SHA1 => "SHA-1 hash",
HashType.SHA256 => "SHA-256 hash",
HashType.SHA384 => "SHA-384 hash",
HashType.SHA512 => "SHA-512 hash",
#if NET8_0_OR_GREATER
HashType.SHA3_256 => "SHA3-256 hash",
HashType.SHA3_384 => "SHA3-384 hash",
HashType.SHA3_512 => "SHA3-512 hash",
HashType.SHAKE128 => "SHAKE128 SHA-3 family hash (256-bit)",
HashType.SHAKE256 => "SHAKE256 SHA-3 family hash (512-bit)",
#endif
HashType.SpamSum => "spamsum fuzzy hash",
HashType.Tiger128_3 => "Tiger 128-bit hash, 3 passes",
HashType.Tiger128_4 => "Tiger 128-bit hash, 4 passes",
HashType.Tiger160_3 => "Tiger 160-bit hash, 3 passes",
HashType.Tiger160_4 => "Tiger 160-bit hash, 4 passes",
HashType.Tiger192_3 => "Tiger 192-bit hash, 3 passes",
HashType.Tiger192_4 => "Tiger 192-bit hash, 4 passes",
HashType.Tiger2_128_3 => "Tiger2 128-bit hash, 3 passes",
HashType.Tiger2_128_4 => "Tiger2 128-bit hash, 4 passes",
HashType.Tiger2_160_3 => "Tiger2 160-bit hash, 3 passes",
HashType.Tiger2_160_4 => "Tiger2 160-bit hash, 4 passes",
HashType.Tiger2_192_3 => "Tiger2 192-bit hash, 3 passes",
HashType.Tiger2_192_4 => "Tiger2 192-bit hash, 4 passes",
HashType.XxHash32 => "xxHash32 hash",
HashType.XxHash64 => "xxHash64 hash",
#if NET462_OR_GREATER || NETCOREAPP
HashType.XxHash3 => "XXH3 64-bit hash",
HashType.XxHash128 => "XXH128 128-bit hash",
#endif
_ => $"{hashType}",
};
}
/// <summary>
/// Get the hash type associated to a string, if possible
/// </summary>
/// TODO: This should be automated instead of hardcoded
public static HashType? GetHashType(this string? str)
{
// Ignore invalid strings
if (string.IsNullOrEmpty(str))
return null;
// Normalize the string before matching
str = str!.Replace("-", string.Empty);
str = str.Replace(" ", string.Empty);
str = str.Replace("/", "_");
str = str.Replace("\\", "_");
str = str.ToLowerInvariant();
// Match based on potential names
return str switch
{
"adler" or "adler32" => HashType.Adler32,
#if NET7_0_OR_GREATER
"blake3" => HashType.BLAKE3,
#endif
"crc1_0" or "crc1_zero" => HashType.CRC1_ZERO,
"crc1_1" or "crc1_one" => HashType.CRC1_ONE,
"crc3_gsm" => HashType.CRC3_GSM,
"crc3_rohc" => HashType.CRC3_ROHC,
"crc4_g704" or "crc4_itu" => HashType.CRC4_G704,
"crc4_interlaken" => HashType.CRC4_INTERLAKEN,
"crc5_epc" or "crc5_epcc1g2" => HashType.CRC5_EPCC1G2,
"crc5_g704" or "crc5_itu" => HashType.CRC5_G704,
"crc5_usb" => HashType.CRC5_USB,
"crc6_cdma2000a" => HashType.CRC6_CDMA2000A,
"crc6_cdma2000b" => HashType.CRC6_CDMA2000B,
"crc6_darc" => HashType.CRC6_DARC,
"crc6_g704" or "crc6_itu" => HashType.CRC6_G704,
"crc6_gsm" => HashType.CRC6_GSM,
"crc7" or "crc7_mmc" => HashType.CRC7_MMC,
"crc7_rohc" => HashType.CRC7_ROHC,
"crc7_umts" => HashType.CRC7_UMTS,
"crc8" => HashType.CRC8,
"crc8_autosar" => HashType.CRC8_AUTOSAR,
"crc8_bluetooth" => HashType.CRC8_BLUETOOTH,
"crc8_cdma2000" => HashType.CRC8_CDMA2000,
"crc8_darc" => HashType.CRC8_DARC,
"crc8_dvbs2" => HashType.CRC8_DVBS2,
"crc8_gsma" => HashType.CRC8_GSMA,
"crc8_gsmb" => HashType.CRC8_GSMB,
"crc8_hitag" => HashType.CRC8_HITAG,
"crc8_i4321" or "crc8_itu" => HashType.CRC8_I4321,
"crc8_icode" => HashType.CRC8_ICODE,
"crc8_lte" => HashType.CRC8_LTE,
"crc8_maximdow" or "crc8_maxim" or "dowcrc" => HashType.CRC8_MAXIMDOW,
"crc8_mifaremad" => HashType.CRC8_MIFAREMAD,
"crc8_nrsc5" => HashType.CRC8_NRSC5,
"crc8_opensafety" => HashType.CRC8_OPENSAFETY,
"crc8_rohc" => HashType.CRC8_ROHC,
"crc8_saej1850" => HashType.CRC8_SAEJ1850,
"crc8_smbus" => HashType.CRC8_SMBUS,
"crc8_tech3250" or "crc8_aes" or "crc8_ebu" => HashType.CRC8_TECH3250,
"crc8_wcdma" => HashType.CRC8_WCDMA,
"crc10_atm" or "crc10" or "crc10_i610" => HashType.CRC10_ATM,
"crc10_cdma2000" => HashType.CRC10_CDMA2000,
"crc10_gsm" => HashType.CRC10_GSM,
"crc11_flexray" or "crc11" => HashType.CRC11_FLEXRAY,
"crc11_umts" => HashType.CRC11_UMTS,
"crc12_cdma2000" => HashType.CRC12_CDMA2000,
"crc12_dect" or "xcrc12" => HashType.CRC12_DECT,
"crc12_gsm" => HashType.CRC12_GSM,
"crc12_umts" or "crc12_3gpp" => HashType.CRC12_UMTS,
"crc13_bbc" => HashType.CRC13_BBC,
"crc14_darc" => HashType.CRC14_DARC,
"crc14_gsm" => HashType.CRC14_GSM,
"crc15_can" or "crc15" => HashType.CRC15_CAN,
"crc15_mpt1327" => HashType.CRC15_MPT1327,
"crc16" => HashType.CRC16,
"crc16_arc" or "arc" or "crc16_lha" or "crcibm" => HashType.CRC16_ARC,
"crc16_cdma2000" => HashType.CRC16_CDMA2000,
"crc16_cms" => HashType.CRC16_CMS,
"crc16_dds110" => HashType.CRC16_DDS110,
"crc16_dectr" or "rcrc16" => HashType.CRC16_DECTR,
"crc16_dectx" or "xcrc16" => HashType.CRC16_DECTX,
"crc16_dnp" => HashType.CRC16_DNP,
"crc16_en13757" => HashType.CRC16_EN13757,
"crc16_genibus" or "crc16_darc" or "crc16_epc" or "crc16_epcc1g2" or "crc16_icode" => HashType.CRC16_GENIBUS,
"crc16_gsm" => HashType.CRC16_GSM,
"crc16_ibm3740" or "crc16_autosar" or "crc16_cittfalse" => HashType.CRC16_IBM3740,
"crc16_ibmsdlc" or "crc16_isohdlc" or "crc16_isoiec144433b" or "crc16_x25" or "crcb" or "x25" => HashType.CRC16_IBMSDLC,
"crc16_isoiec144433a" or "crca" => HashType.CRC16_ISOIEC144433A,
"crc16_kermit" or "crc16_bluetooth" or "crc16_ccitt" or "crc16_ccitttrue" or "crc16_v41lsb" or "crcccitt" or "kermit" => HashType.CRC16_KERMIT,
"crc16_lj1200" => HashType.CRC16_LJ1200,
"crc16_m17" => HashType.CRC16_M17,
"crc16_maximdow" or "crc16_maxim" => HashType.CRC16_MAXIMDOW,
"crc16_mcrf4xx" => HashType.CRC16_MCRF4XX,
"crc16_modbus" or "modbus" => HashType.CRC16_MODBUS,
"crc16_nrsc5" => HashType.CRC16_NRSC5,
"crc16_opensafetya" => HashType.CRC16_OPENSAFETYA,
"crc16_opensafetyb" => HashType.CRC16_OPENSAFETYB,
"crc16_profibus" or "crc16_iec611582" => HashType.CRC16_PROFIBUS,
"crc16_riello" => HashType.CRC16_RIELLO,
"crc16_spifujitsu" or "crc16_augccitt" => HashType.CRC16_SPIFUJITSU,
"crc16_t10dif" => HashType.CRC16_T10DIF,
"crc16_teledisk" => HashType.CRC16_TELEDISK,
"crc16_tms37157" => HashType.CRC16_TMS37157,
"crc16_umts" or "crc16_buypass" or "crc16_verifone" => HashType.CRC16_UMTS,
"crc16_usb" => HashType.CRC16_USB,
"crc16_xmodem" or "crc16_acorn" or "crc16_lte" or "crc16_v41msb" or "xmodem" or "zmodem" => HashType.CRC16_XMODEM,
"crc17_canfd" => HashType.CRC17_CANFD,
"crc21_canfd" => HashType.CRC21_CANFD,
"crc24_ble" => HashType.CRC24_BLE,
"crc24_flexraya" => HashType.CRC24_FLEXRAYA,
"crc24_flexrayb" => HashType.CRC24_FLEXRAYB,
"crc24_interlaken" => HashType.CRC24_INTERLAKEN,
"crc24_ltea" => HashType.CRC24_LTEA,
"crc24_lteb" => HashType.CRC24_LTEB,
"crc24_openpgp" => HashType.CRC24_OPENPGP,
"crc24_os9" => HashType.CRC24_OS9,
"crc30_cdma" => HashType.CRC30_CDMA,
"crc31_philips" => HashType.CRC31_PHILIPS,
"crc32" => HashType.CRC32,
"crc32_aixm" => HashType.CRC32_AIXM,
"crc32_autosar" => HashType.CRC32_AUTOSAR,
"crc32_base91d" => HashType.CRC32_BASE91D,
"crc32_bzip2" => HashType.CRC32_BZIP2,
"crc32_cdromedc" => HashType.CRC32_CDROMEDC,
"crc32_cksum" => HashType.CRC32_CKSUM,
"crc32_dvdromedc" => HashType.CRC32_DVDROMEDC,
"crc32_iscsi" => HashType.CRC32_ISCSI,
"crc32_isohdlc" => HashType.CRC32_ISOHDLC,
"crc32_jamcrc" => HashType.CRC32_JAMCRC,
"crc32_mef" => HashType.CRC32_MEF,
"crc32_mpeg2" => HashType.CRC32_MPEG2,
"crc32_xfer" => HashType.CRC32_XFER,
"crc40_gsm" => HashType.CRC40_GSM,
"crc64" => HashType.CRC64,
"crc64_ecma182" => HashType.CRC64_ECMA182,
"crc64_goiso" => HashType.CRC64_GOISO,
"crc64_ms" => HashType.CRC64_MS,
"crc64_nvme" => HashType.CRC64_NVME,
"crc64_redis" => HashType.CRC64_REDIS,
"crc64_we" => HashType.CRC64_WE,
"crc64_xz" => HashType.CRC64_XZ,
"fletcher16" => HashType.Fletcher16,
"fletcher32" => HashType.Fletcher32,
"fletcher64" => HashType.Fletcher64,
"fnv0_32" => HashType.FNV0_32,
"fnv0_64" => HashType.FNV0_64,
"fnv1_32" => HashType.FNV1_32,
"fnv1_64" => HashType.FNV1_64,
"fnv1a_32" => HashType.FNV1a_32,
"fnv1a_64" => HashType.FNV1a_64,
"meka" or "mekacrc" or "meka_crc" => HashType.MekaCrc,
"md2" => HashType.MD2,
"md4" => HashType.MD4,
"md5" => HashType.MD5,
"ripemd128" => HashType.RIPEMD128,
"ripemd160" => HashType.RIPEMD160,
"ripemd256" => HashType.RIPEMD256,
"ripemd320" => HashType.RIPEMD320,
"sha1" => HashType.SHA1,
"sha256" => HashType.SHA256,
"sha384" => HashType.SHA384,
"sha512" => HashType.SHA512,
#if NET8_0_OR_GREATER
"sha3_256" => HashType.SHA3_256,
"sha3_384" => HashType.SHA3_384,
"sha3_512" => HashType.SHA3_512,
"shake128" => HashType.SHAKE128,
"shake256" => HashType.SHAKE256,
#endif
"spamsum" => HashType.SpamSum,
"tiger128_3" => HashType.Tiger128_3,
"tiger128_4" => HashType.Tiger128_4,
"tiger160_3" => HashType.Tiger160_3,
"tiger160_4" => HashType.Tiger160_4,
"tiger192_3" => HashType.Tiger192_3,
"tiger192_4" => HashType.Tiger192_4,
"tiger2_128_3" => HashType.Tiger2_128_3,
"tiger2_128_4" => HashType.Tiger2_128_4,
"tiger2_160_3" => HashType.Tiger2_160_3,
"tiger2_160_4" => HashType.Tiger2_160_4,
"tiger2_192_3" => HashType.Tiger2_192_3,
"tiger2_192_4" => HashType.Tiger2_192_4,
"xxh" or "xxh32" or "xxh_32" or "xxhash" or "xxhash32" or "xxhash_32" => HashType.XxHash32,
"xxh64" or "xxh_64" or "xxhash64" or "xxhash_64" => HashType.XxHash64,
#if NET462_OR_GREATER || NETCOREAPP
"xxh3" or "xxh3_64" or "xxhash3" or "xxhash_3" => HashType.XxHash3,
"xxh128" or "xxh_128" or "xxhash128" or "xxhash_128" => HashType.XxHash128,
#endif
_ => null,
};
}
}
}

View File

@@ -15,7 +15,7 @@ namespace SabreTools.Hashing
public static string? ByteArrayToString(byte[]? bytes)
{
// If we get null in, we send null out
if (bytes == null)
if (bytes is null)
return null;
try
@@ -38,7 +38,7 @@ namespace SabreTools.Hashing
public static ulong BytesToUInt64(byte[]? bytes)
{
// If we get null in, we send 0 out
if (bytes == null)
if (bytes is null)
return default;
ulong result = 0;
@@ -60,9 +60,9 @@ namespace SabreTools.Hashing
public static uint ReadBE32(byte[] data, int offset)
{
return (uint)(data[offset + 3]
| data[offset + 2] << 8
| data[offset + 1] << 16
| data[offset + 0] << 24);
| (data[offset + 2] << 8)
| (data[offset + 1] << 16)
| (data[offset + 0] << 24));
}
/// <summary>
@@ -71,13 +71,13 @@ namespace SabreTools.Hashing
public static ulong ReadBE64(byte[] data, int offset)
{
return data[offset + 7]
| (ulong)data[offset + 6] << 8
| (ulong)data[offset + 5] << 16
| (ulong)data[offset + 4] << 24
| (ulong)data[offset + 3] << 32
| (ulong)data[offset + 2] << 40
| (ulong)data[offset + 1] << 48
| (ulong)data[offset + 0] << 56;
| ((ulong)data[offset + 6] << 8)
| ((ulong)data[offset + 5] << 16)
| ((ulong)data[offset + 4] << 24)
| ((ulong)data[offset + 3] << 32)
| ((ulong)data[offset + 2] << 40)
| ((ulong)data[offset + 1] << 48)
| ((ulong)data[offset + 0] << 56);
}
#endregion
@@ -90,9 +90,9 @@ namespace SabreTools.Hashing
public static uint ReadLE32(byte[] data, int offset)
{
return (uint)(data[offset + 0]
| data[offset + 1] << 8
| data[offset + 2] << 16
| data[offset + 3] << 24);
| (data[offset + 1] << 8)
| (data[offset + 2] << 16)
| (data[offset + 3] << 24));
}
/// <summary>
@@ -101,13 +101,13 @@ namespace SabreTools.Hashing
public static ulong ReadLE64(byte[] data, int offset)
{
return data[offset + 0]
| (ulong)data[offset + 1] << 8
| (ulong)data[offset + 2] << 16
| (ulong)data[offset + 3] << 24
| (ulong)data[offset + 4] << 32
| (ulong)data[offset + 5] << 40
| (ulong)data[offset + 6] << 48
| (ulong)data[offset + 7] << 56;
| ((ulong)data[offset + 1] << 8)
| ((ulong)data[offset + 2] << 16)
| ((ulong)data[offset + 3] << 24)
| ((ulong)data[offset + 4] << 32)
| ((ulong)data[offset + 5] << 40)
| ((ulong)data[offset + 6] << 48)
| ((ulong)data[offset + 7] << 56);
}
#endregion
@@ -178,4 +178,4 @@ namespace SabreTools.Hashing
#endregion
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
#if NET40_OR_GREATER || NETCOREAPP
#if NET40_OR_GREATER || NETCOREAPP || NETSTANDARD2_0_OR_GREATER
using System.Threading.Tasks;
#endif
@@ -12,10 +12,16 @@ namespace SabreTools.Hashing
/// </summary>
public static class HashTool
{
#region Standard Hashes
/// <summary>
/// Get CRC-32, MD5, and SHA-1 hashes from an input file path
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="size">Calculated file size on success, -1 on error</param>
/// <param name="crc32">CRC-32 for the input file</param>
/// <param name="md5">MD5 for the input file</param>
/// <param name="sha1">SHA-1 for the input file</param>
/// <returns>True if hashing was successful, false otherwise</returns>
public static bool GetStandardHashes(string filename, out long size, out string? crc32, out string? md5, out string? sha1)
{
@@ -25,7 +31,7 @@ namespace SabreTools.Hashing
// Get all file hashes
HashType[] standardHashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1];
var fileHashes = GetFileHashesAndSize(filename, standardHashTypes, out size);
if (fileHashes == null)
if (fileHashes is null)
return false;
// Assign the file hashes and return
@@ -35,6 +41,62 @@ namespace SabreTools.Hashing
return true;
}
/// <summary>
/// Get CRC-32, MD5, and SHA-1 hashes from an input byte array
/// </summary>
/// <param name="array">Input byte array</param>
/// <param name="size">Calculated file size on success, -1 on error</param>
/// <param name="crc32">CRC-32 for the input file</param>
/// <param name="md5">MD5 for the input file</param>
/// <param name="sha1">SHA-1 for the input file</param>
/// <returns>True if hashing was successful, false otherwise</returns>
public static bool GetStandardHashes(byte[] array, out long size, out string? crc32, out string? md5, out string? sha1)
{
// Set all initial values
crc32 = null; md5 = null; sha1 = null;
// Get all file hashes
HashType[] standardHashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1];
var fileHashes = GetByteArrayHashesAndSize(array, standardHashTypes, out size);
if (fileHashes is null)
return false;
// Assign the file hashes and return
crc32 = fileHashes[HashType.CRC32];
md5 = fileHashes[HashType.MD5];
sha1 = fileHashes[HashType.SHA1];
return true;
}
/// <summary>
/// Get CRC-32, MD5, and SHA-1 hashes from an input stream
/// </summary>
/// <param name="stream">Input stream</param>
/// <param name="size">Calculated file size on success, -1 on error</param>
/// <param name="crc32">CRC-32 for the input file</param>
/// <param name="md5">MD5 for the input file</param>
/// <param name="sha1">SHA-1 for the input file</param>
/// <returns>True if hashing was successful, false otherwise</returns>
public static bool GetStandardHashes(Stream stream, out long size, out string? crc32, out string? md5, out string? sha1)
{
// Set all initial values
crc32 = null; md5 = null; sha1 = null;
// Get all file hashes
HashType[] standardHashTypes = [HashType.CRC32, HashType.MD5, HashType.SHA1];
var fileHashes = GetStreamHashesAndSize(stream, standardHashTypes, out size);
if (fileHashes is null)
return false;
// Assign the file hashes and return
crc32 = fileHashes[HashType.CRC32];
md5 = fileHashes[HashType.MD5];
sha1 = fileHashes[HashType.SHA1];
return true;
}
#endregion
#region File Hashes Without Size
/// <summary>
@@ -60,10 +122,7 @@ namespace SabreTools.Hashing
/// <param name="hashType">Hash type to get from the file</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetFileHash(string filename, HashType hashType)
{
var hashes = GetFileHashes(filename, [hashType]);
return hashes?[hashType];
}
=> GetFileHashAndSize(filename, hashType, out _);
/// <summary>
/// Get a hash from an input file path
@@ -72,10 +131,7 @@ namespace SabreTools.Hashing
/// <param name="hashType">Hash type to get from the file</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetFileHashArray(string filename, HashType hashType)
{
var hashes = GetFileHashArrays(filename, [hashType]);
return hashes?[hashType];
}
=> GetFileHashArrayAndSize(filename, hashType, out _);
/// <summary>
/// Get hashes from an input file path
@@ -103,6 +159,7 @@ namespace SabreTools.Hashing
/// Get hashes and size from an input file path
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetFileHashesAndSize(string filename, out long size)
{
@@ -117,6 +174,7 @@ namespace SabreTools.Hashing
/// Get hashes and size from an input file path
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetFileHashArraysAndSize(string filename, out long size)
{
@@ -132,6 +190,7 @@ namespace SabreTools.Hashing
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash and size on success, null on error</returns>
public static string? GetFileHashAndSize(string filename, HashType hashType, out long size)
{
@@ -144,6 +203,7 @@ namespace SabreTools.Hashing
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash and size on success, null on error</returns>
public static byte[]? GetFileHashArrayAndSize(string filename, HashType hashType, out long size)
{
@@ -156,6 +216,7 @@ namespace SabreTools.Hashing
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetFileHashesAndSize(string filename, HashType[] hashTypes, out long size)
{
@@ -166,14 +227,11 @@ namespace SabreTools.Hashing
return null;
}
// Set the file size
size = new FileInfo(filename).Length;
// Open the input file
var input = File.OpenRead(filename);
// Return the hashes from the stream
return GetStreamHashes(input, hashTypes);
return GetStreamHashesAndSize(input, hashTypes, out size);
}
/// <summary>
@@ -181,6 +239,7 @@ namespace SabreTools.Hashing
/// </summary>
/// <param name="filename">Path to the input file</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetFileHashArraysAndSize(string filename, HashType[] hashTypes, out long size)
{
@@ -191,19 +250,16 @@ namespace SabreTools.Hashing
return null;
}
// Set the file size
size = new FileInfo(filename).Length;
// Open the input file
var input = File.OpenRead(filename);
// Return the hashes from the stream
return GetStreamHashArrays(input, hashTypes);
return GetStreamHashArraysAndSize(input, hashTypes, out size);
}
#endregion
#region Byte Array Hashes
#region Byte Array Hashes Without Size
/// <summary>
/// Get hashes from an input byte array
@@ -211,13 +267,7 @@ namespace SabreTools.Hashing
/// <param name="input">Byte array to hash</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetByteArrayHashes(byte[] input)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Return the hashes from the stream
return GetStreamHashes(new MemoryStream(input), hashTypes);
}
=> GetByteArrayHashesAndSize(input, out _);
/// <summary>
/// Get hashes from an input byte array
@@ -225,13 +275,7 @@ namespace SabreTools.Hashing
/// <param name="input">Byte array to hash</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetByteArrayHashArrays(byte[] input)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Return the hashes from the stream
return GetStreamHashArrays(new MemoryStream(input), hashTypes);
}
=> GetByteArrayHashArraysAndSize(input, out _);
/// <summary>
/// Get a hash from an input byte array
@@ -240,10 +284,7 @@ namespace SabreTools.Hashing
/// <param name="hashType">Hash type to get from the file</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetByteArrayHash(byte[] input, HashType hashType)
{
var hashes = GetStreamHashes(new MemoryStream(input), [hashType]);
return hashes?[hashType];
}
=> GetByteArrayHashAndSize(input, hashType, out _);
/// <summary>
/// Get a hash from an input byte array
@@ -252,10 +293,7 @@ namespace SabreTools.Hashing
/// <param name="hashType">Hash type to get from the file</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetByteArrayHashArray(byte[] input, HashType hashType)
{
var hashes = GetStreamHashArrays(new MemoryStream(input), [hashType]);
return hashes?[hashType];
}
=> GetByteArrayHashArrayAndSize(input, hashType, out _);
/// <summary>
/// Get hashes from an input byte array
@@ -264,7 +302,7 @@ namespace SabreTools.Hashing
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetByteArrayHashes(byte[] input, HashType[] hashTypes)
=> GetStreamHashes(new MemoryStream(input), hashTypes);
=> GetByteArrayHashesAndSize(input, hashTypes, out _);
/// <summary>
/// Get hashes from an input byte array
@@ -273,71 +311,271 @@ namespace SabreTools.Hashing
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetByteArrayHashArrays(byte[] input, HashType[] hashTypes)
=> GetStreamHashArrays(new MemoryStream(input), hashTypes);
=> GetByteArrayHashArraysAndSize(input, hashTypes, out _);
#endregion
#region Stream Hashes
#region Byte Array Hashes With Size
/// <summary>
/// Get hashes from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetByteArrayHashesAndSize(byte[] input, out long size)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Return the hashes from the stream
return GetStreamHashesAndSize(new MemoryStream(input), hashTypes, out size);
}
/// <summary>
/// Get hashes from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetByteArrayHashArraysAndSize(byte[] input, out long size)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Return the hashes from the stream
return GetStreamHashArraysAndSize(new MemoryStream(input), hashTypes, out size);
}
/// <summary>
/// Get a hash from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetByteArrayHashAndSize(byte[] input, HashType hashType, out long size)
{
var hashes = GetStreamHashesAndSize(new MemoryStream(input), [hashType], out size);
return hashes?[hashType];
}
/// <summary>
/// Get a hash from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetByteArrayHashArrayAndSize(byte[] input, HashType hashType, out long size)
{
var hashes = GetStreamHashArraysAndSize(new MemoryStream(input), [hashType], out size);
return hashes?[hashType];
}
/// <summary>
/// Get hashes from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetByteArrayHashesAndSize(byte[] input, HashType[] hashTypes, out long size)
=> GetStreamHashesAndSize(new MemoryStream(input), hashTypes, out size);
/// <summary>
/// Get hashes from an input byte array
/// </summary>
/// <param name="input">Byte array to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetByteArrayHashArraysAndSize(byte[] input, HashType[] hashTypes, out long size)
=> GetStreamHashArraysAndSize(new MemoryStream(input), hashTypes, out size);
#endregion
#region Stream Hashes Without Size
/// <summary>
/// Get hashes from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashes(Stream input, bool leaveOpen = false)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Get the output hashes
return GetStreamHashes(input, hashTypes, leaveOpen);
}
=> GetStreamHashesAndSize(input, leaveOpen, out _);
/// <summary>
/// Get hashes from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArrays(Stream input, bool leaveOpen = false)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Get the output hashes
return GetStreamHashArrays(input, hashTypes, leaveOpen);
}
=> GetStreamHashArraysAndSize(input, leaveOpen, out _);
/// <summary>
/// Get a hash and size from an input Stream
/// Get a hash from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetStreamHash(Stream input, HashType hashType, bool leaveOpen = false)
{
var hashes = GetStreamHashes(input, [hashType], leaveOpen);
return hashes?[hashType];
}
=> GetStreamHashAndSize(input, hashType, leaveOpen, out _);
/// <summary>
/// Get a hash and size from an input Stream
/// Get a hash from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetStreamHashArray(Stream input, HashType hashType, bool leaveOpen = false)
{
var hashes = GetStreamHashArrays(input, [hashType], leaveOpen);
return hashes?[hashType];
}
=> GetStreamHashArrayAndSize(input, hashType, leaveOpen, out _);
/// <summary>
/// Get hashes from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashes(Stream input, HashType[] hashTypes, bool leaveOpen = false)
=> GetStreamHashesAndSize(input, hashTypes, leaveOpen, out _);
/// <summary>
/// Get hashes from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArrays(Stream input, HashType[] hashTypes, bool leaveOpen = false)
=> GetStreamHashArraysAndSize(input, hashTypes, leaveOpen, out _);
#endregion
#region Stream Hashes With Size
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashesAndSize(Stream input, out long size)
=> GetStreamHashesAndSize(input, leaveOpen: false, out size);
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashesAndSize(Stream input, bool leaveOpen, out long size)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Get the output hashes
return GetStreamHashesAndSize(input, hashTypes, leaveOpen, out size);
}
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArraysAndSize(Stream input, out long size)
=> GetStreamHashArraysAndSize(input, leaveOpen: false, out size);
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArraysAndSize(Stream input, bool leaveOpen, out long size)
{
// Create a hash array for all entries
HashType[] hashTypes = (HashType[])Enum.GetValues(typeof(HashType));
// Get the output hashes
return GetStreamHashArraysAndSize(input, hashTypes, leaveOpen, out size);
}
/// <summary>
/// Get a hash and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetStreamHashAndSize(Stream input, HashType hashType, out long size)
=> GetStreamHashAndSize(input, hashType, leaveOpen: false, out size);
/// <summary>
/// Get a hash and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static string? GetStreamHashAndSize(Stream input, HashType hashType, bool leaveOpen, out long size)
{
var hashes = GetStreamHashesAndSize(input, [hashType], leaveOpen, out size);
return hashes?[hashType];
}
/// <summary>
/// Get a hash and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetStreamHashArrayAndSize(Stream input, HashType hashType, out long size)
=> GetStreamHashArrayAndSize(input, hashType, leaveOpen: false, out size);
/// <summary>
/// Get a hash and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashType">Hash type to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Hash on success, null on error</returns>
public static byte[]? GetStreamHashArrayAndSize(Stream input, HashType hashType, bool leaveOpen, out long size)
{
var hashes = GetStreamHashArraysAndSize(input, [hashType], leaveOpen, out size);
return hashes?[hashType];
}
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashesAndSize(Stream input, HashType[] hashTypes, out long size)
=> GetStreamHashesAndSize(input, hashTypes, leaveOpen: false, out size);
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, string?>? GetStreamHashesAndSize(Stream input, HashType[] hashTypes, bool leaveOpen, out long size)
{
// Create the output dictionary
var hashDict = new Dictionary<HashType, string?>();
@@ -352,14 +590,15 @@ namespace SabreTools.Hashing
hashDict[hashType] = ZeroHash.GetString(hashType);
}
size = 0;
return hashDict;
}
}
catch { }
// Run the hashing
var hashers = GetStreamHashesInternal(input, hashTypes, leaveOpen);
if (hashers == null)
var hashers = GetStreamHashesInternal(input, hashTypes, leaveOpen, out size);
if (hashers is null)
return null;
// Get the results
@@ -378,12 +617,24 @@ namespace SabreTools.Hashing
}
/// <summary>
/// Get hashes from an input Stream
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArrays(Stream input, HashType[] hashTypes, bool leaveOpen = false)
public static Dictionary<HashType, byte[]?>? GetStreamHashArraysAndSize(Stream input, HashType[] hashTypes, out long size)
=> GetStreamHashArraysAndSize(input, hashTypes, leaveOpen: false, out size);
/// <summary>
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns>Dictionary containing hashes on success, null on error</returns>
public static Dictionary<HashType, byte[]?>? GetStreamHashArraysAndSize(Stream input, HashType[] hashTypes, bool leaveOpen, out long size)
{
// Create the output dictionary
var hashDict = new Dictionary<HashType, byte[]?>();
@@ -398,14 +649,15 @@ namespace SabreTools.Hashing
hashDict[hashType] = ZeroHash.GetBytes(hashType);
}
size = 0;
return hashDict;
}
}
catch { }
// Run the hashing
var hashers = GetStreamHashesInternal(input, hashTypes, leaveOpen);
if (hashers == null)
var hashers = GetStreamHashesInternal(input, hashTypes, leaveOpen, out size);
if (hashers is null)
return null;
// Get the results
@@ -424,16 +676,18 @@ namespace SabreTools.Hashing
}
/// <summary>
/// Get hashes from an input stream
/// Get hashes and size from an input Stream
/// </summary>
/// <param name="input"></param>
/// <param name="hashTypes"></param>
/// <param name="leaveOpen"></param>
/// <param name="input">Stream to hash</param>
/// <param name="hashTypes">Array of hash types to get from the file</param>
/// <param name="leaveOpen">Indicates if the source stream should be left open after hashing</param>
/// <param name="size">Amount of bytes read during hashing</param>
/// <returns></returns>
private static Dictionary<HashType, HashWrapper>? GetStreamHashesInternal(Stream input, HashType[] hashTypes, bool leaveOpen)
private static Dictionary<HashType, HashWrapper>? GetStreamHashesInternal(Stream input, HashType[] hashTypes, bool leaveOpen, out long size)
{
// Create the output dictionary
// Create the output dictionary and size counter
var hashDict = new Dictionary<HashType, string?>();
size = 0;
try
{
@@ -456,6 +710,7 @@ namespace SabreTools.Hashing
{
// Load the buffer and hold the number of bytes read
lastRead = input.Read(buffer, 0, buffersize);
size += lastRead;
if (lastRead == 0)
break;
@@ -498,4 +753,4 @@ namespace SabreTools.Hashing
#endregion
}
}
}

View File

@@ -133,7 +133,7 @@ namespace SabreTools.Hashing
/// <summary>
/// CRC 8-bit checksum
/// </summary>
/// <remarks>Identical to <see cref="CRC8_SMBUS"/>
/// <remarks>Identical to <see cref="CRC8_SMBUS"/>
CRC8,
/// <summary>
@@ -337,7 +337,7 @@ namespace SabreTools.Hashing
/// <summary>
/// CRC 16-bit checksum
/// </summary>
/// <remarks>Identical to <see cref="CRC16_ARC"/>
/// <remarks>Identical to <see cref="CRC16_ARC"/>
CRC16,
/// <summary>
@@ -582,7 +582,7 @@ namespace SabreTools.Hashing
/// <summary>
/// CRC 32-bit checksum
/// </summary>
/// <remarks>Identical to <see cref="CRC32_ISOHDLC"/>
/// <remarks>Identical to <see cref="CRC32_ISOHDLC"/>
CRC32,
/// <summary>
@@ -615,6 +615,11 @@ namespace SabreTools.Hashing
/// </summary>
CRC32_CKSUM,
/// <summary>
/// CRC 32-bit checksum (CRC-32/DVD-ROM-EDC)
/// </summary>
CRC32_DVDROMEDC,
/// <summary>
/// CRC 32-bit checksum (CRC-32/ISCSI)
/// </summary>
@@ -661,7 +666,7 @@ namespace SabreTools.Hashing
/// <summary>
/// CRC 64-bit checksum
/// </summary>
/// <remarks>Identical to <see cref="CRC64_ECMA182"/>
/// <remarks>Identical to <see cref="CRC64_ECMA182"/>
CRC64,
/// <summary>
@@ -756,6 +761,11 @@ namespace SabreTools.Hashing
#endregion
/// <summary>
/// Custom checksum used by MEKA
/// </summary>
MekaCrc,
#region Message Digest
/// <summary>
@@ -835,7 +845,7 @@ namespace SabreTools.Hashing
/// <summary>
/// SHA3-512 hash
/// </summary>
SHA3_512,
SHA3_512,
/// <summary>
/// SHAKE128 SHA-3 family hash
@@ -947,4 +957,4 @@ namespace SabreTools.Hashing
#endregion
}
}
}

View File

@@ -1,14 +1,8 @@
using System;
#if NET462_OR_GREATER || NETCOREAPP
using System.IO.Hashing;
#endif
using System.Security.Cryptography;
using Aaru.Checksums;
using Aaru.CommonTypes.Interfaces;
#if NET7_0_OR_GREATER
using Blake3;
#endif
using SabreTools.Hashing.Checksum;
using SabreTools.Hashing.CryptographicHash;
using SabreTools.Hashing.NonCryptographicHash;
using static SabreTools.Hashing.HashOperations;
namespace SabreTools.Hashing
@@ -28,81 +22,33 @@ namespace SabreTools.Hashing
/// <summary>
/// Current hash in bytes
/// </summary>
public byte[]? CurrentHashBytes
public byte[]? CurrentHashBytes => _hasher switch
{
get
{
switch (_hasher)
{
case ChecksumBase cb:
var cbArr = cb.Finalize();
Array.Reverse(cbArr);
return cbArr;
case HashAlgorithm ha:
return ha.Hash;
case IChecksum ic:
return ic.Final();
case MessageDigest.MessageDigestBase mdb:
return mdb.GetHash();
HashAlgorithm ha => ha.Hash,
#if NET462_OR_GREATER || NETCOREAPP
case XxHash3 xxh3:
return xxh3.GetCurrentHash();
case XxHash128 xxh128:
return xxh128.GetCurrentHash();
case NonCryptographicHashAlgorithm ncha:
var nchaArr = ncha.GetCurrentHash();
Array.Reverse(nchaArr);
return nchaArr;
System.IO.Hashing.NonCryptographicHashAlgorithm ncha => ncha.GetCurrentHash(),
#endif
#if NET8_0_OR_GREATER
case Shake128 s128:
return s128.GetCurrentHash(32);
case Shake256 s256:
return s256.GetCurrentHash(64);
Shake128 s128 => s128.GetCurrentHash(32),
Shake256 s256 => s256.GetCurrentHash(64),
#endif
case XxHash.XxHash32 xxh32:
var xxh32Arr = xxh32.Finalize();
Array.Reverse(xxh32Arr);
return xxh32Arr;
case XxHash.XxHash64 xxh64:
var xxh64Arr = xxh64.Finalize();
Array.Reverse(xxh64Arr);
return xxh64Arr;
default:
return null;
}
}
}
_ => null,
};
/// <summary>
/// Current hash as a string
/// </summary>
public string? CurrentHashString
public string? CurrentHashString => _hasher switch
{
get
{
switch (_hasher)
{
case Crc cr:
var crArr = cr.Finalize();
ulong crHash = BytesToUInt64(crArr);
int length = cr.Def.Width / 4 + (cr.Def.Width % 4 > 0 ? 1 : 0);
return crHash.ToString($"x{length}");
case IChecksum ic:
return ic.End();
// Needed due to variable bit widths
Crc cr => GetCRCVariableLengthString(cr),
default:
return ByteArrayToString(CurrentHashBytes);
}
}
}
// Needed due to Base64 text output
SpamSum.SpamSum ss => GetSpamSumBase64String(ss),
// Everything else are direct conversions
_ => ByteArrayToString(CurrentHashBytes),
};
#endregion
@@ -111,7 +57,6 @@ namespace SabreTools.Hashing
/// <summary>
/// Internal hasher being used for processing
/// </summary>
/// <remarks>May be either a HashAlgorithm or NonCryptographicHashAlgorithm</remarks>
private object? _hasher;
#endregion
@@ -138,7 +83,7 @@ namespace SabreTools.Hashing
HashType.Adler32 => new Adler32(),
#if NET7_0_OR_GREATER
HashType.BLAKE3 => new Blake3HashAlgorithm(),
HashType.BLAKE3 => new Blake3.Blake3HashAlgorithm(),
#endif
HashType.CRC1_ZERO => new Crc(StandardDefinitions.CRC1_ZERO),
@@ -263,6 +208,7 @@ namespace SabreTools.Hashing
HashType.CRC32_BZIP2 => new Crc(StandardDefinitions.CRC32_BZIP2),
HashType.CRC32_CDROMEDC => new Crc(StandardDefinitions.CRC32_CDROMEDC),
HashType.CRC32_CKSUM => new Crc(StandardDefinitions.CRC32_CKSUM),
HashType.CRC32_DVDROMEDC => new Crc(StandardDefinitions.CRC32_DVDROMEDC),
HashType.CRC32_ISCSI => new Crc(StandardDefinitions.CRC32_ISCSI),
HashType.CRC32_ISOHDLC => new Crc(StandardDefinitions.CRC32_ISOHDLC),
HashType.CRC32_JAMCRC => new Crc(StandardDefinitions.CRC32_JAMCRC),
@@ -292,14 +238,16 @@ namespace SabreTools.Hashing
HashType.FNV1a_32 => new FNV1a_32(),
HashType.FNV1a_64 => new FNV1a_64(),
HashType.MD2 => new MessageDigest.MD2(),
HashType.MD4 => new MessageDigest.MD4(),
HashType.MekaCrc => new MekaCrc(),
HashType.MD2 => new MD2(),
HashType.MD4 => new MD4(),
HashType.MD5 => MD5.Create(),
HashType.RIPEMD128 => new MessageDigest.RipeMD128(),
HashType.RIPEMD160 => new MessageDigest.RipeMD160(),
HashType.RIPEMD256 => new MessageDigest.RipeMD256(),
HashType.RIPEMD320 => new MessageDigest.RipeMD320(),
HashType.RIPEMD128 => new RipeMD128(),
HashType.RIPEMD160 => new RipeMD160(),
HashType.RIPEMD256 => new RipeMD256(),
HashType.RIPEMD320 => new RipeMD320(),
HashType.SHA1 => SHA1.Create(),
HashType.SHA256 => SHA256.Create(),
@@ -313,26 +261,26 @@ namespace SabreTools.Hashing
HashType.SHAKE256 => Shake256.IsSupported ? new Shake256() : null,
#endif
HashType.SpamSum => new SpamSumContext(),
HashType.SpamSum => new SpamSum.SpamSum(),
HashType.Tiger128_3 => new MessageDigest.Tiger128_3(),
HashType.Tiger128_4 => new MessageDigest.Tiger128_4(),
HashType.Tiger160_3 => new MessageDigest.Tiger160_3(),
HashType.Tiger160_4 => new MessageDigest.Tiger160_4(),
HashType.Tiger192_3 => new MessageDigest.Tiger192_3(),
HashType.Tiger192_4 => new MessageDigest.Tiger192_4(),
HashType.Tiger2_128_3 => new MessageDigest.Tiger2_128_3(),
HashType.Tiger2_128_4 => new MessageDigest.Tiger2_128_4(),
HashType.Tiger2_160_3 => new MessageDigest.Tiger2_160_3(),
HashType.Tiger2_160_4 => new MessageDigest.Tiger2_160_4(),
HashType.Tiger2_192_3 => new MessageDigest.Tiger2_192_3(),
HashType.Tiger2_192_4 => new MessageDigest.Tiger2_192_4(),
HashType.Tiger128_3 => new Tiger128_3(),
HashType.Tiger128_4 => new Tiger128_4(),
HashType.Tiger160_3 => new Tiger160_3(),
HashType.Tiger160_4 => new Tiger160_4(),
HashType.Tiger192_3 => new Tiger192_3(),
HashType.Tiger192_4 => new Tiger192_4(),
HashType.Tiger2_128_3 => new Tiger2_128_3(),
HashType.Tiger2_128_4 => new Tiger2_128_4(),
HashType.Tiger2_160_3 => new Tiger2_160_3(),
HashType.Tiger2_160_4 => new Tiger2_160_4(),
HashType.Tiger2_192_3 => new Tiger2_192_3(),
HashType.Tiger2_192_4 => new Tiger2_192_4(),
HashType.XxHash32 => new XxHash.XxHash32(),
HashType.XxHash64 => new XxHash.XxHash64(),
HashType.XxHash32 => new XxHash32(),
HashType.XxHash64 => new XxHash64(),
#if NET462_OR_GREATER || NETCOREAPP
HashType.XxHash3 => new XxHash3(),
HashType.XxHash128 => new XxHash128(),
HashType.XxHash3 => new System.IO.Hashing.XxHash3(),
HashType.XxHash128 => new System.IO.Hashing.XxHash128(),
#endif
_ => null,
};
@@ -356,26 +304,12 @@ namespace SabreTools.Hashing
{
switch (_hasher)
{
case ChecksumBase cb:
cb.TransformBlock(buffer, offset, size);
break;
case HashAlgorithm ha:
ha.TransformBlock(buffer, offset, size, null, 0);
break;
case IChecksum ic:
byte[] icBlock = new byte[size];
Array.Copy(buffer, offset, icBlock, 0, size);
ic.Update(icBlock);
break;
case MessageDigest.MessageDigestBase mdb:
mdb.TransformBlock(buffer, offset, size);
break;
#if NET462_OR_GREATER || NETCOREAPP
case NonCryptographicHashAlgorithm ncha:
case System.IO.Hashing.NonCryptographicHashAlgorithm ncha:
var nchaBufferSpan = new ReadOnlySpan<byte>(buffer, offset, size);
ncha.Append(nchaBufferSpan);
break;
@@ -392,11 +326,8 @@ namespace SabreTools.Hashing
break;
#endif
case XxHash.XxHash32 xxh32:
xxh32.TransformBlock(buffer, offset, size);
break;
case XxHash.XxHash64 xxh64:
xxh64.TransformBlock(buffer, offset, size);
default:
// No-op
break;
}
}
@@ -413,13 +344,43 @@ namespace SabreTools.Hashing
case HashAlgorithm ha:
ha.TransformFinalBlock(emptyBuffer, 0, 0);
break;
case MessageDigest.MessageDigestBase mdb:
mdb.Terminate();
default:
// No-op
break;
}
}
/// <summary>
/// Get the variable-length string representing a CRC value
/// </summary>
/// <param name="cr">Crc to get the value from</param>
/// <returns>String representing the CRC, null on error</returns>
private static string? GetCRCVariableLengthString(Crc cr)
{
// Ignore null values
if (cr.Hash is null)
return null;
// Get the total number of characters needed
ulong hash = BytesToUInt64(cr.Hash);
int length = (cr.Def.Width / 4) + (cr.Def.Width % 4 > 0 ? 1 : 0);
return hash.ToString($"x{length}");
}
/// <summary>
/// Get the Base64 representation of a SpamSum value
/// </summary>
/// <param name="ss">SpamSum to get the value from</param>
/// <returns>String representing the SpamSum, null on error</returns>
private static string? GetSpamSumBase64String(SpamSum.SpamSum ss)
{
// Ignore null values
if (ss.Hash is null)
return null;
return System.Text.Encoding.ASCII.GetString(ss.Hash);
}
#endregion
}
}
}

View File

@@ -0,0 +1,43 @@
namespace SabreTools.Hashing.NonCryptographicHash
{
internal static class Constants
{
#region FNV
public const uint FNV32Basis = 0x811c9dc5;
public const ulong FNV64Basis = 0xcbf29ce484222325;
public const uint FNV32Prime = 0x01000193;
public const ulong FNV64Prime = 0x00000100000001b3;
#endregion
#region xxHash-32
public const uint XXH_PRIME32_1 = 0x9E3779B1;
public const uint XXH_PRIME32_2 = 0x85EBCA77;
public const uint XXH_PRIME32_3 = 0xC2B2AE3D;
public const uint XXH_PRIME32_4 = 0x27D4EB2F;
public const uint XXH_PRIME32_5 = 0x165667B1;
#endregion
#region xxHash-64
public const ulong XXH_PRIME64_1 = 0x9E3779B185EBCA87;
public const ulong XXH_PRIME64_2 = 0xC2B2AE3D27D4EB4F;
public const ulong XXH_PRIME64_3 = 0x165667B19E3779F9;
public const ulong XXH_PRIME64_4 = 0x85EBCA77C2B2AE63;
public const ulong XXH_PRIME64_5 = 0x27D4EB2F165667C5;
#endregion
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV0_32 : FnvBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 32;
public FNV0_32()
{
_basis = 0;
_prime = FNV32Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV0_64 : FnvBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => 64;
public FNV0_64()
{
_basis = 0;
_prime = FNV64Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV1_32 : FnvBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 32;
public FNV1_32()
{
_basis = FNV32Basis;
_prime = FNV32Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV1_64 : FnvBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => 64;
public FNV1_64()
{
_basis = FNV64Basis;
_prime = FNV64Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV1a_32 : FnvBase<uint>
{
/// <inheritdoc/>
public override int HashSize => 32;
public FNV1a_32()
{
_basis = FNV32Basis;
_prime = FNV32Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -1,18 +1,21 @@
using static SabreTools.Hashing.Checksum.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.Checksum
namespace SabreTools.Hashing.NonCryptographicHash
{
public class FNV1a_64 : FnvBase<ulong>
{
/// <inheritdoc/>
public override int HashSize => 64;
public FNV1a_64()
{
_basis = FNV64Basis;
_prime = FNV64Prime;
Reset();
Initialize();
}
/// <inheritdoc/>
public override void TransformBlock(byte[] data, int offset, int length)
protected override void HashCore(byte[] data, int offset, int length)
{
for (int i = offset; length > 0; i++, length--)
{
@@ -20,4 +23,4 @@ namespace SabreTools.Hashing.Checksum
}
}
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
namespace SabreTools.Hashing.NonCryptographicHash
{
/// <summary>
/// Common base class for FNV non-cryptographic hashes
/// </summary>
public abstract class FnvBase : System.Security.Cryptography.HashAlgorithm
{
// No common, untyped functionality
}
/// <summary>
/// Common base class for FNV non-cryptographic hashes
/// </summary>
public abstract class FnvBase<T> : FnvBase where T : struct
{
/// <summary>
/// Initial value to use
/// </summary>
protected T _basis;
/// <summary>
/// Round prime to use
/// </summary>
protected T _prime;
/// <summary>
/// The current value of the hash
/// </summary>
protected T _hash;
/// <inheritdoc/>
public override void Initialize()
{
_hash = _basis;
}
/// <inheritdoc/>
protected override byte[] HashFinal()
{
byte[] hashArr = _hash switch
{
short s => BitConverter.GetBytes(s),
ushort s => BitConverter.GetBytes(s),
int i => BitConverter.GetBytes(i),
uint i => BitConverter.GetBytes(i),
long l => BitConverter.GetBytes(l),
ulong l => BitConverter.GetBytes(l),
_ => [],
};
Array.Reverse(hashArr);
return hashArr;
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
namespace SabreTools.Hashing.NonCryptographicHash
{
public class XxHash32 : System.Security.Cryptography.HashAlgorithm
{
/// <inheritdoc/>
public override int HashSize => 32;
/// <summary>
/// The 32-bit seed to alter the hash result predictably.
/// </summary>
private readonly uint _seed;
/// <summary>
/// Internal xxHash-32 state
/// </summary>
private readonly XxHash32State _state;
public XxHash32(uint seed = 0)
{
_seed = seed;
_state = new XxHash32State();
_state.Reset(seed);
}
/// <inheritdoc/>
public override void Initialize()
{
_state.Reset(_seed);
}
/// <inheritdoc/>
protected override void HashCore(byte[] data, int offset, int length)
=> _state.Update(data, offset, length);
/// <inheritdoc/>
protected override byte[] HashFinal()
{
uint hash = _state.Digest();
byte[] hashArr = BitConverter.GetBytes(hash);
Array.Reverse(hashArr);
return hashArr;
}
}
}

View File

@@ -1,14 +1,14 @@
using System;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.XxHash.Constants;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.XxHash
namespace SabreTools.Hashing.NonCryptographicHash
{
/// <summary>
/// Structure for XXH32 streaming API.
/// Structure for xxHash-32 streaming API.
/// </summary>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XXH32State
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XxHash32State
{
/// <summary>
/// Total length hashed, modulo 2^32
@@ -137,7 +137,7 @@ namespace SabreTools.Hashing.XxHash
/// <summary>
/// Normal stripe processing routine.
///
///
/// This shuffles the bits so that any bit from <paramref name="input"/> impacts
/// several bits in <paramref name="acc"/>.
/// </summary>
@@ -147,14 +147,14 @@ namespace SabreTools.Hashing.XxHash
private static uint Round(uint acc, uint input)
{
acc += input * XXH_PRIME32_2;
acc = RotateLeft32(acc, 13);
acc = RotateLeft32(acc, 13);
acc *= XXH_PRIME32_1;
return acc;
}
/// <summary>
/// Mixes all bits to finalize the hash.
///
///
/// The final mix ensures that all input bits have a chance to impact any bit in
/// the output digest, resulting in an unbiased distribution.
/// </summary>
@@ -170,7 +170,7 @@ namespace SabreTools.Hashing.XxHash
/// <summary>
/// Processes the last 0-15 bytes of @p ptr.
///
///
/// There may be up to 15 bytes remaining to consume from the input.
/// This final stage will digest them to ensure that all input bytes are present
/// in the final mix.
@@ -201,4 +201,4 @@ namespace SabreTools.Hashing.XxHash
return Avalanche(hash);
}
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
namespace SabreTools.Hashing.NonCryptographicHash
{
public class XxHash64 : System.Security.Cryptography.HashAlgorithm
{
/// <inheritdoc/>
public override int HashSize => 64;
/// <summary>
/// The 64-bit seed to alter the hash result predictably.
/// </summary>
private readonly uint _seed;
/// <summary>
/// Internal xxHash-64 state
/// </summary>
private readonly XxHash64State _state;
public XxHash64(uint seed = 0)
{
_seed = seed;
_state = new XxHash64State();
_state.Reset(seed);
}
/// <inheritdoc/>
public override void Initialize()
{
_state.Reset(_seed);
}
/// <inheritdoc/>
protected override void HashCore(byte[] data, int offset, int length)
=> _state.Update(data, offset, length);
/// <inheritdoc/>
protected override byte[] HashFinal()
{
ulong hash = _state.Digest();
byte[] hashArr = BitConverter.GetBytes(hash);
Array.Reverse(hashArr);
return hashArr;
}
}
}

View File

@@ -1,15 +1,14 @@
using System;
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.XxHash.Constants;
using static SabreTools.Hashing.XxHash.Utility;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
namespace SabreTools.Hashing.XxHash
namespace SabreTools.Hashing.NonCryptographicHash
{
/// <summary>
/// Structure for XXH64 streaming API.
/// Structure for xxHash-64 streaming API.
/// </summary>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XXH64State
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XxHash64State
{
/// <summary>
/// Total length hashed. This is always 64-bit.
@@ -135,7 +134,7 @@ namespace SabreTools.Hashing.XxHash
/// <summary>
/// Normal stripe processing routine.
///
///
/// This shuffles the bits so that any bit from @p input impacts
/// several bits in @p acc.
/// </summary>
@@ -145,7 +144,7 @@ namespace SabreTools.Hashing.XxHash
private static ulong Round(ulong acc, ulong input)
{
acc += unchecked(input * XXH_PRIME64_2);
acc = RotateLeft64(acc, 31);
acc = RotateLeft64(acc, 31);
acc *= XXH_PRIME64_1;
return acc;
}
@@ -154,13 +153,13 @@ namespace SabreTools.Hashing.XxHash
{
val = Round(0, val);
acc ^= val;
acc = acc * XXH_PRIME64_1 + XXH_PRIME64_4;
acc = (acc * XXH_PRIME64_1) + XXH_PRIME64_4;
return acc;
}
/// <summary>
/// Processes the last 0-31 bytes of @p ptr.
///
///
/// There may be up to 31 bytes remaining to consume from the input.
/// This final stage will digest them to ensure that all input bytes are present
/// in the final mix.
@@ -179,7 +178,7 @@ namespace SabreTools.Hashing.XxHash
ulong k1 = Round(0, ReadLE64(data, offset));
offset += 8;
hash ^= k1;
hash = RotateLeft64(hash, 27) * XXH_PRIME64_1 + XXH_PRIME64_4;
hash = (RotateLeft64(hash, 27) * XXH_PRIME64_1) + XXH_PRIME64_4;
length -= 8;
}
@@ -187,7 +186,7 @@ namespace SabreTools.Hashing.XxHash
{
hash ^= ReadLE32(data, offset) * XXH_PRIME64_1;
offset += 4;
hash = RotateLeft64(hash, 23) * XXH_PRIME64_2 + XXH_PRIME64_3;
hash = (RotateLeft64(hash, 23) * XXH_PRIME64_2) + XXH_PRIME64_3;
length -= 4;
}
@@ -198,7 +197,23 @@ namespace SabreTools.Hashing.XxHash
--length;
}
return XXH64Avalanche(hash);
return Avalanche(hash);
}
/// <summary>
/// Mixes all bits to finalize the hash.
///
/// The final mix ensures that all input bits have a chance to impact any bit in
/// the output digest, resulting in an unbiased distribution.
/// </summary>
private static ulong Avalanche(ulong hash)
{
hash ^= hash >> 33;
hash *= XXH_PRIME64_2;
hash ^= hash >> 29;
hash *= XXH_PRIME64_3;
hash ^= hash >> 32;
return hash;
}
}
}
}

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<IncludeSymbols>true</IncludeSymbols>
@@ -11,11 +11,11 @@
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>1.4.1</Version>
<Version>1.6.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Copyright>Copyright (c)2016-2024 Matt Nadareski</Copyright>
<Copyright>Copyright (c)2016-2025 Matt Nadareski</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Hashing</RepositoryUrl>
@@ -28,12 +28,10 @@
<None Include="../README.md" Pack="true" PackagePath="" />
</ItemGroup>
<!-- Support for old .NET versions -->
<ItemGroup Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net45`))">
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
<PackageReference Include="Blake3" Version="1.1.0" />
<ItemGroup>
<PackageReference Include="Blake3" Version="1.1.0" Condition="$(TargetFramework.StartsWith(`net7`))" />
<PackageReference Include="Blake3" Version="2.0.0" Condition="$(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`)) OR $(TargetFramework.StartsWith(`net10`))" />
<PackageReference Include="System.IO.Hashing" Version="10.0.0" Condition="!$(TargetFramework.StartsWith(`net2`)) AND !$(TargetFramework.StartsWith(`net3`)) AND !$(TargetFramework.StartsWith(`net40`)) AND !$(TargetFramework.StartsWith(`net45`))" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using static SabreTools.Hashing.SpamSum.Constants;
namespace SabreTools.Hashing.SpamSum
{
/// <summary>
/// A blockhash contains a signature state for a specific (implicit) blocksize.
/// The blocksize is given by <see cref="SSDEEP_BS(uint)"/>
/// </summary>
/// <see href="https://github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c"/>
internal class BlockhashContext
{
/// <summary>
/// Current digest length
/// </summary>
public uint DIndex { get; set; }
/// <summary>
/// Current message digest
/// </summary>
public byte[] Digest { get; set; }
/// <summary>
/// Digest value at <see cref="HalfH"/>
/// </summary>
public byte HalfDigest { get; set; }
/// <summary>
/// Partial FNV hash
/// </summary>
public byte H { get; set; }
/// <summary>
/// Partial FNV hash reset after <see cref="Digest"/> is
/// <see cref="SPAMSUM_LENGTH"/> / 2 long. This is needed
/// to be able to truncate digest for the second output hash
/// to stay compatible with ssdeep output.
/// </summary>
public byte HalfH { get; set; }
public BlockhashContext()
{
Digest = new byte[SPAMSUM_LENGTH];
}
}
}

View File

@@ -0,0 +1,201 @@
using System;
using System.Text.RegularExpressions;
namespace SabreTools.Hashing.SpamSum;
internal static class Comparisons
{
/// <summary>
/// Regex to reduce any sequences longer than 3
/// </summary>
private static readonly Regex _reduceRegex = new("(.)(?<=\\1\\1\\1\\1)", RegexOptions.Compiled);
/// <summary>
/// Compares how similar two SpamSums are to each other
/// </summary>
/// <param name="first">First hash to compare</param>
/// <param name="second">Second hash to compare</param>
/// <returns>-1 on validity failure, 0 if they're not comparable, score from 0 (least similar) to 100 (most similar) otherwise.</returns>
/// <remarks>Implements ssdeep's fuzzy_compare</remarks>
/// <see href="https://github.com/ssdeep-project/ssdeep/blob/df3b860f8918261b3faeec9c7d2c8a241089e6e6/fuzzy.c#L860"/>
public static int FuzzyCompare(string? first, string? second)
{
// If either input is invalid
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second))
return -1;
// Split the string into 3 parts for processing
var firstSplit = first!.Split(':');
var secondSplit = second!.Split(':');
if (firstSplit.Length != 3 || secondSplit.Length != 3)
return -1;
// If any of the required parts are empty
if (firstSplit[0].Length == 0 || firstSplit[2].Length == 0)
return -1;
if (secondSplit[0].Length == 0 || secondSplit[2].Length == 0)
return -1;
// Ensure only second block data before a comma is used
firstSplit[2] = firstSplit[2].Split(',')[0];
secondSplit[2] = secondSplit[2].Split(',')[0];
// Each SpamSum string starts with its block size before the first semicolon.
if (!uint.TryParse(firstSplit[0], out uint firstBlockSize))
return -1;
if (!uint.TryParse(secondSplit[0], out uint secondBlockSize))
return -1;
// Check if blocksizes don't match. Each spamSum is broken up into two blocks.
// fuzzy_compare allows you to compare if one block in one hash is the same
// size as one block in the other hash, even if the other two are non-matching,
// so that's also checked for.
if (firstBlockSize != secondBlockSize
&& (firstBlockSize > uint.MaxValue / 2 || firstBlockSize * 2 != secondBlockSize)
&& (firstBlockSize % 2 == 1 || firstBlockSize / 2 != secondBlockSize))
{
return 0;
}
// Reduce any sequences longer than 3
// These sequences contain very little info and can be reduced as a result
string firstBlockOne = _reduceRegex.Replace(firstSplit[1], string.Empty);
string firstBlockTwo = _reduceRegex.Replace(firstSplit[2], string.Empty);
string secondBlockOne = _reduceRegex.Replace(secondSplit[1], string.Empty);
string secondBlockTwo = _reduceRegex.Replace(secondSplit[2], string.Empty);
// Return 100 immediately if both spamSums are identical.
if (firstBlockSize == secondBlockSize && firstBlockOne == secondBlockOne && firstBlockTwo == secondBlockTwo)
return 100;
// Choose different scoring combinations depending on block sizes present.
if (firstBlockSize <= uint.MaxValue / 2)
{
if (firstBlockSize == secondBlockSize)
{
uint score1 = ScoreStrings(firstBlockOne, secondBlockOne, firstBlockSize);
uint score2 = ScoreStrings(firstBlockTwo, secondBlockTwo, firstBlockSize * 2);
return (int)Math.Max(score1, score2);
}
else if (firstBlockSize * 2 == secondBlockSize)
{
return (int)ScoreStrings(secondBlockOne, firstBlockTwo, secondBlockSize);
}
else
{
return (int)ScoreStrings(firstBlockOne, secondBlockTwo, firstBlockSize);
}
}
else
{
if (firstBlockSize == secondBlockSize)
return (int)ScoreStrings(firstBlockOne, secondBlockOne, firstBlockSize);
else if (firstBlockSize % 2 == 0 && firstBlockSize / 2 == secondBlockSize)
return (int)ScoreStrings(firstBlockOne, secondBlockTwo, firstBlockSize);
else
return 0;
}
}
/// <summary>
/// Checks whether the two SpamSum strings have a common substring of 7 or more characters (as defined in fuzzy_compare's ROLLING_WINDOW size).
/// </summary>
/// <param name="first">First string to score</param>
/// <param name="second">Second string to score</param>
/// <returns>False if there is no common substring of 7 or more characters, true if there is.</returns>
private static bool HasCommmonSubstring(string first, string second)
{
// If either string is less than 7 characters
if (first.Length < 7 || second.Length < 7)
return false;
for (int i = 0; i < first.Length; i++)
{
for (int j = 0; j < second.Length; j++)
{
int currentIndex = 0;
while ((i + currentIndex) < first.Length && (j + currentIndex) < second.Length && first[i + currentIndex] == second[j + currentIndex])
{
currentIndex++;
}
if (currentIndex >= 7)
return true;
}
}
return false;
}
/// <summary>
/// Compares how similar two SpamSums are to each other. Implements ssdeep's fuzzy_compare.
/// </summary>
/// <param name="first">First string to score</param>
/// <param name="second">Second string to score</param>
/// <param name="blockSize">Current blocksize</param>
/// <returns>-1 on validity failure, 0 if they're not comparable, score from 0 (least similar) to 100 (most similar) otherwise.</returns>
private static uint ScoreStrings(string first, string second, uint blockSize)
{
if (!HasCommmonSubstring(first, second))
return 0;
const uint maxLength = 64;
const uint insertCost = 1;
const uint removeCost = 1;
const uint replaceCost = 2;
var firstTraverse = new uint[maxLength + 1];
var secondTraverse = new uint[maxLength + 1];
for (uint secondIndex = 0; secondIndex <= second.Length; secondIndex++)
{
firstTraverse[secondIndex] = secondIndex * removeCost;
}
for (uint firstIndex = 0; firstIndex < first.Length; firstIndex++)
{
secondTraverse[0] = (firstIndex + 1) * insertCost;
for (uint secondIndex = 0; secondIndex < second.Length; secondIndex++)
{
var costA = firstTraverse[secondIndex + 1] + insertCost;
var costD = secondTraverse[secondIndex] + removeCost;
var costR = firstTraverse[secondIndex] + (first[(int)firstIndex] == second[(int)secondIndex] ? 0 : replaceCost);
secondTraverse[secondIndex + 1] = Math.Min(Math.Min(costA, costD), costR);
}
#pragma warning disable IDE0180 // Use tuple to swap values
#if NETCOREAPP || NETSTANDARD2_0_OR_GREATER
(secondTraverse, firstTraverse) = (firstTraverse, secondTraverse);
#else
var tempArray = firstTraverse;
firstTraverse = secondTraverse;
secondTraverse = tempArray;
#endif
#pragma warning restore IDE0180
}
long score = firstTraverse[second.Length];
const int spamSumLength = 64;
const int rollingWindow = 7;
const int minBlocksize = 3;
score = score * spamSumLength / (first.Length + second.Length);
// Currently, the score ranges from 0-64 (64 being the length of a spamsum), with 0 being the strongest match
// and 64 being the weakest match.
// Change scale to 0-100
score = 100 * score / spamSumLength;
// Invert scale so 0 is the weakest possible match and 100 is the strongest
score = 100 - score;
// Compensate for small blocksizes, so match isn't reported as overly strong.
if (blockSize >= (99 + rollingWindow) / rollingWindow * minBlocksize)
return (uint)score;
if (score > blockSize / minBlocksize * Math.Min(first.Length, second.Length))
score = blockSize / minBlocksize * Math.Min(first.Length, second.Length);
return (uint)score;
}
}

View File

@@ -0,0 +1,446 @@
using System.Text;
namespace SabreTools.Hashing.SpamSum
{
/// <see href="github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c"/>
/// <see href="github.com/ssdeep-project/ssdeep/blob/master/fuzzy.h"/>
internal static class Constants
{
/// <summary>
/// fuzzy_digest flag indicating to eliminate sequences of more than
/// three identical characters
/// </summary>
public const uint FUZZY_FLAG_ELIMSEQ = 1;
/// <summary>
/// fuzzy_digest flag indicating not to truncate the second part to
/// SPAMSUM_LENGTH / 2 characters.
/// </summary>
public const uint FUZZY_FLAG_NOTRUNC = 2;
/// <summary>
/// Length of an individual fuzzy hash signature component.
/// </summary>
public const int SPAMSUM_LENGTH = 64;
/// <summary>
/// The longest possible length for a fuzzy hash signature
/// (without the filename)
/// </summary>
public const int FUZZY_MAX_RESULT = (2 * SPAMSUM_LENGTH) + 20;
public const uint ROLLING_WINDOW = 7;
public const uint MIN_BLOCKSIZE = 3;
public const byte HASH_INIT = 0x27;
public const int NUM_BLOCKHASHES = 31;
public const uint FUZZY_STATE_NEED_LASTHASH = 1;
public const uint FUZZY_STATE_SIZE_FIXED = 2;
public static uint SSDEEP_BS(uint index) => MIN_BLOCKSIZE << (int)index;
public static ulong SSDEEP_TOTAL_SIZE_MAX
=> (ulong)SSDEEP_BS(NUM_BLOCKHASHES - 1) * SPAMSUM_LENGTH;
public static readonly byte[] B64 = Encoding.ASCII.GetBytes("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
#region Precomputed Tables
/// <summary>
/// Precomputed patrial FNV hash table
/// </summary>
public static readonly byte[][] SUM_TABLE = // [64][64]
[
[ // 0x00
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
],
[ // 0x01
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14, 0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c,
0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34, 0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c,
0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24, 0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c,
],
[ // 0x02
0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29,
0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39,
0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09,
0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19,
],
[ // 0x03
0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e, 0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36,
0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e, 0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26,
0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e, 0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16,
0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06,
],
[ // 0x04
0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03,
0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13,
0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23,
0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33,
],
[ // 0x05
0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
],
[ // 0x06
0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35, 0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d,
0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25, 0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d,
0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15, 0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d,
0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05, 0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d,
],
[ // 0x07
0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02, 0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a,
0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12, 0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a,
0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22, 0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a,
0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32, 0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a,
],
[ // 0x08
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
],
[ // 0x09
0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c, 0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24,
0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c, 0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34,
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c, 0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
],
[ // 0x0a
0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39, 0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31,
0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21,
0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11,
0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01,
],
[ // 0x0b
0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16, 0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e,
0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e,
0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36, 0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e,
0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26, 0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e,
],
[ // 0x0c
0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b,
0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b,
0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b,
0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b,
],
[ // 0x0d
0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38,
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28,
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
],
[ // 0x0e
0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d, 0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05,
0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d, 0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15,
0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d, 0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25,
0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d, 0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35,
],
[ // 0x0f
0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a, 0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12,
0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a, 0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02,
0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a, 0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32,
0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a, 0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22,
],
[ // 0x10
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
],
[ // 0x11
0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14, 0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c,
0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24, 0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c,
0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34, 0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c,
],
[ // 0x12
0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19,
0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09,
0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39,
0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29,
],
[ // 0x13
0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e, 0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26,
0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e, 0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36,
0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06,
0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e, 0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16,
],
[ // 0x14
0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33,
0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23,
0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13,
0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03,
],
[ // 0x15
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
],
[ // 0x16
0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25, 0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d,
0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35, 0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d,
0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05, 0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d,
0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15, 0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d,
],
[ // 0x17
0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32, 0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a,
0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22, 0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a,
0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12, 0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a,
0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02, 0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a,
],
[ // 0x18
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
],
[ // 0x19
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c, 0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c, 0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34,
0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c, 0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24,
],
[ // 0x1a
0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21,
0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39, 0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31,
0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01,
0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11,
],
[ // 0x1b
0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e,
0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16, 0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e,
0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26, 0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e,
0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36, 0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e,
],
[ // 0x1c
0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b,
0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b,
0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b,
0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b,
],
[ // 0x1d
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28,
0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18,
],
[ // 0x1e
0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d, 0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35,
0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d, 0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25,
0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d, 0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15,
0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d, 0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05,
],
[ // 0x1f
0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a, 0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02,
0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a, 0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12,
0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a, 0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22,
0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a, 0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32,
],
[ // 0x20
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
],
[ // 0x21
0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34, 0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c,
0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24, 0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c,
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14, 0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c,
0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
],
[ // 0x22
0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09,
0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19,
0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29,
0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39,
],
[ // 0x23
0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e, 0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16,
0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06,
0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e, 0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36,
0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e, 0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26,
],
[ // 0x24
0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23,
0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33,
0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03,
0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13,
],
[ // 0x25
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
],
[ // 0x26
0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15, 0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d,
0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05, 0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d,
0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35, 0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d,
0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25, 0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d,
],
[ // 0x27
0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22, 0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a,
0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32, 0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a,
0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02, 0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a,
0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12, 0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a,
],
[ // 0x28
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
],
[ // 0x29
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c, 0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c, 0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24,
0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c, 0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34,
],
[ // 0x2a
0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11,
0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01,
0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39, 0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31,
0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21,
],
[ // 0x2b
0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36, 0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e,
0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26, 0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e,
0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16, 0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e,
0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e,
],
[ // 0x2c
0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b,
0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b,
0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b,
0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b,
],
[ // 0x2d
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18,
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38,
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28,
],
[ // 0x2e
0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d, 0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25,
0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d, 0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35,
0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d, 0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05,
0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d, 0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15,
],
[ // 0x2f
0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a, 0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32,
0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a, 0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22,
0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a, 0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12,
0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a, 0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02,
],
[ // 0x30
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
],
[ // 0x31
0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24, 0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c,
0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34, 0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c,
0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c,
0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14, 0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c,
],
[ // 0x32
0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31, 0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39,
0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21, 0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29,
0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11, 0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19,
0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01, 0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09,
],
[ // 0x33
0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06,
0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e, 0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16,
0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e, 0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26,
0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e, 0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36,
],
[ // 0x34
0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13,
0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03,
0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b, 0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33,
0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23,
],
[ // 0x35
0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20,
0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30,
0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10,
],
[ // 0x36
0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05, 0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d,
0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15, 0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d,
0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25, 0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d,
0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35, 0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d,
],
[ // 0x37
0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12, 0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a,
0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02, 0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a,
0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32, 0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a,
0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22, 0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a,
],
[ // 0x38
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
],
[ // 0x39
0x3b, 0x3a, 0x39, 0x38, 0x3f, 0x3e, 0x3d, 0x3c, 0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34,
0x2b, 0x2a, 0x29, 0x28, 0x2f, 0x2e, 0x2d, 0x2c, 0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24,
0x1b, 0x1a, 0x19, 0x18, 0x1f, 0x1e, 0x1d, 0x1c, 0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14,
0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04,
],
[ // 0x3a
0x0e, 0x0f, 0x0c, 0x0d, 0x0a, 0x0b, 0x08, 0x09, 0x06, 0x07, 0x04, 0x05, 0x02, 0x03, 0x00, 0x01,
0x1e, 0x1f, 0x1c, 0x1d, 0x1a, 0x1b, 0x18, 0x19, 0x16, 0x17, 0x14, 0x15, 0x12, 0x13, 0x10, 0x11,
0x2e, 0x2f, 0x2c, 0x2d, 0x2a, 0x2b, 0x28, 0x29, 0x26, 0x27, 0x24, 0x25, 0x22, 0x23, 0x20, 0x21,
0x3e, 0x3f, 0x3c, 0x3d, 0x3a, 0x3b, 0x38, 0x39, 0x36, 0x37, 0x34, 0x35, 0x32, 0x33, 0x30, 0x31,
],
[ // 0x3b
0x21, 0x20, 0x23, 0x22, 0x25, 0x24, 0x27, 0x26, 0x29, 0x28, 0x2b, 0x2a, 0x2d, 0x2c, 0x2f, 0x2e,
0x31, 0x30, 0x33, 0x32, 0x35, 0x34, 0x37, 0x36, 0x39, 0x38, 0x3b, 0x3a, 0x3d, 0x3c, 0x3f, 0x3e,
0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x09, 0x08, 0x0b, 0x0a, 0x0d, 0x0c, 0x0f, 0x0e,
0x11, 0x10, 0x13, 0x12, 0x15, 0x14, 0x17, 0x16, 0x19, 0x18, 0x1b, 0x1a, 0x1d, 0x1c, 0x1f, 0x1e,
],
[ // 0x3c
0x34, 0x35, 0x36, 0x37, 0x30, 0x31, 0x32, 0x33, 0x3c, 0x3d, 0x3e, 0x3f, 0x38, 0x39, 0x3a, 0x3b,
0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2d, 0x2e, 0x2f, 0x28, 0x29, 0x2a, 0x2b,
0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1c, 0x1d, 0x1e, 0x1f, 0x18, 0x19, 0x1a, 0x1b,
0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0c, 0x0d, 0x0e, 0x0f, 0x08, 0x09, 0x0a, 0x0b,
],
[ // 0x3d
0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08,
0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18,
0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28,
0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38,
],
[ // 0x3e
0x1a, 0x1b, 0x18, 0x19, 0x1e, 0x1f, 0x1c, 0x1d, 0x12, 0x13, 0x10, 0x11, 0x16, 0x17, 0x14, 0x15,
0x0a, 0x0b, 0x08, 0x09, 0x0e, 0x0f, 0x0c, 0x0d, 0x02, 0x03, 0x00, 0x01, 0x06, 0x07, 0x04, 0x05,
0x3a, 0x3b, 0x38, 0x39, 0x3e, 0x3f, 0x3c, 0x3d, 0x32, 0x33, 0x30, 0x31, 0x36, 0x37, 0x34, 0x35,
0x2a, 0x2b, 0x28, 0x29, 0x2e, 0x2f, 0x2c, 0x2d, 0x22, 0x23, 0x20, 0x21, 0x26, 0x27, 0x24, 0x25,
],
[ // 0x3f
0x2d, 0x2c, 0x2f, 0x2e, 0x29, 0x28, 0x2b, 0x2a, 0x25, 0x24, 0x27, 0x26, 0x21, 0x20, 0x23, 0x22,
0x3d, 0x3c, 0x3f, 0x3e, 0x39, 0x38, 0x3b, 0x3a, 0x35, 0x34, 0x37, 0x36, 0x31, 0x30, 0x33, 0x32,
0x0d, 0x0c, 0x0f, 0x0e, 0x09, 0x08, 0x0b, 0x0a, 0x05, 0x04, 0x07, 0x06, 0x01, 0x00, 0x03, 0x02,
0x1d, 0x1c, 0x1f, 0x1e, 0x19, 0x18, 0x1b, 0x1a, 0x15, 0x14, 0x17, 0x16, 0x11, 0x10, 0x13, 0x12,
],
];
#endregion
}
}

View File

@@ -0,0 +1,91 @@
using System;
using static SabreTools.Hashing.SpamSum.Constants;
namespace SabreTools.Hashing.SpamSum
{
/// <see href="https://github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c"/>
internal class FuzzyState
{
public ulong TotalSize { get; set; }
public ulong FixedSize { get; set; }
public ulong ReduceBorder { get; set; }
public uint BHStart { get; set; }
public uint BHEnd { get; set; }
public uint BHEndLimit { get; set; }
public uint Flags { get; set; }
public uint RollMask { get; set; }
public BlockhashContext[] BH { get; set; }
public RollState Roll { get; set; }
public byte LastH { get; set; }
public FuzzyState()
{
BH = new BlockhashContext[NUM_BLOCKHASHES];
for (int i = 0; i < NUM_BLOCKHASHES; i++)
{
BH[i] = new BlockhashContext();
}
Roll = new RollState();
}
public void TryForkBlockhash()
{
uint obh, nbh;
if (BHEnd <= 0)
throw new Exception("assert(BHEnd > 0)");
obh = BHEnd - 1;
if (BHEnd <= BHEndLimit)
{
nbh = obh + 1;
BH[nbh].H = BH[obh].H;
BH[nbh].HalfH = BH[obh].HalfH;
BH[nbh].Digest[0] = 0x00;
BH[nbh].HalfDigest = 0x00;
BH[nbh].DIndex = 0;
++BHEnd;
}
else if (BHEnd == NUM_BLOCKHASHES
&& ((Flags & FUZZY_STATE_NEED_LASTHASH) == 0))
{
Flags |= FUZZY_STATE_NEED_LASTHASH;
LastH = BH[obh].H;
}
}
public void TryReduceBlockhash()
{
if (BHStart >= BHEnd)
throw new Exception("assert(BHStart < BHEnd)");
// Need at least two working hashes.
if (BHEnd - BHStart < 2)
return;
// Initial blocksize estimate would select this or a smaller blocksize.
if (ReduceBorder >= (((Flags & FUZZY_STATE_SIZE_FIXED) != 0) ? FixedSize : TotalSize))
return;
// Estimate adjustment would select this blocksize.
if (BH[BHStart + 1].DIndex < SPAMSUM_LENGTH / 2)
return;
// At this point we are clearly no longer interested in the
// start_blocksize. Get rid of it.
++BHStart;
ReduceBorder *= 2;
RollMask = (RollMask * 2) + 1;
}
}
}

View File

@@ -0,0 +1,56 @@
using static SabreTools.Hashing.SpamSum.Constants;
namespace SabreTools.Hashing.SpamSum
{
/// <see href="https://github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c"/>
internal class RollState
{
public byte[] Window { get; set; }
public uint H1 { get; set; }
public uint H2 { get; set; }
public uint H3 { get; set; }
public uint N { get; set; }
public RollState()
{
Window = new byte[ROLLING_WINDOW];
}
/// <summary>
/// A rolling hash, based on the Adler checksum. By using a rolling hash
/// we can perform auto resynchronisation after inserts/deletes.
///
/// Internally, H1 is the sum of the bytes in the window and H2
/// is the sum of the bytes times the index.
///
/// H3 is a shift/xor based rolling hash, and is mostly needed to ensure that
/// we can cope with large blocksize values.
/// </summary>
public void RollHash(byte c)
{
H2 -= H1;
H2 += ROLLING_WINDOW * c;
H1 += c;
H1 -= Window[N % ROLLING_WINDOW];
Window[N % ROLLING_WINDOW] = c;
N++;
// The original spamsum AND'ed this value with 0xFFFFFFFF which
// in theory should have no effect. This AND has been removed
// for performance (jk)
H3 <<= 5;
H3 ^= c;
}
/// <summary>
/// Return the current rolling sum
/// </summary>
public uint RollSum() => H1 + H2 + H3;
}
}

View File

@@ -0,0 +1,361 @@
using System;
using System.Text;
using static SabreTools.Hashing.SpamSum.Constants;
#pragma warning disable IDE0059 // Unnecessary assignment of a value
namespace SabreTools.Hashing.SpamSum
{
/// <see href="https://github.com/ssdeep-project/ssdeep/blob/master/fuzzy.c"/>
public class SpamSum : System.Security.Cryptography.HashAlgorithm
{
private FuzzyState _state;
public SpamSum()
{
_state = new();
Initialize();
}
/// <inheritdoc/>
public override void Initialize()
{
_state = new FuzzyState
{
BHStart = 0,
BHEnd = 1,
BHEndLimit = NUM_BLOCKHASHES - 1,
TotalSize = 0,
ReduceBorder = MIN_BLOCKSIZE * SPAMSUM_LENGTH,
Flags = 0,
RollMask = 0,
};
_state.BH[0].H = HASH_INIT;
_state.BH[0].HalfH = HASH_INIT;
_state.BH[0].Digest[0] = 0x00;
_state.BH[0].HalfDigest = 0x00;
_state.BH[0].DIndex = 0;
}
/// <inheritdoc cref="Comparisons.FuzzyCompare(string?, string?)"/>
public static int FuzzyCompare(string? firstHash, string? secondHash)
=> Comparisons.FuzzyCompare(firstHash, secondHash);
/// <inheritdoc/>
protected override void HashCore(byte[] array, int ibStart, int cbSize)
{
_state.TotalSize += (ulong)cbSize;
for (int i = ibStart; i < cbSize; i++)
{
ProcessByte(array[i]);
}
}
/// <inheritdoc/>
protected override byte[] HashFinal()
{
string? digest = Finalize(0);
if (digest is null)
return [];
return Encoding.ASCII.GetBytes(digest.TrimEnd('\0'));
}
/// <remarks>
/// Originally named `fuzzy_engine_step`
/// </remarks>
private void ProcessByte(byte c)
{
// At each character we update the rolling hash and the normal hashes.
// When the rolling hash hits a reset value then we emit a normal hash
// as a element of the signature and reset the normal hash.
_state.Roll.RollHash(c);
uint horg = _state.Roll.RollSum() + 1;
uint h = horg / MIN_BLOCKSIZE;
uint i;
for (i = _state.BHStart; i < _state.BHEnd; ++i)
{
_state.BH[i].H = SumHash(c, _state.BH[i].H);
_state.BH[i].HalfH = SumHash(c, _state.BH[i].HalfH);
}
if ((_state.Flags & FUZZY_STATE_NEED_LASTHASH) != 0)
_state.LastH = SumHash(c, _state.LastH);
// 0xffffffff !== -1 (mod 3)
if (horg == 0)
return;
// With growing blocksize almost no runs fail the next test.
if ((h & _state.RollMask) != 0)
return;
// Delay computation of modulo as possible.
if ((horg % MIN_BLOCKSIZE) != 0)
return;
h >>= (int)_state.BHStart;
i = _state.BHStart;
do
{
// We have hit a reset point. We now emit hashes which are
// based on all characters in the piece of the message between
// the last reset point and this one
if (_state.BH[i].DIndex == 0)
{
// Can only happen 30 times.
// First step for this blocksize. Clone next.
_state.TryForkBlockhash();
}
_state.BH[i].Digest[_state.BH[i].DIndex] = B64[_state.BH[i].H];
_state.BH[i].HalfDigest = B64[_state.BH[i].HalfH];
if (_state.BH[i].DIndex < SPAMSUM_LENGTH - 1)
{
// We can have a problem with the tail overflowing. The
// easiest way to cope with this is to only reset the
// normal hash if we have room for more characters in
// our signature. This has the effect of combining the
// last few pieces of the message into a single piece
_state.BH[i].Digest[++_state.BH[i].DIndex] = 0x00;
_state.BH[i].H = HASH_INIT;
if (_state.BH[i].DIndex < SPAMSUM_LENGTH / 2)
{
_state.BH[i].HalfH = HASH_INIT;
_state.BH[i].HalfDigest = 0x00;
}
}
else
{
_state.TryReduceBlockhash();
}
if ((h & 1) != 0)
break;
h >>= 1;
} while (++i < _state.BHEnd);
}
/// <summary>
/// A simple non-rolling hash, based on the FNV hash
/// </summary>
private static byte SumHash(byte c, byte h) => SUM_TABLE[h][c & 0x3f];
/// <remarks>
/// Originally named `fuzzy_digest`
/// </remarks>
private string? Finalize(uint flags)
{
uint bi = _state.BHStart;
uint h = _state.Roll.RollSum();
int i;
// Exclude terminating '\0'.
int remain = FUZZY_MAX_RESULT - 1;
// Verify that our elimination was not overeager.
if (bi != 0 && (ulong)SSDEEP_BS(bi) / 2 * SPAMSUM_LENGTH >= _state.TotalSize)
return null;
// The input exceeds data types.
if (_state.TotalSize > SSDEEP_TOTAL_SIZE_MAX)
return null;
// Initial blocksize guess.
while ((ulong)SSDEEP_BS(bi) * SPAMSUM_LENGTH < _state.TotalSize)
{
++bi;
}
// Adapt blocksize guess to actual digest length.
if (bi >= _state.BHEnd)
bi = _state.BHEnd - 1;
while (bi > _state.BHStart && _state.BH[bi].DIndex < SPAMSUM_LENGTH / 2)
{
--bi;
}
if (bi > 0 && _state.BH[bi].DIndex < SPAMSUM_LENGTH / 2)
return null;
byte[] result = new byte[FUZZY_MAX_RESULT];
int resultPtr = 0;
string prefixStr = $"{(ulong)SSDEEP_BS(bi)}:";
byte[] prefixArr = Encoding.ASCII.GetBytes(prefixStr);
Array.Copy(prefixArr, result, prefixArr.Length);
i = prefixArr.Length;
if (i >= remain)
return null;
remain -= i;
resultPtr += i;
i = (int)_state.BH[bi].DIndex;
if (i > remain)
return null;
if ((flags & FUZZY_FLAG_ELIMSEQ) != 0)
i = EliminateSequences(result, resultPtr, _state.BH[bi].Digest, 0, i);
else
Array.Copy(_state.BH[bi].Digest, 0, result, resultPtr, i);
resultPtr += i;
remain -= i;
if (h != 0)
{
if (remain <= 0)
return null;
result[resultPtr] = B64[_state.BH[bi].H];
if ((flags & FUZZY_FLAG_ELIMSEQ) == 0
|| i < 3
|| result[resultPtr] != result[resultPtr - 1]
|| result[resultPtr] != result[resultPtr - 2]
|| result[resultPtr] != result[resultPtr - 3])
{
++resultPtr;
--remain;
}
}
else if (_state.BH[bi].Digest[_state.BH[bi].DIndex] != '\0')
{
if (remain <= 0)
return null;
result[resultPtr] = _state.BH[bi].Digest[_state.BH[bi].DIndex];
if ((flags & FUZZY_FLAG_ELIMSEQ) == 0
|| i < 3
|| result[resultPtr] != result[resultPtr - 1]
|| result[resultPtr] != result[resultPtr - 2]
|| result[resultPtr] != result[resultPtr - 3])
{
++resultPtr;
--remain;
}
}
if (remain <= 0)
return null;
result[resultPtr++] = (byte)':';
--remain;
if (bi < _state.BHEnd - 1)
{
++bi;
i = (int)_state.BH[bi].DIndex;
if ((flags & FUZZY_FLAG_NOTRUNC) == 0 && i > (SPAMSUM_LENGTH / 2) - 1)
i = (SPAMSUM_LENGTH / 2) - 1;
if (i > remain)
return null;
if ((flags & FUZZY_FLAG_ELIMSEQ) != 0)
i = EliminateSequences(result, resultPtr, _state.BH[bi].Digest, 0, i);
else
Array.Copy(_state.BH[bi].Digest, 0, result, resultPtr, i);
resultPtr += i;
remain -= i;
if (h != 0)
{
if (remain <= 0)
return null;
h = (flags & FUZZY_FLAG_NOTRUNC) != 0
? _state.BH[bi].H
: _state.BH[bi].HalfH;
result[resultPtr] = B64[h];
if ((flags & FUZZY_FLAG_ELIMSEQ) == 0
|| i < 3
|| result[resultPtr] != result[resultPtr - 1]
|| result[resultPtr] != result[resultPtr - 2]
|| result[resultPtr] != result[resultPtr - 3])
{
++resultPtr;
--remain;
}
}
else
{
i = (flags & FUZZY_FLAG_NOTRUNC) != 0
? _state.BH[bi].Digest[_state.BH[bi].DIndex]
: _state.BH[bi].HalfDigest;
if (i != 0x00)
{
if (remain <= 0)
return null;
result[resultPtr] = (byte)i;
if ((flags & FUZZY_FLAG_ELIMSEQ) == 0
|| i < 3
|| result[resultPtr] != result[resultPtr - 1]
|| result[resultPtr] != result[resultPtr - 2]
|| result[resultPtr] != result[resultPtr - 3])
{
++resultPtr;
--remain;
}
}
}
}
else if (h != 0)
{
if (bi != 0 && bi != NUM_BLOCKHASHES - 1)
return null;
if (remain <= 0)
return null;
if (bi == 0)
result[resultPtr++] = B64[_state.BH[bi].H];
else
result[resultPtr++] = B64[_state.LastH];
/* No need to bother with FUZZY_FLAG_ELIMSEQ, because this
* digest has length 1. */
--remain;
}
result[resultPtr] = 0x00;
return Encoding.ASCII.GetString(result);
}
/// <remarks>
/// Originally named `memcpy_eliminate_sequences`
/// </remarks>
private static int EliminateSequences(byte[] dst, int dstPtr, byte[] src, int srcPtr, int n)
{
int srcend = srcPtr + n;
if (n < 0)
throw new ArgumentOutOfRangeException(nameof(n));
if (srcPtr < srcend) dst[dstPtr++] = src[srcPtr++];
if (srcPtr < srcend) dst[dstPtr++] = src[srcPtr++];
if (srcPtr < srcend) dst[dstPtr++] = src[srcPtr++];
while (srcPtr < srcend)
{
if (src[srcPtr] == dst[dstPtr - 1]
&& src[srcPtr] == dst[dstPtr - 2]
&& src[srcPtr] == dst[dstPtr - 3])
{
++srcPtr;
--n;
}
else
{
dst[dstPtr++] = src[srcPtr++];
}
}
return n;
}
}
}

View File

@@ -1,3 +1,4 @@
#pragma warning disable IDE0051 // Remove unused private members
namespace SabreTools.Hashing.XxHash
{
// https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h
@@ -13,34 +14,6 @@ namespace SabreTools.Hashing.XxHash
#endregion
#region XXH32
public const uint XXH_PRIME32_1 = 0x9E3779B1;
public const uint XXH_PRIME32_2 = 0x85EBCA77;
public const uint XXH_PRIME32_3 = 0xC2B2AE3D;
public const uint XXH_PRIME32_4 = 0x27D4EB2F;
public const uint XXH_PRIME32_5 = 0x165667B1;
#endregion
#region XXH64
public const ulong XXH_PRIME64_1 = 0x9E3779B185EBCA87;
public const ulong XXH_PRIME64_2 = 0xC2B2AE3D27D4EB4F;
public const ulong XXH_PRIME64_3 = 0x165667B19E3779F9;
public const ulong XXH_PRIME64_4 = 0x85EBCA77C2B2AE63;
public const ulong XXH_PRIME64_5 = 0x27D4EB2F165667C5;
#endregion
#region XXH3
/// <summary>
@@ -95,4 +68,4 @@ namespace SabreTools.Hashing.XxHash
#endregion
}
}
}

View File

@@ -71,4 +71,4 @@ namespace SabreTools.Hashing.XxHash
/// </summary>
XXH_SVE = 6,
}
}
}

View File

@@ -1,4 +1,5 @@
using static SabreTools.Hashing.HashOperations;
using static SabreTools.Hashing.NonCryptographicHash.Constants;
using static SabreTools.Hashing.XxHash.Constants;
namespace SabreTools.Hashing.XxHash
@@ -62,7 +63,7 @@ namespace SabreTools.Hashing.XxHash
/// <summary>
/// Mixes all bits to finalize the hash.
///
///
/// The final mix ensures that all input bits have a chance to impact any bit in
/// the output digest, resulting in an unbiased distribution.
/// </summary>
@@ -171,7 +172,7 @@ namespace SabreTools.Hashing.XxHash
if (length > 0)
return Len1To3Out64(data, offset, length, secret, seed);
return XXH64Avalanche(seed ^ (ReadLE64(secret, 56) ^ ReadLE64(secret, 64)));
return XXH64Avalanche(seed ^ ReadLE64(secret, 56) ^ ReadLE64(secret, 64));
}
public static ulong Mix16B(byte[] data, int offset, byte[] secret, int secretOffset, ulong seed)
@@ -248,4 +249,4 @@ namespace SabreTools.Hashing.XxHash
#endregion
}
}
}

View File

@@ -3,7 +3,7 @@ namespace SabreTools.Hashing.XxHash
internal class XXH3_128Hash
{
public ulong Low { get; set; }
public ulong High { get; set; }
}
}
}

View File

@@ -1,14 +1,16 @@
#pragma warning disable CS0169 // Private field is never used
#pragma warning disable CS0414 // Private field is assigned but its value is never used
#pragma warning disable CS0649 // Field is never assigned to
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable IDE0052 // Remove unread private members
#pragma warning disable IDE0060 // Remove unused parameter
namespace SabreTools.Hashing.XxHash
{
// Handle unused private fields
#pragma warning disable CS0169
#pragma warning disable CS0414
#pragma warning disable CS0649
/// <summary>
/// Structure for XXH3 streaming API.
/// </summary>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XXH3_128State
{
/// <summary>
@@ -27,7 +29,7 @@ namespace SabreTools.Hashing.XxHash
private readonly byte[] _buffer = new byte[Constants.XXH3_INTERNALBUFFER_SIZE];
/// <summary>
/// The amount of memory in <see cref="_buffer"/>, <see cref="XXH32State._memsize"/>
/// The amount of memory in <see cref="_buffer"/>, <see cref="XXH32State._memsize"/>
/// </summary>
private uint _bufferedSize;
@@ -106,7 +108,7 @@ namespace SabreTools.Hashing.XxHash
{
// TODO: XXH3_128bits_reset_withSecret
}
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
@@ -128,4 +130,4 @@ namespace SabreTools.Hashing.XxHash
return ulong.MaxValue;
}
}
}
}

View File

@@ -1,14 +1,16 @@
#pragma warning disable CS0169 // Private field is never used
#pragma warning disable CS0414 // Private field is assigned but its value is never used
#pragma warning disable CS0649 // Field is never assigned to
#pragma warning disable IDE0044 // Add readonly modifier
#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable IDE0052 // Remove unread private members
#pragma warning disable IDE0060 // Remove unused parameter
namespace SabreTools.Hashing.XxHash
{
// Handle unused private fields
#pragma warning disable CS0169
#pragma warning disable CS0414
#pragma warning disable CS0649
/// <summary>
/// Structure for XXH3 streaming API.
/// </summary>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
/// <see href="https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h"/>
internal class XXH3_64State
{
/// <summary>
@@ -27,7 +29,7 @@ namespace SabreTools.Hashing.XxHash
private readonly byte[] _buffer = new byte[Constants.XXH3_INTERNALBUFFER_SIZE];
/// <summary>
/// The amount of memory in <see cref="_buffer"/>, <see cref="XXH32State._memsize"/>
/// The amount of memory in <see cref="_buffer"/>, <see cref="XXH32State._memsize"/>
/// </summary>
private uint _bufferedSize;
@@ -111,7 +113,7 @@ namespace SabreTools.Hashing.XxHash
{
// TODO: XXH3_64bits_reset_withSecret
}
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
@@ -133,4 +135,4 @@ namespace SabreTools.Hashing.XxHash
return ulong.MaxValue;
}
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
namespace SabreTools.Hashing.XxHash
{
public class XxHash32
{
/// <summary>
/// The 32-bit seed to alter the hash result predictably.
/// </summary>
private readonly uint _seed;
/// <summary>
/// Internal xxHash32 state
/// </summary>
private readonly XXH32State _state;
public XxHash32(uint seed = 0)
{
_seed = seed;
_state = new XXH32State();
_state.Reset(seed);
}
/// <summary>
/// Reset the internal hashing state
/// </summary>
public void Reset()
{
_state.Reset(_seed);
}
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="offset">Offset in the byte array to include</param>
/// <param name="length">Length of the data to hash</param>
public void TransformBlock(byte[] data, int offset, int length)
=> _state.Update(data, offset, length);
/// <summary>
/// Finalize the hash and return as a byte array
/// </summary>
public byte[] Finalize()
{
uint hash = _state.Digest();
return BitConverter.GetBytes(hash);
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
namespace SabreTools.Hashing.XxHash
{
public class XxHash64
{
/// <summary>
/// The 64-bit seed to alter the hash result predictably.
/// </summary>
private readonly uint _seed;
/// <summary>
/// Internal xxHash64 state
/// </summary>
private readonly XXH64State _state;
public XxHash64(uint seed = 0)
{
_seed = seed;
_state = new XXH64State();
_state.Reset(seed);
}
/// <summary>
/// Reset the internal hashing state
/// </summary>
public void Reset()
{
_state.Reset(_seed);
}
/// <summary>
/// Hash a block of data and append it to the existing hash
/// </summary>
/// <param name="data">Byte array representing the data</param>
/// <param name="offset">Offset in the byte array to include</param>
/// <param name="length">Length of the data to hash</param>
public void TransformBlock(byte[] data, int offset, int length)
=> _state.Update(data, offset, length);
/// <summary>
/// Finalize the hash and return as a byte array
/// </summary>
public byte[] Finalize()
{
ulong hash = _state.Digest();
return BitConverter.GetBytes(hash);
}
}
}

View File

@@ -221,6 +221,7 @@ namespace SabreTools.Hashing
{HashType.CRC32_BZIP2, [0x00, 0x00, 0x00, 0x00]},
{HashType.CRC32_CDROMEDC, [0x00, 0x00, 0x00, 0x00]},
{HashType.CRC32_CKSUM, [0xff, 0xff, 0xff, 0xff]},
{HashType.CRC32_DVDROMEDC, [0x00, 0x00, 0x00, 0x00]},
{HashType.CRC32_ISCSI, [0x00, 0x00, 0x00, 0x00]},
{HashType.CRC32_ISOHDLC, [0x00, 0x00, 0x00, 0x00]},
{HashType.CRC32_JAMCRC, [0xff, 0xff, 0xff, 0xff]},
@@ -250,6 +251,8 @@ namespace SabreTools.Hashing
{HashType.FNV1a_32, [0x81, 0x1c, 0x9d, 0xc5]},
{HashType.FNV1a_64, [0xcb, 0xf2, 0x9c, 0xe4, 0x84, 0x22, 0x23, 0x25]},
{HashType.MekaCrc, [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]},
{HashType.MD2, [0x83, 0x50, 0xe5, 0xa3, 0xe2, 0x4c, 0x15, 0x3d,
0xf2, 0x27, 0x5c, 0x9f, 0x80, 0x69, 0x27, 0x73]},
{HashType.MD4, [0x31, 0xd6, 0xcf, 0xe0, 0xd1, 0x6a, 0xe9, 0x31,
@@ -521,6 +524,7 @@ namespace SabreTools.Hashing
{HashType.CRC32_BZIP2, "00000000"},
{HashType.CRC32_CDROMEDC, "00000000"},
{HashType.CRC32_CKSUM, "ffffffff"},
{HashType.CRC32_DVDROMEDC, "00000000"},
{HashType.CRC32_ISCSI, "00000000"},
{HashType.CRC32_ISOHDLC, "00000000"},
{HashType.CRC32_JAMCRC, "ffffffff"},
@@ -550,6 +554,8 @@ namespace SabreTools.Hashing
{HashType.FNV1a_32, "811c9dc5"},
{HashType.FNV1a_64, "cbf29ce484222325"},
{HashType.MekaCrc, "0000000000000000"},
{HashType.MD2, "8350e5a3e24c153df2275c9f80692773"},
{HashType.MD4, "31d6cfe0d16ae931b73c59d7e0c089c0"},
{HashType.MD5, "d41d8cd98f00b204e9800998ecf8427e"},
@@ -620,4 +626,4 @@ namespace SabreTools.Hashing
return _strings[hashType];
}
}
}
}

View File

@@ -1,19 +1,32 @@
#! /bin/bash
#!/bin/bash
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - .NET 10.0 (or newer) SDK is installed and in PATH
# - zip is installed and in PATH
# - Git is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
USE_ALL=false
INCLUDE_DEBUG=false
NO_BUILD=false
while getopts "b" OPTION
do
NO_ARCHIVE=false
while getopts "udba" OPTION; do
case $OPTION in
u)
USE_ALL=true
;;
d)
INCLUDE_DEBUG=true
;;
b)
NO_BUILD=true
;;
a)
NO_ARCHIVE=true
;;
*)
echo "Invalid option provided"
exit 1
@@ -24,13 +37,115 @@ done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Set the current commit hash
COMMIT=$(git log --pretty=%H -1)
# Output the selected options
echo "Selected Options:"
echo " Use all frameworks (-u) $USE_ALL"
echo " Include debug builds (-d) $INCLUDE_DEBUG"
echo " No build (-b) $NO_BUILD"
echo " No archive (-a) $NO_ARCHIVE"
echo " "
# Create the build matrix arrays
FRAMEWORKS=("net10.0")
RUNTIMES=("win-x86" "win-x64" "win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Use expanded lists, if requested
if [ $USE_ALL = true ]; then
FRAMEWORKS=("net20" "net35" "net40" "net452" "net462" "net472" "net48" "netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
fi
# Create the filter arrays
SINGLE_FILE_CAPABLE=("net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_APPLE_FRAMEWORKS=("net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_CROSS_PLATFORM_FRAMEWORKS=("netcoreapp3.1" "net5.0" "net6.0" "net7.0" "net8.0" "net9.0" "net10.0")
VALID_CROSS_PLATFORM_RUNTIMES=("win-arm64" "linux-x64" "linux-arm64" "osx-x64" "osx-arm64")
# Only build if requested
if [ $NO_BUILD = false ]
then
if [ $NO_BUILD = false ]; then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Hashing/SabreTools.Hashing.csproj --output $BUILD_FOLDER
fi
# Build Hasher
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Build Hasher - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only .NET 5 and above can publish to a single file
if [[ $(echo ${SINGLE_FILE_CAPABLE[@]} | fgrep -w $FRAMEWORK) ]]; then
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
dotnet publish Hasher/Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
fi
dotnet publish Hasher/Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
else
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
dotnet publish Hasher/Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
fi
dotnet publish Hasher/Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
fi
done
done
fi
# Only create archives if requested
if [ $NO_ARCHIVE = false ]; then
# Create Hasher archives
for FRAMEWORK in "${FRAMEWORKS[@]}"; do
for RUNTIME in "${RUNTIMES[@]}"; do
# Output the current build
echo "===== Archive Hasher - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if [[ ! $(echo ${VALID_CROSS_PLATFORM_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [[ $(echo ${VALID_CROSS_PLATFORM_RUNTIMES[@]} | fgrep -w $RUNTIME) ]]; then
echo "Skipped due to invalid combination"
continue
fi
fi
# If we have Apple silicon but an unsupported framework
if [[ ! $(echo ${VALID_APPLE_FRAMEWORKS[@]} | fgrep -w $FRAMEWORK) ]]; then
if [ $RUNTIME = "osx-arm64" ]; then
echo "Skipped due to no Apple Silicon support"
continue
fi
fi
# Only include Debug if set
if [ $INCLUDE_DEBUG = true ]; then
cd $BUILD_FOLDER/Hasher/bin/Debug/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/Hasher_${FRAMEWORK}_${RUNTIME}_debug.zip .
fi
cd $BUILD_FOLDER/Hasher/bin/Release/${FRAMEWORK}/${RUNTIME}/publish/
zip -r $BUILD_FOLDER/Hasher_${FRAMEWORK}_${RUNTIME}_release.zip .
done
done
# Reset the directory
cd $BUILD_FOLDER
fi

View File

@@ -1,26 +1,134 @@
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
# - .NET 10.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("UseAll")]
[switch]$USE_ALL,
[Parameter(Mandatory = $false)]
[Alias("IncludeDebug")]
[switch]$INCLUDE_DEBUG,
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD
[switch]$NO_BUILD,
[Parameter(Mandatory = $false)]
[Alias("NoArchive")]
[switch]$NO_ARCHIVE
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Set the current commit hash
$COMMIT = git log --pretty=format:"%H" -1
# Output the selected options
Write-Host "Selected Options:"
Write-Host " Use all frameworks (-UseAll) $USE_ALL"
Write-Host " Include debug builds (-IncludeDebug) $INCLUDE_DEBUG"
Write-Host " No build (-NoBuild) $NO_BUILD"
Write-Host " No archive (-NoArchive) $NO_ARCHIVE"
Write-Host " "
# Create the build matrix arrays
$FRAMEWORKS = @('net10.0')
$RUNTIMES = @('win-x86', 'win-x64', 'win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Use expanded lists, if requested
if ($USE_ALL.IsPresent) {
$FRAMEWORKS = @('net20', 'net35', 'net40', 'net452', 'net462', 'net472', 'net48', 'netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
}
# Create the filter arrays
$SINGLE_FILE_CAPABLE = @('net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_APPLE_FRAMEWORKS = @('net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_CROSS_PLATFORM_FRAMEWORKS = @('netcoreapp3.1', 'net5.0', 'net6.0', 'net7.0', 'net8.0', 'net9.0', 'net10.0')
$VALID_CROSS_PLATFORM_RUNTIMES = @('win-arm64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64')
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
if (!$NO_BUILD.IsPresent) {
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Hashing\SabreTools.Hashing.csproj --output $BUILD_FOLDER
# Build Hasher
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Build Hasher - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only .NET 5 and above can publish to a single file
if ($SINGLE_FILE_CAPABLE -contains $FRAMEWORK) {
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
dotnet publish Hasher\Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true
}
dotnet publish Hasher\Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false
}
else {
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
dotnet publish Hasher\Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Debug --self-contained true --version-suffix $COMMIT
}
dotnet publish Hasher\Hasher.csproj -f $FRAMEWORK -r $RUNTIME -c Release --self-contained true --version-suffix $COMMIT -p:DebugType=None -p:DebugSymbols=false
}
}
}
}
# Only create archives if requested
if (!$NO_ARCHIVE.IsPresent) {
# Create Hasher archives
foreach ($FRAMEWORK in $FRAMEWORKS) {
foreach ($RUNTIME in $RUNTIMES) {
# Output the current build
Write-Host "===== Archive Hasher - $FRAMEWORK, $RUNTIME ====="
# If we have an invalid combination of framework and runtime
if ($VALID_CROSS_PLATFORM_FRAMEWORKS -notcontains $FRAMEWORK -and $VALID_CROSS_PLATFORM_RUNTIMES -contains $RUNTIME) {
Write-Host "Skipped due to invalid combination"
continue
}
# If we have Apple silicon but an unsupported framework
if ($VALID_APPLE_FRAMEWORKS -notcontains $FRAMEWORK -and $RUNTIME -eq 'osx-arm64') {
Write-Host "Skipped due to no Apple Silicon support"
continue
}
# Only include Debug if set
if ($INCLUDE_DEBUG.IsPresent) {
Set-Location -Path $BUILD_FOLDER\Hasher\bin\Debug\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\Hasher_${FRAMEWORK}_${RUNTIME}_debug.zip *
}
Set-Location -Path $BUILD_FOLDER\Hasher\bin\Release\${FRAMEWORK}\${RUNTIME}\publish\
7z a -tzip $BUILD_FOLDER\Hasher_${FRAMEWORK}_${RUNTIME}_release.zip *
}
}
# Reset the directory
Set-Location -Path $PSScriptRoot
}