fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
157 lines
3.9 KiB
C#
157 lines
3.9 KiB
C#
using System;
|
|
|
|
namespace Invensys.Compression;
|
|
|
|
internal class AAFastEncoder
|
|
{
|
|
private AAFastEncoderWindow inputWindow;
|
|
|
|
private AAMatch currentMatch;
|
|
|
|
private double lastCompressionRatio;
|
|
|
|
internal int BytesInHistory => inputWindow.BytesAvailable;
|
|
|
|
internal AADeflateInput UnprocessedInput => inputWindow.UnprocessedInput;
|
|
|
|
internal double LastCompressionRatio => lastCompressionRatio;
|
|
|
|
public AAFastEncoder()
|
|
{
|
|
inputWindow = new AAFastEncoderWindow();
|
|
currentMatch = new AAMatch();
|
|
}
|
|
|
|
internal void FlushInput()
|
|
{
|
|
inputWindow.FlushWindow();
|
|
}
|
|
|
|
internal void GetBlock(AADeflateInput input, AAOutputBuffer output, int maxBytesToCopy)
|
|
{
|
|
WriteDeflatePreamble(output);
|
|
GetCompressedOutput(input, output, maxBytesToCopy);
|
|
WriteEndOfBlock(output);
|
|
}
|
|
|
|
internal void GetCompressedData(AADeflateInput input, AAOutputBuffer output)
|
|
{
|
|
GetCompressedOutput(input, output, -1);
|
|
}
|
|
|
|
internal void GetBlockHeader(AAOutputBuffer output)
|
|
{
|
|
WriteDeflatePreamble(output);
|
|
}
|
|
|
|
internal void GetBlockFooter(AAOutputBuffer output)
|
|
{
|
|
WriteEndOfBlock(output);
|
|
}
|
|
|
|
private void GetCompressedOutput(AADeflateInput input, AAOutputBuffer output, int maxBytesToCopy)
|
|
{
|
|
int bytesWritten = output.BytesWritten;
|
|
int num = 0;
|
|
int num2 = BytesInHistory + input.Count;
|
|
do
|
|
{
|
|
int num3 = ((input.Count < inputWindow.FreeWindowSpace) ? input.Count : inputWindow.FreeWindowSpace);
|
|
if (maxBytesToCopy >= 1)
|
|
{
|
|
num3 = Math.Min(num3, maxBytesToCopy - num);
|
|
}
|
|
if (num3 > 0)
|
|
{
|
|
inputWindow.CopyBytes(input.Buffer, input.StartIndex, num3);
|
|
input.ConsumeBytes(num3);
|
|
num += num3;
|
|
}
|
|
GetCompressedOutput(output);
|
|
}
|
|
while (SafeToWriteTo(output) && InputAvailable(input) && (maxBytesToCopy < 1 || num < maxBytesToCopy));
|
|
int num4 = output.BytesWritten - bytesWritten;
|
|
int num5 = BytesInHistory + input.Count;
|
|
int num6 = num2 - num5;
|
|
if (num4 != 0)
|
|
{
|
|
lastCompressionRatio = (double)num4 / (double)num6;
|
|
}
|
|
}
|
|
|
|
private void GetCompressedOutput(AAOutputBuffer output)
|
|
{
|
|
while (inputWindow.BytesAvailable > 0 && SafeToWriteTo(output))
|
|
{
|
|
inputWindow.GetNextSymbolOrMatch(currentMatch);
|
|
if (currentMatch.State == AAMatchState.HasSymbol)
|
|
{
|
|
WriteChar(currentMatch.Symbol, output);
|
|
continue;
|
|
}
|
|
if (currentMatch.State == AAMatchState.HasMatch)
|
|
{
|
|
WriteMatch(currentMatch.Length, currentMatch.Position, output);
|
|
continue;
|
|
}
|
|
WriteChar(currentMatch.Symbol, output);
|
|
WriteMatch(currentMatch.Length, currentMatch.Position, output);
|
|
}
|
|
}
|
|
|
|
private bool InputAvailable(AADeflateInput input)
|
|
{
|
|
if (input.Count <= 0)
|
|
{
|
|
return BytesInHistory > 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private bool SafeToWriteTo(AAOutputBuffer output)
|
|
{
|
|
return output.FreeBytes > 16;
|
|
}
|
|
|
|
private void WriteEndOfBlock(AAOutputBuffer output)
|
|
{
|
|
uint num = AAFastEncoderStatics.FastEncoderLiteralCodeInfo[256];
|
|
int n = (int)(num & 0x1F);
|
|
output.WriteBits(n, num >> 5);
|
|
}
|
|
|
|
internal static void WriteMatch(int matchLen, int matchPos, AAOutputBuffer output)
|
|
{
|
|
uint num = AAFastEncoderStatics.FastEncoderLiteralCodeInfo[254 + matchLen];
|
|
int num2 = (int)(num & 0x1F);
|
|
if (num2 <= 16)
|
|
{
|
|
output.WriteBits(num2, num >> 5);
|
|
}
|
|
else
|
|
{
|
|
output.WriteBits(16, (num >> 5) & 0xFFFF);
|
|
output.WriteBits(num2 - 16, num >> 21);
|
|
}
|
|
num = AAFastEncoderStatics.FastEncoderDistanceCodeInfo[AAFastEncoderStatics.GetSlot(matchPos)];
|
|
output.WriteBits((int)(num & 0xF), num >> 8);
|
|
int num3 = (int)((num >> 4) & 0xF);
|
|
if (num3 != 0)
|
|
{
|
|
output.WriteBits(num3, (uint)matchPos & AAFastEncoderStatics.BitMask[num3]);
|
|
}
|
|
}
|
|
|
|
internal static void WriteChar(byte b, AAOutputBuffer output)
|
|
{
|
|
uint num = AAFastEncoderStatics.FastEncoderLiteralCodeInfo[b];
|
|
output.WriteBits((int)(num & 0x1F), num >> 5);
|
|
}
|
|
|
|
internal static void WriteDeflatePreamble(AAOutputBuffer output)
|
|
{
|
|
output.WriteBytes(AAFastEncoderStatics.FastEncoderTreeStructureData, 0, AAFastEncoderStatics.FastEncoderTreeStructureData.Length);
|
|
output.WriteBits(9, 34u);
|
|
}
|
|
}
|