diff --git a/examples/main.go b/examples/main.go index 5adae8c..0a31fb2 100644 --- a/examples/main.go +++ b/examples/main.go @@ -41,6 +41,7 @@ func main() { trixContainer := &trix.Trix{ Header: header, Payload: actualCiphertext, + Sigils: []trix.Sigil{&trix.ReverseSigil{}}, } // 4. Encode the .trix container into its binary format @@ -58,6 +59,13 @@ func main() { log.Fatalf("Failed to decode .trix container: %v", err) } + // Manually apply the Out method of the sigil to restore the original payload. + restoredPayload, err := trixContainer.Sigils[0].Out(decodedTrix.Payload) + if err != nil { + log.Fatalf("Failed to apply sigil: %v", err) + } + decodedTrix.Payload = restoredPayload + // 6. Reassemble the ciphertext (nonce + payload) and decrypt retrievedNonceStr, ok := decodedTrix.Header["nonce"].(string) if !ok { diff --git a/pkg/trix/trix.go b/pkg/trix/trix.go index 8f1c9e7..bab5edd 100644 --- a/pkg/trix/trix.go +++ b/pkg/trix/trix.go @@ -17,12 +17,20 @@ var ( ErrInvalidMagicNumber = errors.New("trix: invalid magic number") ErrInvalidVersion = errors.New("trix: invalid version") ErrMagicNumberLength = errors.New("trix: magic number must be 4 bytes long") + ErrNilSigil = errors.New("trix: sigil cannot be nil") ) +// Sigil defines the interface for a data transformer. +type Sigil interface { + In(data []byte) ([]byte, error) + Out(data []byte) ([]byte, error) +} + // Trix represents the structure of a .trix file. type Trix struct { Header map[string]interface{} Payload []byte + Sigils []Sigil `json:"-"` // Ignore Sigils during JSON marshaling } // Encode serializes a Trix struct into the .trix binary format. @@ -31,6 +39,19 @@ func Encode(trix *Trix, magicNumber string) ([]byte, error) { return nil, ErrMagicNumberLength } + // Apply sigils to the payload before encoding + payload := trix.Payload + for _, sigil := range trix.Sigils { + if sigil == nil { + return nil, ErrNilSigil + } + var err error + payload, err = sigil.In(payload) + if err != nil { + return nil, err + } + } + headerBytes, err := json.Marshal(trix.Header) if err != nil { return nil, err @@ -60,7 +81,7 @@ func Encode(trix *Trix, magicNumber string) ([]byte, error) { } // Write Payload - if _, err := buf.Write(trix.Payload); err != nil { + if _, err := buf.Write(payload); err != nil { return nil, err } @@ -68,6 +89,7 @@ func Encode(trix *Trix, magicNumber string) ([]byte, error) { } // Decode deserializes the .trix binary format into a Trix struct. +// Note: Sigils are not stored in the format and must be re-attached by the caller. func Decode(data []byte, magicNumber string) (*Trix, error) { if len(magicNumber) != 4 { return nil, ErrMagicNumberLength @@ -120,3 +142,21 @@ func Decode(data []byte, magicNumber string) (*Trix, error) { Payload: payload, }, nil } + +// ReverseSigil is an example Sigil that reverses the bytes of the payload. +type ReverseSigil struct{} + +// In reverses the bytes of the data. +func (s *ReverseSigil) In(data []byte) ([]byte, error) { + reversed := make([]byte, len(data)) + for i, j := 0, len(data)-1; i < len(data); i, j = i+1, j-1 { + reversed[i] = data[j] + } + return reversed, nil +} + +// Out reverses the bytes of the data. +func (s *ReverseSigil) Out(data []byte) ([]byte, error) { + // Reversing the bytes again restores the original data. + return s.In(data) +} diff --git a/pkg/trix/trix_test.go b/pkg/trix/trix_test.go index 4d9d1ed..2cf4912 100644 --- a/pkg/trix/trix_test.go +++ b/pkg/trix/trix_test.go @@ -1,6 +1,7 @@ package trix import ( + "errors" "io" "reflect" "testing" @@ -111,3 +112,64 @@ func TestTrixEncodeDecode_Ugly(t *testing.T) { assert.NotNil(t, decoded) }) } + +// --- Sigil Tests --- + +// FailingSigil is a helper for testing sigils that intentionally fail. +type FailingSigil struct { + err error +} + +func (s *FailingSigil) In(data []byte) ([]byte, error) { + return nil, s.err +} +func (s *FailingSigil) Out(data []byte) ([]byte, error) { + return nil, s.err +} + +func TestSigilPipeline_Good(t *testing.T) { + originalPayload := []byte("hello world") + trix := &Trix{ + Header: map[string]interface{}{}, + Payload: originalPayload, + Sigils: []Sigil{&ReverseSigil{}}, + } + + encoded, err := Encode(trix, "SIGL") + assert.NoError(t, err) + + decoded, err := Decode(encoded, "SIGL") + assert.NoError(t, err) + + // Manually apply the Out method to restore the original payload. + restoredPayload, err := trix.Sigils[0].Out(decoded.Payload) + assert.NoError(t, err) + assert.Equal(t, originalPayload, restoredPayload) +} + +func TestSigilPipeline_Bad(t *testing.T) { + expectedErr := errors.New("sigil failed") + trix := &Trix{ + Header: map[string]interface{}{}, + Payload: []byte("some data"), + Sigils: []Sigil{&ReverseSigil{}, &FailingSigil{err: expectedErr}}, + } + + _, err := Encode(trix, "FAIL") + assert.Error(t, err) + assert.Equal(t, expectedErr, err) +} + +func TestSigilPipeline_Ugly(t *testing.T) { + t.Run("NilSigil", func(t *testing.T) { + trix := &Trix{ + Header: map[string]interface{}{}, + Payload: []byte("some data"), + Sigils: []Sigil{nil}, + } + + _, err := Encode(trix, "UGLY") + assert.Error(t, err) + assert.Equal(t, ErrNilSigil, err) + }) +}