MoonHashKit

Content-defined chunking, rolling fingerprints, and deduplication for MoonBit.

hash
rolling
content-defined-chunking
dedupe
sync
moon add cn-ybm/MoonHashKit@0.3.0
Download zip
Author
Version
0.3.0
License
Apache-2.0
Last updated
last month
Downloads
13
README

#MoonHashKit

MoonHashKit 是面向 MoonBit 的内容定义分块、滚动指纹与轻量去重基础库。它不与社区 底层 hash 函数库竞争,而是解决增量同步、备份、缓存和重复内容发现中的工程问题。

本项目只提供非密码学指纹,不用于签名、口令存储或安全认证。

#核心价值

  • 真正 O(n) 滚动哈希:窗口移动时移出旧字节、移入新字节,不再重复复制窗口。
  • 内容定义分块(CDC):边界由有限窗口内容决定,局部编辑后可重新同步。
  • 溢出安全:使用 Int64 中间算术,避免 32 位乘法先溢出再取模。
  • 分块去重:固定分块、重复组、候选位置和内容摘要。
  • 流式 CDCCdcStream 支持把网络包或文件块分批喂入,边界结果与一次性 处理保持一致,不必保留完整输入。
  • 双指纹与验证匹配:双多项式指纹用于低成本候选筛选;同步计划会再逐字节 验证,避免把非密码学哈希碰撞误判为可复用内容。
  • 清单与同步计划:固定分块和 CDC 都能生成确定性清单,并计算目标端缺失的 固定分块,便于上层备份、缓存或传输系统接入。
  • 后端中立:核心算法不依赖文件系统、浏览器、网络或平台 FFI。

#内容定义分块

///|
test {
let bytes = ascii_bytes("alpha-beta-gamma-alpha-beta-gamma-alpha-beta-gamma")
let config = CdcConfig::new(
8, // 最小分块
16, // 目标平均分块
32, // 最大分块
4, // 滚动窗口
)
let chunks = content_defined_chunks(bytes, config)
let summary = summarize_cdc(chunks)

assert_eq(summary.bytes, bytes.length())
assert_true(summary.chunks > 0)
}

与固定大小分块相比,CDC 在文件前部插入或删除少量内容后,后续边界能够重新对齐, 从而复用更多未变化分块。

#滚动窗口指纹

///|
test {
let hashes = window_hashes([1, 2, 3, 4, 5], 3, base=31, modulo=1009)
assert_eq(hashes.length(), 3)
}

每个滚动结果均与对应窗口独立计算的多项式哈希一致。

#流式分块与缺失块计划

///|
let config = CdcConfig::new(8, 16, 32, 4)

///|
let stream = CdcStream::new(config)

///|
let completed = stream.push(ascii_bytes("first network packet"))

///|
let final_chunks = stream.finish()

///|
let missing = missing_fixed_chunks(
ascii_bytes("alpha-beta-gamma"),
ascii_bytes("alpha-beta"),
4,
)

CdcStreampush 只返回已经确定边界的块;finish 返回最后的残留块。 missing_fixed_chunks 先用索引筛选候选,再做逐字节验证;它返回传输计划, 不会复制或持久化数据。

#运行与验收

moon check --target all moon test --target wasm moon run cmd/main moon run cmd/bench

CI 采用当前 MoonBit 工具链可执行的等价验收门禁:moon fmt --check moon check --deny-warn --target allmoon info.mbti 无差异,以及 moon test --deny-warn --target all。当前 moon fmtmoon info 不接受 --deny-warn 参数,因此不会在 CI 中加入必然失败的伪检查。

#仓库

#
CdcConfig

pub(all) struct CdcConfig {
min_size : Int
average_size : Int
max_size : Int
window_size : Int
} derive(Eq,
Debug
)

Parameters for finite-window content-defined chunking.

#
CdcConfig::new

fn CdcConfig::new(min_size : Int, average_size : Int, max_size : Int, window_size : Int) -> CdcConfig

#
CdcStream

pub(all) struct CdcStream {
config : CdcConfig
pending : Array[Int]
ring : Array[Int]
ring_start : Int
rolling_value : Int
next_start : Int
highest_power : Int
} derive(
Debug
)

Stateful finite-window CDC processor for streaming or batched input.

The rolling window remains continuous across emitted chunks, matching the boundary semantics of content_defined_chunks while avoiding the need to retain the complete input in memory.

#
CdcStream::finish

fn CdcStream::finish(self : CdcStream) -> Array[ChunkFingerprint]

Finishes a stream and returns its final partial chunk, if present.

#
CdcStream::new

fn CdcStream::new(config : CdcConfig) -> CdcStream

#
CdcStream::pending_bytes

fn CdcStream::pending_bytes(self : CdcStream) -> Int

#
CdcStream::push

fn CdcStream::push(self : CdcStream, bytes : Array[Int]) -> Array[ChunkFingerprint]

Feeds any number of bytes and returns only fully decided chunks.

#
CdcSummary

pub(all) struct CdcSummary {
bytes : Int
chunks : Int
min_chunk : Int
max_chunk : Int
average_chunk : Double
} derive(
Debug
)

#
CdcSummary::to_json

fn CdcSummary::to_json(self : CdcSummary) -> String

#
Checksum

pub(all) struct Checksum {
algorithm : String
value : Int
bytes : Int
} derive(Eq,
Debug
)

#
Checksum::to_json

fn Checksum::to_json(self : Checksum) -> String

#
ChunkFingerprint

pub(all) struct ChunkFingerprint {
start : Int
length : Int
hash : Int
} derive(Eq,
Debug
)

#
ChunkFingerprint::to_json

fn ChunkFingerprint::to_json(self : ChunkFingerprint) -> String

#
ChunkManifest

pub(all) struct ChunkManifest {
bytes : Int
entries : Array[ManifestEntry]
} derive(
Debug
)

A deterministic chunk manifest suitable for backup or synchronization metadata. It stores offsets and fingerprints, not source bytes.

#
ChunkManifest::entry_count

fn ChunkManifest::entry_count(self : ChunkManifest) -> Int

#
ChunkManifest::to_json

fn ChunkManifest::to_json(self : ChunkManifest) -> String

#
CompareResult

pub(all) struct CompareResult {
same_length : Bool
same_checksum : Bool
same_hash : Bool
} derive(Eq,
Debug
)

#
CompareResult::is_probable_match

fn CompareResult::is_probable_match(self : CompareResult) -> Bool

#
ContentSummary

pub(all) struct ContentSummary {
bytes : Int
checksum8 : Int
hash32 : Int
chunks : Int
duplicate_pairs : Int
} derive(Eq,
Debug
)

#
ContentSummary::to_json

fn ContentSummary::to_json(self : ContentSummary) -> String

#
DualFingerprint

pub(all) struct DualFingerprint {
primary : Int
secondary : Int
bytes : Int
} derive(Eq,
Debug
)

Two independent polynomial fingerprints for a byte sequence.

This reduces accidental candidate collisions during transport planning, but is still not a cryptographic digest. Use byte_ranges_equal before treating untrusted content as identical.

#
DualFingerprint::to_json

fn DualFingerprint::to_json(self : DualFingerprint) -> String

#
DuplicateGroup

pub(all) struct DuplicateGroup {
hash : Int
length : Int
occurrences : Int
first_start : Int
} derive(Eq,
Debug
)

#
DuplicateGroup::to_json

fn DuplicateGroup::to_json(self : DuplicateGroup) -> String

#
FingerprintBucket

pub(all) struct FingerprintBucket {
hash : Int
length : Int
starts : Array[Int]
} derive(
Debug
)

#
FingerprintBucket::add_start

fn FingerprintBucket::add_start(self : FingerprintBucket, start : Int) -> Unit

#
FingerprintBucket::new

#
FingerprintBucket::occurrences

fn FingerprintBucket::occurrences(self : FingerprintBucket) -> Int

#
FingerprintBucket::to_json

fn FingerprintBucket::to_json(self : FingerprintBucket) -> String

#
Hash32

pub(all) struct Hash32 {
algorithm : String
value : Int
bytes : Int
} derive(Eq,
Debug
)

#
Hash32::to_json

fn Hash32::to_json(self : Hash32) -> String

#
HashIndex

pub(all) struct HashIndex {
chunk_size : Int
buckets : Array[FingerprintBucket]
} derive(
Debug
)

#
HashIndex::add_chunk

fn HashIndex::add_chunk(self : HashIndex, chunk : ChunkFingerprint) -> Unit

#
HashIndex::bucket_count

fn HashIndex::bucket_count(self : HashIndex) -> Int

#
HashIndex::candidate_starts

fn HashIndex::candidate_starts(self : HashIndex, hash : Int, length : Int) -> Array[Int]

#
HashIndex::duplicate_group_count

fn HashIndex::duplicate_group_count(self : HashIndex) -> Int

#
HashIndex::duplicate_groups

fn HashIndex::duplicate_groups(self : HashIndex) -> Array[DuplicateGroup]

#
HashIndex::new

fn HashIndex::new(chunk_size : Int) -> HashIndex

#
HashIndex::to_json

fn HashIndex::to_json(self : HashIndex) -> String

#
HashIndex::total_chunks

fn HashIndex::total_chunks(self : HashIndex) -> Int

#
ManifestEntry

pub(all) struct ManifestEntry {
start : Int
length : Int
fingerprint : DualFingerprint
} derive(
Debug
)

A serializable entry in a fixed-size or CDC chunk manifest.

#
VerifiedChunkMatch

pub(all) struct VerifiedChunkMatch {
left_start : Int
right_start : Int
length : Int
} derive(Eq,
Debug
)

A content match that has been verified byte-for-byte after fingerprint lookup. Hash equality alone is deliberately not treated as proof of equality by this type.

#
VerifiedChunkMatch::to_json

fn VerifiedChunkMatch::to_json(self : VerifiedChunkMatch) -> String

#
ascii_bytes

fn ascii_bytes(text : String) -> Array[Int]

#
ascii_code

fn ascii_code(ch : Char) -> Int

#
ascii_window_hashes

fn ascii_window_hashes(text : String, window_size : Int) -> Array[Hash32]

#
build_hash_index

fn build_hash_index(bytes : Array[Int], chunk_size : Int) -> HashIndex

#
byte_ranges_equal

fn byte_ranges_equal(left : Array[Int], left_start : Int, right : Array[Int], right_start : Int, length : Int) -> Bool

Compares two byte ranges without allocating slice arrays.

Inputs are normalized in the same way as the polynomial fingerprint APIs, so a caller cannot accidentally accept bytes that hash differently from their verification representation.

#
cdc_chunk_manifest

fn cdc_chunk_manifest(bytes : Array[Int], config : CdcConfig) -> ChunkManifest

Builds a manifest for content-defined chunks.

#
checksum8

fn checksum8(bytes : Array[Int]) -> Checksum

#
checksum8_value

fn checksum8_value(bytes : Array[Int]) -> Int

#
chunk_fingerprints

fn chunk_fingerprints(bytes : Array[Int], chunk_size : Int) -> Array[ChunkFingerprint]

#
compare_content

fn compare_content(left : Array[Int], right : Array[Int]) -> CompareResult

#
content_defined_chunks

fn content_defined_chunks(bytes : Array[Int], config : CdcConfig) -> Array[ChunkFingerprint]

Splits data at content-derived boundaries.

Boundary decisions use a finite rolling window, allowing chunk boundaries to resynchronize after local insertions or deletions.

#
dual_fingerprint

fn dual_fingerprint(bytes : Array[Int]) -> DualFingerprint

#
duplicate_hash_count

fn duplicate_hash_count(chunks : Array[ChunkFingerprint]) -> Int

#
fixed_chunk_manifest

fn fixed_chunk_manifest(bytes : Array[Int], chunk_size : Int) -> ChunkManifest

Builds a manifest for fixed-size chunking.

#
has_duplicate_hashes

fn has_duplicate_hashes(chunks : Array[ChunkFingerprint]) -> Bool

#
hash_ascii

fn hash_ascii(text : String) -> Hash32

#
missing_fixed_chunks

fn missing_fixed_chunks(source : Array[Int], destination : Array[Int], chunk_size : Int) -> Array[ChunkFingerprint]

Returns source chunks not present in a destination under byte-for-byte verification. This is a transport plan, not a copy operation.

#
normalize_byte

fn normalize_byte(value : Int) -> Int

#
polynomial_hash

fn polynomial_hash(bytes : Array[Int], base? : Int, modulo? : Int) -> Hash32

#
polynomial_hash_value

fn polynomial_hash_value(bytes : Array[Int], base? : Int, modulo? : Int) -> Int

#
summarize_cdc

fn summarize_cdc(chunks : Array[ChunkFingerprint]) -> CdcSummary

#
summarize_content

fn summarize_content(bytes : Array[Int], chunk_size? : Int) -> ContentSummary

#
verified_chunk_matches

fn verified_chunk_matches(left : Array[Int], left_chunks : Array[ChunkFingerprint], right : Array[Int], right_chunks : Array[ChunkFingerprint]) -> Array[VerifiedChunkMatch]

Verifies matches between arbitrary chunk layouts, including CDC manifests. It is intentionally straightforward so callers can apply their own index strategy when processing very large manifests.

#
verified_fixed_chunk_matches

fn verified_fixed_chunk_matches(left : Array[Int], right : Array[Int], chunk_size : Int) -> Array[VerifiedChunkMatch]

Finds equal fixed-size chunks in two contents.

The right-side index narrows the search by fingerprint and length; every candidate is then checked with byte_ranges_equal, avoiding false-positive deduplication when a non-cryptographic fingerprint collides.

#
window_hashes

fn window_hashes(bytes : Array[Int], window_size : Int, base? : Int, modulo? : Int) -> Array[Hash32]