shadow_compress_test.go raw

   1  package crypto
   2  
   3  import (
   4  	"bytes"
   5  	"testing"
   6  
   7  	"git.mleku.dev/mleku/dendrite/pkg/epoch"
   8  	"git.mleku.dev/mleku/dendrite/pkg/state"
   9  )
  10  
  11  // TestShadowCompressDecompressRoundTrip tests full compress/decompress
  12  // cycle with and without auxiliary data.
  13  func TestShadowCompressDecompressRoundTrip(t *testing.T) {
  14  	ep := epoch.Colony
  15  
  16  	testCases := []struct {
  17  		name string
  18  		data []byte
  19  		aux  []byte
  20  	}{
  21  		{"empty", nil, nil},
  22  		{"single byte", []byte{42}, nil},
  23  		{"short text", []byte("hello shadow compression"), nil},
  24  		{"with auxiliary", []byte("primary data stream"), []byte("aux")},
  25  		{"aux 1 byte", []byte("data for one byte aux"), []byte{0xFF}},
  26  		{"aux 2 bytes", []byte("data for two byte aux"), []byte{0x42, 0x37}},
  27  		{"aux 7 bytes", []byte("seven bytes of auxiliary payload here"), []byte("7bytes!")},
  28  		{"long text", bytes.Repeat([]byte("the shadow between binary and decimal "), 20), nil},
  29  		{"long with aux", bytes.Repeat([]byte("compress me "), 50), bytes.Repeat([]byte{0xAB}, 10)},
  30  	}
  31  
  32  	for _, tc := range testCases {
  33  		t.Run(tc.name, func(t *testing.T) {
  34  			sc, err := ShadowCompress(tc.data, tc.aux, ep)
  35  			if err != nil {
  36  				t.Fatalf("ShadowCompress: %v", err)
  37  			}
  38  
  39  			if len(tc.data) == 0 {
  40  				if sc.OrigLen != 0 {
  41  					t.Errorf("empty data: OrigLen = %d, want 0", sc.OrigLen)
  42  				}
  43  				return
  44  			}
  45  
  46  			if sc.OrigLen != len(tc.data) {
  47  				t.Errorf("OrigLen = %d, want %d", sc.OrigLen, len(tc.data))
  48  			}
  49  
  50  			data, aux, err := ShadowDecompress(sc, ep)
  51  			if err != nil {
  52  				t.Fatalf("ShadowDecompress: %v", err)
  53  			}
  54  
  55  			if !bytes.Equal(data, tc.data) {
  56  				t.Errorf("data mismatch:\n  got:  %x\n  want: %x", data, tc.data)
  57  			}
  58  
  59  			if tc.aux != nil {
  60  				if !bytes.Equal(aux, tc.aux) {
  61  					t.Errorf("auxiliary mismatch:\n  got:  %x\n  want: %x", aux, tc.aux)
  62  				}
  63  			}
  64  		})
  65  	}
  66  }
  67  
  68  // TestShadowCompressWireRoundTrip tests marshal/unmarshal cycle.
  69  func TestShadowCompressWireRoundTrip(t *testing.T) {
  70  	ep := epoch.Colony
  71  	data := []byte("wire format round-trip test data")
  72  
  73  	sc, err := ShadowCompress(data, []byte("aux"), ep)
  74  	if err != nil {
  75  		t.Fatalf("ShadowCompress: %v", err)
  76  	}
  77  
  78  	wire, err := MarshalShadowCompressed(sc)
  79  	if err != nil {
  80  		t.Fatalf("Marshal: %v", err)
  81  	}
  82  
  83  	sc2, err := UnmarshalShadowCompressed(wire)
  84  	if err != nil {
  85  		t.Fatalf("Unmarshal: %v", err)
  86  	}
  87  
  88  	// Verify all fields match.
  89  	if sc2.OrigLen != sc.OrigLen {
  90  		t.Errorf("OrigLen: %d vs %d", sc2.OrigLen, sc.OrigLen)
  91  	}
  92  	if sc2.TokenCount != sc.TokenCount {
  93  		t.Errorf("TokenCount: %d vs %d", sc2.TokenCount, sc.TokenCount)
  94  	}
  95  	if sc2.EpochDec != sc.EpochDec || sc2.EpochBin != sc.EpochBin {
  96  		t.Errorf("Epoch: %d/%d vs %d/%d", sc2.EpochDec, sc2.EpochBin, sc.EpochDec, sc.EpochBin)
  97  	}
  98  	if sc2.ShadowPayloadLen != sc.ShadowPayloadLen {
  99  		t.Errorf("ShadowPayloadLen: %d vs %d", sc2.ShadowPayloadLen, sc.ShadowPayloadLen)
 100  	}
 101  	if !bytes.Equal(sc2.Primary, sc.Primary) {
 102  		t.Errorf("Primary stream mismatch")
 103  	}
 104  	if !bytes.Equal(sc2.Shadow, sc.Shadow) {
 105  		t.Errorf("Shadow stream mismatch")
 106  	}
 107  	if sc2.FreqTable != sc.FreqTable {
 108  		t.Errorf("FreqTable mismatch")
 109  	}
 110  	if sc2.ContentHash != sc.ContentHash {
 111  		t.Errorf("ContentHash mismatch")
 112  	}
 113  
 114  	// Verify full round-trip: unmarshal then decompress.
 115  	recovered, aux, err := ShadowDecompress(sc2, ep)
 116  	if err != nil {
 117  		t.Fatalf("ShadowDecompress after unmarshal: %v", err)
 118  	}
 119  	if !bytes.Equal(recovered, data) {
 120  		t.Errorf("data after wire round-trip: %x != %x", recovered, data)
 121  	}
 122  	if !bytes.Equal(aux, []byte("aux")) {
 123  		t.Errorf("aux after wire round-trip: %x", aux)
 124  	}
 125  }
 126  
 127  // TestHuffmanTreeDeterminism verifies that building the Huffman tree
 128  // from the same frequency table always produces identical codebooks.
 129  func TestHuffmanTreeDeterminism(t *testing.T) {
 130  	var freq [64]uint32
 131  	freq[0] = 100
 132  	freq[1] = 50
 133  	freq[2] = 25
 134  	freq[10] = 10
 135  	freq[63] = 5
 136  
 137  	total := int64(190)
 138  	var allCodes []map[uint8]int // symbol → code length
 139  
 140  	for range 100 {
 141  		tree := buildHuffmanTree(freq, total)
 142  		codes := make(map[uint8]int)
 143  		codeMap := make(map[state.Hexagram]huffCode)
 144  		buildCodes(tree, nil, codeMap)
 145  		for sym, code := range codeMap {
 146  			codes[uint8(sym)] = code.length
 147  		}
 148  		allCodes = append(allCodes, codes)
 149  	}
 150  
 151  	// All 100 codebooks should be identical.
 152  	for i := 1; i < len(allCodes); i++ {
 153  		for sym, length := range allCodes[0] {
 154  			if allCodes[i][sym] != length {
 155  				t.Errorf("iteration %d: symbol %d has length %d, want %d",
 156  					i, sym, allCodes[i][sym], length)
 157  			}
 158  		}
 159  	}
 160  }
 161  
 162  // TestShadowCompressRatio verifies that compression produces output
 163  // smaller than or comparable to input for repetitive data.
 164  func TestShadowCompressRatio(t *testing.T) {
 165  	ep := epoch.Colony
 166  
 167  	// Highly repetitive data should compress well.
 168  	data := bytes.Repeat([]byte("AAAA"), 1000)
 169  	sc, err := ShadowCompress(data, nil, ep)
 170  	if err != nil {
 171  		t.Fatalf("ShadowCompress: %v", err)
 172  	}
 173  
 174  	compressedSize := len(sc.Primary) + len(sc.Shadow) + shadowCompressedHeaderSize
 175  	ratio := float64(compressedSize) / float64(len(data))
 176  	t.Logf("repetitive data: %d bytes → %d bytes (ratio %.2f)",
 177  		len(data), compressedSize, ratio)
 178  
 179  	// The shadow stream adds 6 bits per token regardless of auxiliary
 180  	// data, so the total output includes structural overhead. For
 181  	// highly repetitive data the Huffman stream compresses well, but
 182  	// the shadow stream is proportional to token count. Ratio under
 183  	// 2.0 indicates the Huffman layer is contributing.
 184  	if ratio > 2.0 {
 185  		t.Errorf("compression ratio %.2f too high for repetitive data", ratio)
 186  	}
 187  }
 188  
 189  // TestShadowCompressEpochVariation verifies that different epochs
 190  // produce different shadow streams but recover the same data.
 191  func TestShadowCompressEpochVariation(t *testing.T) {
 192  	data := []byte("epoch variation test")
 193  
 194  	epochs := []epoch.Epoch{epoch.Colony, epoch.CryptoWalk128, epoch.CryptoWalk256}
 195  
 196  	for _, ep := range epochs {
 197  		sc, err := ShadowCompress(data, nil, ep)
 198  		if err != nil {
 199  			t.Fatalf("epoch %s: ShadowCompress: %v", ep, err)
 200  		}
 201  
 202  		recovered, _, err := ShadowDecompress(sc, ep)
 203  		if err != nil {
 204  			t.Fatalf("epoch %s: ShadowDecompress: %v", ep, err)
 205  		}
 206  
 207  		if !bytes.Equal(recovered, data) {
 208  			t.Errorf("epoch %s: recovered != original", ep)
 209  		}
 210  	}
 211  }
 212