summaryrefslogtreecommitdiffstats
path: root/katzenpost/dispatcher/frame.go
diff options
context:
space:
mode:
Diffstat (limited to 'katzenpost/dispatcher/frame.go')
-rw-r--r--katzenpost/dispatcher/frame.go84
1 files changed, 84 insertions, 0 deletions
diff --git a/katzenpost/dispatcher/frame.go b/katzenpost/dispatcher/frame.go
new file mode 100644
index 0000000..43f547c
--- /dev/null
+++ b/katzenpost/dispatcher/frame.go
@@ -0,0 +1,84 @@
+package dispatcher
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "errors"
+ "fmt"
+
+ "github.com/fxamacker/cbor/v2"
+)
+
+const (
+ ProtocolVersion = 1
+ MaxSphinxPayload = 2000
+ FrameDataBytes = 1500
+ MaxFrames = (MaxEnvelopeBytes + FrameDataBytes - 1) / FrameDataBytes
+)
+
+type Frame struct {
+ Version uint8 `cbor:"1,keyasint"`
+ ID [16]byte `cbor:"2,keyasint"`
+ Digest [32]byte `cbor:"3,keyasint"`
+ Index uint16 `cbor:"4,keyasint"`
+ Total uint16 `cbor:"5,keyasint"`
+ Data []byte `cbor:"6,keyasint"`
+}
+
+func SplitEnvelope(encoded []byte) ([][]byte, error) {
+ if len(encoded) == 0 || len(encoded) > MaxEnvelopeBytes {
+ return nil, errors.New("invalid encoded envelope size")
+ }
+
+ var id [16]byte
+ if _, err := rand.Read(id[:]); err != nil {
+ return nil, fmt.Errorf("generate message id: %w", err)
+ }
+ digest := sha256.Sum256(encoded)
+ total := (len(encoded) + FrameDataBytes - 1) / FrameDataBytes
+ if total > MaxFrames {
+ return nil, errors.New("encoded envelope requires too many frames")
+ }
+
+ frames := make([][]byte, 0, total)
+ for index, start := 0, 0; start < len(encoded); index, start = index+1, start+FrameDataBytes {
+ end := min(start+FrameDataBytes, len(encoded))
+ frame := Frame{
+ Version: ProtocolVersion,
+ ID: id,
+ Digest: digest,
+ Index: uint16(index),
+ Total: uint16(total),
+ Data: encoded[start:end],
+ }
+ payload, err := cbor.Marshal(frame)
+ if err != nil {
+ return nil, fmt.Errorf("encode frame %d: %w", index, err)
+ }
+ if len(payload) > MaxSphinxPayload {
+ return nil, fmt.Errorf("frame %d exceeds Sphinx payload limit", index)
+ }
+ frames = append(frames, payload)
+ }
+ return frames, nil
+}
+
+func DecodeFrame(payload []byte) (Frame, error) {
+ if len(payload) == 0 || len(payload) > MaxSphinxPayload {
+ return Frame{}, errors.New("invalid frame size")
+ }
+ var frame Frame
+ if err := cbor.Unmarshal(payload, &frame); err != nil {
+ return Frame{}, fmt.Errorf("decode frame: %w", err)
+ }
+ if frame.Version != ProtocolVersion {
+ return Frame{}, errors.New("unsupported frame version")
+ }
+ if frame.Total == 0 || int(frame.Total) > MaxFrames || frame.Index >= frame.Total {
+ return Frame{}, errors.New("invalid frame sequence")
+ }
+ if len(frame.Data) == 0 || len(frame.Data) > FrameDataBytes {
+ return Frame{}, errors.New("invalid frame data size")
+ }
+ return frame, nil
+}