Skip to content

Commit

Permalink
wire: handle marshalling null-terminated strings
Browse files Browse the repository at this point in the history
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. ¯\_(ツ)_/¯
  • Loading branch information
mk6i committed Jul 26, 2024
1 parent 899663f commit fa33cdf
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 4 deletions.
13 changes: 12 additions & 1 deletion wire/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions wire/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 16 additions & 3 deletions wire/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -172,6 +176,7 @@ type oscarTag struct {
hasLenPrefix bool
lenPrefix reflect.Kind
optional bool
nullTerminated bool
}

func parseOSCARTag(tag reflect.StructTag) (oscarTag, error) {
Expand Down Expand Up @@ -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])
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions wire/encode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down

0 comments on commit fa33cdf

Please sign in to comment.