diff --git a/common/packetPaddingContainer/container.go b/common/packetPaddingContainer/container.go new file mode 100644 index 0000000..acf5137 --- /dev/null +++ b/common/packetPaddingContainer/container.go @@ -0,0 +1,47 @@ +package packetPaddingContainer + +import "encoding/binary" + +func New() PacketPaddingContainer { + return packetPaddingContainer{} +} + +type packetPaddingContainer struct { +} + +func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte { + data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, padding)...) + data_length := len(data_OWNERSHIP_RELINQUISHED) + data = append(data, byte(data_length>>8), byte(data_length)) + return data +} + +func (c packetPaddingContainer) Pad(padding int) []byte { + if assertPaddingLengthIsNotNegative := padding < 0; assertPaddingLengthIsNotNegative { + return nil + } + switch padding { + case 0: + return []byte{} + case 1: + return []byte{0} + case 2: + return []byte{0, 0} + default: + return append(make([]byte, padding-2), byte(padding>>8), byte(padding)) + } + +} + +func (c packetPaddingContainer) Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) { + if len(wrappedData_OWNERSHIP_RELINQUISHED) < 2 { + return nil, len(wrappedData_OWNERSHIP_RELINQUISHED) + } + wrappedData_tail := wrappedData_OWNERSHIP_RELINQUISHED[len(wrappedData_OWNERSHIP_RELINQUISHED)-2:] + dataLength := int(binary.BigEndian.Uint16(wrappedData_tail)) + paddingLength := len(wrappedData_OWNERSHIP_RELINQUISHED) - dataLength - 2 + if paddingLength < 0 { + return nil, paddingLength + } + return wrappedData_OWNERSHIP_RELINQUISHED[:dataLength], paddingLength +} diff --git a/common/packetPaddingContainer/containerIfce.go b/common/packetPaddingContainer/containerIfce.go new file mode 100644 index 0000000..31cf317 --- /dev/null +++ b/common/packetPaddingContainer/containerIfce.go @@ -0,0 +1,34 @@ +package packetPaddingContainer + +// PacketPaddingContainer is an interface that defines methods to pad packets +// with a given number of bytes, and to unpack the padding from a padded packet. +// The packet format is as follows if the desired output length is greater than +// 2 bytes: +// | data | padding | data length | +// The data length is a 16-bit big-endian integer that represents the length of +// the data in bytes. +// If the desired output length is 2 bytes or less, the packet format is as +// follows: +// | padding | +// No payload will be included in the packet. +type PacketPaddingContainer interface { + // Pack pads the given data with the given number of bytes, and appends the + // length of the data to the end of the data. The returned byte slice + // contains the padded data. + // This generates a packet with a length of + // len(data_OWNERSHIP_RELINQUISHED) + padding + 2 + // @param data_OWNERSHIP_RELINQUISHED - The payload, this reference is consumed and should not be used after this call. + // @param padding - The number of padding bytes to add to the data. + Pack(data_OWNERSHIP_RELINQUISHED []byte, padding int) []byte + + // Unpack extracts the data and padding from the given padded data. It + // returns the data and the number of padding bytes. + // the data may be nil. + // @param wrappedData_OWNERSHIP_RELINQUISHED - The packet, this reference is consumed and should not be used after this call. + Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) + + // Pad returns a padding packet of padding length. + // If the padding length is less than 0, nil is returned. + // @param padding - The number of padding bytes to add to the data. + Pad(padding int) []byte +} diff --git a/common/packetPaddingContainer/container_test.go b/common/packetPaddingContainer/container_test.go new file mode 100644 index 0000000..06e72fe --- /dev/null +++ b/common/packetPaddingContainer/container_test.go @@ -0,0 +1,113 @@ +package packetPaddingContainer_test + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetPaddingContainer" +) + +func TestPacketPaddingContainer(t *testing.T) { + Convey("Given a PacketPaddingContainer", t, func() { + container := packetPaddingContainer.New() + + Convey("When packing data with padding", func() { + data := []byte("testdata") + padding := 4 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When packing empty data with padding", func() { + data := []byte("") + padding := 4 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When packing data with zero padding", func() { + data := []byte("testdata") + padding := 0 + packedData := container.Pack(data, padding) + + Convey("The packed data should have the correct length", func() { + expectedLength := len(data) + padding + 2 + So(len(packedData), ShouldEqual, expectedLength) + }) + + Convey("When unpacking the packed data", func() { + unpackedData, unpackedPadding := container.Unpack(packedData) + + Convey("The unpacked data should match the original data", func() { + So(string(unpackedData), ShouldEqual, string(data)) + }) + + Convey("The unpacked padding should match the original padding", func() { + So(unpackedPadding, ShouldEqual, padding) + }) + }) + }) + + Convey("When padding data", func() { + Convey("With a positive padding length", func() { + padLength := 3 + padData := container.Pad(padLength) + + Convey("The padded data should have the correct length", func() { + So(len(padData), ShouldEqual, padLength) + }) + }) + + Convey("With a zero padding length", func() { + padLength := 0 + padData := container.Pad(padLength) + + Convey("The padded data should be empty", func() { + So(len(padData), ShouldEqual, 0) + }) + }) + + Convey("With a negative padding length", func() { + padLength := -1 + padData := container.Pad(padLength) + + Convey("The padded data should be nil", func() { + So(padData, ShouldBeNil) + }) + }) + }) + }) +}