feat: Introduce sigil transformers for trix containers
This commit introduces the concept of "sigils," which are programmable, pure-function transformers that can be applied to a Trix container's payload. - A `Sigil` interface with `In` and `Out` methods is defined in the `trix` package. - The `Trix` struct now includes a `Sigils` field to attach a chain of transformers. - The `Encode` function applies the `In` transformations before encoding the payload. - The caller is responsible for applying the `Out` transformations after decoding. This new feature provides a flexible and extensible data pipeline for `Trix` containers. The implementation is fully tested with the Good, Bad, and Ugly testing strategy.
This commit is contained in:
parent
d5ae9a44e1
commit
f7587b2471
3 changed files with 111 additions and 1 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue