Quellcode durchsuchen

wire: handle marshalling null-terminated strings

The ICQ superset of the OSCAR protocol uses null-terminated strings.
Because the null-terminated strings are prefixed with the string length,
they are technically pascal c-strings. ¯\_(ツ)_/¯
Mike vor 2 Jahren
Ursprung
Commit
fa33cdf87b
4 geänderte Dateien mit 64 neuen und 4 gelöschten Zeilen
  1. 12 1
      wire/decode.go
  2. 24 0
      wire/decode_test.go
  3. 16 3
      wire/encode.go
  4. 12 0
      wire/encode_test.go

+ 12 - 1
wire/decode.go

@@ -9,7 +9,10 @@ import (
 	"reflect"
 )
 
-var ErrUnmarshalFailure = errors.New("failed to unmarshal")
+var (
+	ErrUnmarshalFailure  = errors.New("failed to unmarshal")
+	errNotNullTerminated = errors.New("nullterm tag is set, but string is not null-terminated")
+)
 
 // UnmarshalBE unmarshalls OSCAR protocol messages in big-endian format.
 func UnmarshalBE(v any, r io.Reader) error {
@@ -153,6 +156,14 @@ func unmarshalString(v reflect.Value, oscTag oscarTag, r io.Reader, order binary
 			return err
 		}
 	}
+
+	if oscTag.nullTerminated {
+		if buf[len(buf)-1] != 0x00 {
+			return errNotNullTerminated
+		}
+		buf = buf[0 : len(buf)-1] // remove null terminator
+	}
+
 	// todo is there a more efficient way?
 	v.SetString(string(buf))
 	return nil

+ 24 - 0
wire/decode_test.go

@@ -132,6 +132,30 @@ func TestUnmarshal(t *testing.T) {
 				[]byte{0x0, 0xa}, /* len prefix */
 				[]byte{0x74, 0x65, 0x73, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65}...), /* str val */
 		},
+		{
+			name: "null-terminated string16",
+			prototype: &struct {
+				Val string `oscar:"len_prefix=uint16,nullterm"`
+			}{},
+			want: &struct {
+				Val string `oscar:"len_prefix=uint16,nullterm"`
+			}{
+				Val: "test-value",
+			},
+			given: append(
+				[]byte{0x0, 0xb}, /* len prefix */
+				[]byte{0x74, 0x65, 0x73, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00}...), /* str val */
+		},
+		{
+			name: "null-terminated string16 without null terminator",
+			prototype: &struct {
+				Val string `oscar:"len_prefix=uint16,nullterm"`
+			}{},
+			wantErr: errNotNullTerminated,
+			given: append(
+				[]byte{0x0, 0xa}, /* len prefix */
+				[]byte{0x74, 0x65, 0x73, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65}...), /* str val */
+		},
 		{
 			name: "string16 read error",
 			prototype: &struct {

+ 16 - 3
wire/encode.go

@@ -103,12 +103,16 @@ func marshalSlice(t reflect.Type, v reflect.Value, oscTag oscarTag, w io.Writer,
 }
 
 func marshalString(oscTag oscarTag, v reflect.Value, w io.Writer, order binary.ByteOrder) error {
+	str := v.String()
+	if oscTag.nullTerminated {
+		str = str + "\x00"
+	}
 	if oscTag.hasLenPrefix {
-		if err := marshalUnsignedInt(oscTag.lenPrefix, len(v.String()), w, order); err != nil {
+		if err := marshalUnsignedInt(oscTag.lenPrefix, len(str), w, order); err != nil {
 			return err
 		}
 	}
-	return binary.Write(w, order, []byte(v.String()))
+	return binary.Write(w, order, []byte(str))
 }
 
 func marshalStruct(t reflect.Type, v reflect.Value, oscTag oscarTag, w io.Writer, order binary.ByteOrder) error {
@@ -172,6 +176,7 @@ type oscarTag struct {
 	hasLenPrefix   bool
 	lenPrefix      reflect.Kind
 	optional       bool
+	nullTerminated bool
 }
 
 func parseOSCARTag(tag reflect.StructTag) (oscarTag, error) {
@@ -210,7 +215,15 @@ func parseOSCARTag(tag reflect.StructTag) (oscarTag, error) {
 				}
 			}
 		} else {
-			oscTag.optional = kvSplit[0] == "optional"
+			switch kvSplit[0] {
+			case "optional":
+				oscTag.optional = true
+			case "nullterm":
+				oscTag.nullTerminated = true
+			default:
+				return oscTag, fmt.Errorf("%w: unsupported struct tag %s",
+					errInvalidStructTag, kvSplit[0])
+			}
 		}
 	}
 

+ 12 - 0
wire/encode_test.go

@@ -107,6 +107,18 @@ func TestMarshal(t *testing.T) {
 				[]byte{0x0, 0xa}, /* len prefix */
 				[]byte{0x74, 0x65, 0x73, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65}...), /* str val */
 		},
+		{
+			name: "null-terminated string16",
+			w:    &bytes.Buffer{},
+			given: struct {
+				Val string `oscar:"len_prefix=uint16,nullterm"`
+			}{
+				Val: "test-value",
+			},
+			want: append(
+				[]byte{0x0, 0xb}, /* len prefix */
+				[]byte{0x74, 0x65, 0x73, 0x74, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x00}...), /* str val */
+		},
 		{
 			name: "string16 write error",
 			w:    errWriter{},