mirror of
https://github.com/nethical6/conversation-steganography.git
synced 2026-07-22 01:55:08 +02:00
201 lines
6.3 KiB
Go
201 lines
6.3 KiB
Go
package conversationstenography
|
|
|
|
import (
|
|
"bytes"
|
|
"math/rand"
|
|
"testing"
|
|
)
|
|
|
|
func TestChatMessagePackingRoundTrip(t *testing.T) {
|
|
for _, original := range [][]byte{
|
|
{},
|
|
[]byte("i think i might've fucked up a bit too hard"),
|
|
{0, 1, 2, 3, 254, 255},
|
|
} {
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("round trip %q: %q, %v", original, decoded, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommonChatMessageCompresses(t *testing.T) {
|
|
original := []byte("i think i might've fucked up a bit too hard")
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(packed) >= len(original)/2 {
|
|
t.Fatalf("packed message is still too large: %d bytes from %d", len(packed), len(original))
|
|
}
|
|
}
|
|
|
|
func TestCommonExactMessageUsesPhraseProtocol(t *testing.T) {
|
|
original := []byte("meet me after lunch")
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(packed) != 1 || int(packed[0]) < messagePhraseBase {
|
|
t.Fatalf("got %d-byte mode-%d representation %x; want one phrase-mode byte", len(packed), packed[0], packed)
|
|
}
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("got %q, %v", decoded, err)
|
|
}
|
|
}
|
|
|
|
func TestShortChatUsesCompactFragmentProtocol(t *testing.T) {
|
|
original := []byte("meet me after lunch today")
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if packed[0] < messageCompactBase || packed[0] >= messageCompactBase+messageCompactModes {
|
|
t.Fatalf("got mode %d; want compact fragments", packed[0])
|
|
}
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("got %q, %v", decoded, err)
|
|
}
|
|
}
|
|
|
|
func TestMessagePackingRandomBinaryRoundTrips(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(1))
|
|
for size := 0; size <= 4096; size += 17 {
|
|
original := make([]byte, size)
|
|
if _, err := rng.Read(original); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatalf("size %d: %v", size, err)
|
|
}
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("size %d round trip failed: %v", size, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFragmentDecoderRejectsMalformedInput(t *testing.T) {
|
|
for _, packed := range [][]byte{
|
|
{messageFragments, 255},
|
|
{messageFragments, fragmentLiteralBase},
|
|
{messageCompactBase + 2, 0xfc},
|
|
{messageCompactBase + 2, 0xc0},
|
|
{messageCompactBase + 1, 0x01},
|
|
{messageDenseBase + 3, 0xf8},
|
|
{messageDenseBase + 3, 0x80},
|
|
{messageDenseBase + 1, 0x01},
|
|
} {
|
|
if _, err := unpackMessage(packed); err == nil {
|
|
t.Fatalf("malformed fragment stream accepted: %x", packed)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDenseFragmentsWinForCommonConnectiveText(t *testing.T) {
|
|
original := []byte("i that it is in the and it is for me")
|
|
packed, err := packMessage(original)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if packed[0] < messageDenseBase || packed[0] >= messageDenseBase+messageDenseModes {
|
|
t.Fatalf("dense mode did not win: mode=%d size=%d", packed[0], len(packed))
|
|
}
|
|
t.Logf("common connective text: %d bytes raw, %d bytes dense", len(original), len(packed))
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("got %q, %v", decoded, err)
|
|
}
|
|
}
|
|
|
|
func TestDenseFragmentsRandomBinaryRoundTrip(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(4))
|
|
for size := 0; size <= 256; size++ {
|
|
original := make([]byte, size)
|
|
if _, err := rng.Read(original); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
packed := packDenseFragments(original)
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("size %d round trip failed: %v", size, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompactFragmentsRandomBinaryRoundTrip(t *testing.T) {
|
|
rng := rand.New(rand.NewSource(2))
|
|
for size := 0; size <= 256; size++ {
|
|
original := make([]byte, size)
|
|
if _, err := rng.Read(original); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
packed := packCompactFragments(original)
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("size %d round trip failed: %v", size, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDynamicConversationDictionary(t *testing.T) {
|
|
dictionary := []byte("The blue ceramic bicycle is waiting beside the northern greenhouse at Riverside Botanical Garden.")
|
|
original := []byte("the northern greenhouse at Riverside Botanical Garden")
|
|
packed, err := packMessageWithDictionary(original, dictionary)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if packed[0] != messageDynamic || len(packed) >= len(original)/2 {
|
|
t.Fatalf("dynamic dictionary was not effective: mode=%d size=%d original=%d", packed[0], len(packed), len(original))
|
|
}
|
|
t.Logf("repeated-topic text: %d bytes raw, %d bytes with synchronized context", len(original), len(packed))
|
|
decoded, err := unpackMessageWithDictionary(packed, dictionary)
|
|
if err != nil || !bytes.Equal(decoded, original) {
|
|
t.Fatalf("got %q, %v", decoded, err)
|
|
}
|
|
if _, err := unpackMessage(packed); err == nil {
|
|
t.Fatal("dynamic stream decoded without its synchronized dictionary")
|
|
}
|
|
if _, err := unpackMessageWithDictionary(packed, []byte("wrong conversation")); err == nil {
|
|
t.Fatal("dynamic stream decoded with the wrong conversation dictionary")
|
|
}
|
|
}
|
|
|
|
func TestEveryAuthenticatedPhraseRoundTripsWithoutBody(t *testing.T) {
|
|
seen := make(map[string]struct{}, len(commonMessages))
|
|
for index, phrase := range commonMessages {
|
|
if _, duplicate := seen[phrase]; duplicate {
|
|
t.Fatalf("duplicate phrasebook entry %q", phrase)
|
|
}
|
|
seen[phrase] = struct{}{}
|
|
packed, err := packMessage([]byte(phrase))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantMode := byte(messagePhraseBase + index)
|
|
if len(packed) != 1 || packed[0] != wantMode {
|
|
t.Fatalf("phrase %q packed as %x; want mode %d only", phrase, packed, wantMode)
|
|
}
|
|
decoded, err := unpackMessage(packed)
|
|
if err != nil || string(decoded) != phrase {
|
|
t.Fatalf("phrase %q decoded as %q, %v", phrase, decoded, err)
|
|
}
|
|
}
|
|
if messagePhraseBase+len(commonMessages) > 256 {
|
|
t.Fatal("phrasebook exceeds one-byte authenticated mode space")
|
|
}
|
|
}
|
|
|
|
func TestPhraseModeRejectsUnexpectedBody(t *testing.T) {
|
|
if _, err := unpackMessage([]byte{messagePhraseBase, 0}); err == nil {
|
|
t.Fatal("phrase mode accepted unauthenticated body data")
|
|
}
|
|
}
|