Created
January 17, 2026 19:10
-
-
Save complacentsee/b1dd92fc8c5d81cfa42463844f291462 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package merlz | |
| import ( | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| ) | |
| var ( | |
| ErrInputTooShort = errors.New("input too short (<4)") | |
| ErrOutputSmall = errors.New("output buffer too small") | |
| ErrBadBackref = errors.New("bad backreference") | |
| ErrTruncated = errors.New("truncated input") | |
| ) | |
| type Status uint32 | |
| const ( | |
| StatusOKFinal Status = 4 | |
| StatusRawNoDecompress Status = 3 | |
| StatusDecompressFail Status = 2 | |
| ) | |
| func (s Status) String() string { | |
| switch s { | |
| case StatusOKFinal: | |
| return "OKFinal(4)" | |
| case StatusRawNoDecompress: | |
| return "RawNoDecompress(3)" | |
| case StatusDecompressFail: | |
| return "DecompressFail(2)" | |
| default: | |
| return fmt.Sprintf("Status(%d)", uint32(s)) | |
| } | |
| } | |
| func Decompress(src []byte) ([]byte, error) { | |
| // --- DEBUG: header/preview only --- | |
| fmt.Printf("[Decompress] srcLen=%d firstBytes=%s\n", len(src), HexPrefix(src, 16)) | |
| if len(src) >= 2 { | |
| ctrl0 := uint16(src[0]) | (uint16(src[1]) << 8) | |
| fmt.Printf("[Decompress] firstCtrl=0x%04X (bytes %02X %02X)\n", ctrl0, src[0], src[1]) | |
| } | |
| // --------------------------------- | |
| // Same growth heuristic as your working code. | |
| out := make([]byte, 0, len(src)*2) | |
| i := 0 | |
| for i < len(src) { | |
| // Need 2 bytes for ctrl | |
| if i+1 >= len(src) { | |
| break | |
| } | |
| ctrl := uint16(src[i]) | (uint16(src[i+1]) << 8) | |
| i += 2 | |
| for t := 0; t < 16; t++ { | |
| if i >= len(src) { | |
| break | |
| } | |
| if (ctrl & 1) == 0 { | |
| // literal | |
| out = append(out, src[i]) | |
| i++ | |
| } else { | |
| // backref: 2 bytes | |
| if i+1 >= len(src) { | |
| return nil, fmt.Errorf("merlz: truncated backref at %d", i) | |
| } | |
| b0 := src[i] | |
| b1 := src[i+1] | |
| i += 2 | |
| length := int(b0&0x0F) + 1 | |
| offset := int((uint16(b0&0xF0) << 4) | uint16(b1)) | |
| if offset == 0 { | |
| for k := 0; k < length; k++ { | |
| out = append(out, 0x00) | |
| } | |
| } else { | |
| start := len(out) - offset | |
| if start < 0 { | |
| return nil, fmt.Errorf("merlz: invalid offset=%d outLen=%d", offset, len(out)) | |
| } | |
| for k := 0; k < length; k++ { | |
| out = append(out, out[start+k]) | |
| } | |
| } | |
| } | |
| ctrl >>= 1 | |
| } | |
| } | |
| // --- DEBUG: summary only --- | |
| fmt.Printf("[Decompress] outLen=%d\n", len(out)) | |
| // --------------------------- | |
| return out, nil | |
| } | |
| // hexPrefix returns a compact hex string of up to n bytes (e.g. "FF FE 3C 00 ..."). | |
| func HexPrefix(b []byte, n int) string { | |
| if len(b) == 0 { | |
| return "<empty>" | |
| } | |
| if n > len(b) { | |
| n = len(b) | |
| } | |
| const hexd = "0123456789ABCDEF" | |
| buf := make([]byte, 0, n*3+4) | |
| for i := 0; i < n; i++ { | |
| if i > 0 { | |
| buf = append(buf, ' ') | |
| } | |
| v := b[i] | |
| buf = append(buf, hexd[v>>4], hexd[v&0x0F]) | |
| } | |
| if n < len(b) { | |
| buf = append(buf, ' ', '.', '.', '.') | |
| } | |
| return string(buf) | |
| } | |
| func Compress(data []byte) []byte { | |
| if len(data) == 0 { | |
| return nil | |
| } | |
| var out bytes.Buffer | |
| i := 0 | |
| for i < len(data) { | |
| // Reserve space for flags | |
| flagsPos := out.Len() | |
| out.WriteByte(0) | |
| out.WriteByte(0) | |
| var flags uint16 | |
| bit := 0 | |
| for bit < 16 && i < len(data) { | |
| bestLen := 0 | |
| bestOff := 0 | |
| // Search backward up to 4095 bytes | |
| start := i - 4095 | |
| if start < 0 { | |
| start = 0 | |
| } | |
| // Greedy longest match up to 16 bytes | |
| for j := i - 1; j >= start; j-- { | |
| off := i - j | |
| if off <= 0 || off > 4095 { | |
| continue | |
| } | |
| l := 0 | |
| for l < 16 && i+l < len(data) && data[j+l] == data[i+l] { | |
| l++ | |
| } | |
| if l > bestLen { | |
| bestLen = l | |
| bestOff = off | |
| if bestLen == 16 { | |
| break | |
| } | |
| } | |
| } | |
| // Emit backref only if it helps | |
| if bestLen >= 2 { | |
| flags |= (1 << bit) | |
| // Encode offset + length into 2 bytes: | |
| // offset = ((b0&0xF0)<<4) | b1 | |
| // length = (b0&0x0F)+1 | |
| hi4 := byte((bestOff >> 8) & 0x0F) // upper 4 bits of offset | |
| lo8 := byte(bestOff & 0xFF) | |
| b0 := (hi4 << 4) | byte(bestLen-1) | |
| b1 := lo8 | |
| out.WriteByte(b0) | |
| out.WriteByte(b1) | |
| i += bestLen | |
| } else { | |
| // literal | |
| out.WriteByte(data[i]) | |
| i++ | |
| } | |
| bit++ | |
| } | |
| // Patch flags (little-endian) | |
| buf := out.Bytes() | |
| buf[flagsPos] = byte(flags & 0xFF) | |
| buf[flagsPos+1] = byte((flags >> 8) & 0xFF) | |
| } | |
| return out.Bytes() | |
| } | |
| func CompressBlock(data []byte, unknowncontrolbyte byte) []byte { | |
| n := len(data) | |
| // raw empty | |
| if n == 0 { | |
| fmt.Printf("[CompressBlock] n=0 -> raw empty block\n") | |
| return []byte{1, 0, 0, 0} | |
| } | |
| core := Compress(data) | |
| // Decide raw vs compressed | |
| raw := len(core) >= n | |
| var out []byte | |
| if raw { | |
| out = make([]byte, 4+n) | |
| out[0] = 1 | |
| copy(out[4:], data) | |
| } else { | |
| out = make([]byte, 4+len(core)) | |
| out[0] = 0 | |
| copy(out[4:], core) | |
| } | |
| //Specify compression type, just hard code since we only support the one. | |
| out[1] = 0x00 | |
| out[2] = unknowncontrolbyte | |
| out[3] = 0x00 | |
| // ---- DEBUG OUTPUT ---- | |
| encodedLen := int(out[1]) | int(out[2])<<8 | int(out[3])<<16 | |
| fmt.Printf( | |
| "[CompressBlock] inputLen=%d encodedLen=0x%06X payloadLen=%d header=%02X %02X %02X %02X\n", | |
| n, | |
| encodedLen, | |
| len(out), | |
| out[0], out[1], out[2], out[3], | |
| ) | |
| // Optional: show first few bytes of token stream too | |
| if !raw && len(out) >= 10 { | |
| fmt.Printf( | |
| "[CompressBlock] first ctrl=%02X %02X next=%02X %02X %02X %02X\n", | |
| out[4], out[5], out[6], out[7], out[8], out[9], | |
| ) | |
| } | |
| return out | |
| } | |
| func DecompressBlock(payload []byte) ([]byte, error) { | |
| if len(payload) < 4 { | |
| return nil, ErrInputTooShort | |
| } | |
| if payload[0] == 1 { | |
| return append([]byte(nil), payload[4:]...), nil | |
| } | |
| return Decompress(payload[4:]) | |
| } | |
| func CompressBlockTesting(data []byte) []byte { | |
| n := len(data) | |
| // empty raw block | |
| if n == 0 { | |
| return []byte{1, 0, 0, 0} | |
| } | |
| core := Compress(data) | |
| // match Rockwell behavior: if compression isn't smaller, store raw | |
| raw := len(core) >= n | |
| var out []byte | |
| if raw { | |
| out = make([]byte, 4+n) | |
| out[0] = 1 | |
| copy(out[4:], data) | |
| } else { | |
| out = make([]byte, 4+len(core)) | |
| out[0] = 0 | |
| copy(out[4:], core) | |
| } | |
| // bytes 1..3 = uncompressed length (24-bit LE) | |
| out[1] = byte(n) | |
| out[2] = byte(n >> 8) | |
| out[3] = byte(n >> 16) | |
| return out | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment