From 6a7ecbf7c600a2ee3b6207b698b85803a67ef8f2 Mon Sep 17 00:00:00 2001 From: Tnze Date: Fri, 2 Apr 2021 00:30:45 +0800 Subject: [PATCH] Fix bugs with pk.Ary. Add tests and examples. --- net/packet/util.go | 55 +++++++++++++++++++++++++++---- net/packet/util_test.go | 72 +++++++++++++++++++++++++++++++++++++++++ save/chunk_test.go | 23 +++++++------ 3 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 net/packet/util_test.go diff --git a/net/packet/util.go b/net/packet/util.go index 43795ca..d7e08a4 100644 --- a/net/packet/util.go +++ b/net/packet/util.go @@ -1,19 +1,31 @@ package packet import ( + "errors" "io" "reflect" ) +// Ary is used to send or receive the packet field like "Array of X" +// which has a count must be known from the context. +// +// Typically, you must decode an integer representing the length. Then +// receive the corresponding amount of data according to the length. +// In this case, the field Len should be a pointer of integer type so +// the value can be updating when Packet.Scan() method is decoding the +// previous field. +// In some special cases, you might want to read an "Array of X" with a fix length. +// So it's allowed to directly set an integer type Len, but not a pointer. +// +// Note that Ary DO NOT read or write the Len. You are controlling it manually. type Ary struct { - Len Field // Pointer of VarInt, VarLong, Int or Long + Len interface{} // Value or Pointer of any integer type, only needed in ReadFrom Ary interface{} // Slice of FieldEncoder, FieldDecoder or both (Field) } func (a Ary) WriteTo(r io.Writer) (n int64, err error) { - length := int(reflect.ValueOf(a.Len).Int()) array := reflect.ValueOf(a.Ary) - for i := 0; i < length; i++ { + for i := 0; i < array.Len(); i++ { elem := array.Index(i) nn, err := elem.Interface().(FieldEncoder).WriteTo(r) n += nn @@ -24,8 +36,24 @@ func (a Ary) WriteTo(r io.Writer) (n int64, err error) { return n, nil } +func (a Ary) length() int { + v := reflect.ValueOf(a.Len) + for { + switch v.Kind() { + case reflect.Ptr: + v = v.Elem() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return int(v.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return int(v.Uint()) + default: + panic(errors.New("unsupported Len value: " + v.Type().String())) + } + } +} + func (a Ary) ReadFrom(r io.Reader) (n int64, err error) { - length := int(reflect.ValueOf(a.Len).Elem().Int()) + length := a.length() array := reflect.ValueOf(a.Ary).Elem() if array.Cap() < length { array.Set(reflect.MakeSlice(array.Type(), length, length)) @@ -42,19 +70,32 @@ func (a Ary) ReadFrom(r io.Reader) (n int64, err error) { } type Opt struct { - Has *Boolean + Has interface{} // Boolean, *Boolean or func() bool Field interface{} // FieldEncoder, FieldDecoder or both (Field) } +func (o Opt) has() bool { + switch o.Has.(type) { + case Boolean: + return bool(o.Has.(Boolean)) + case *Boolean: + return bool(*o.Has.(*Boolean)) + case func() bool: + return o.Has.(func() bool)() + default: + panic(errors.New("unsupported Has value")) + } +} + func (o Opt) WriteTo(w io.Writer) (int64, error) { - if *o.Has { + if o.has() { return o.Field.(FieldEncoder).WriteTo(w) } return 0, nil } func (o Opt) ReadFrom(r io.Reader) (int64, error) { - if *o.Has { + if o.has() { return o.Field.(FieldDecoder).ReadFrom(r) } return 0, nil diff --git a/net/packet/util_test.go b/net/packet/util_test.go new file mode 100644 index 0000000..e6057fe --- /dev/null +++ b/net/packet/util_test.go @@ -0,0 +1,72 @@ +package packet_test + +import ( + "bytes" + "testing" + + pk "github.com/Tnze/go-mc/net/packet" +) + +func ExampleAry_WriteTo() { + data := []pk.Int{0, 1, 2, 3, 4, 5, 6} + // Len is completely ignored by WriteTo method. + // The length is inferred from the length of Ary. + pk.Marshal( + 0x00, + // It's important to remember that + // typically the responsibility of + // sending the length field + // is on you. + pk.VarInt(len(data)), + pk.Ary{ + Len: len(data), // this line can be removed + Ary: data, + }, + ) +} + +func ExampleAry_ReadFrom() { + var length pk.VarInt + var data []pk.String + + var p pk.Packet // = conn.ReadPacket() + if err := p.Scan( + + &length, // decode length first + pk.Ary{ // then decode Ary according to length + Len: &length, + Ary: &data, + }, + ); err != nil { + panic(err) + } +} + +func TestAry_WriteTo(t *testing.T) { + var buf bytes.Buffer + want := []byte{ + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x03, + } + int3, long3, varint3, varlong3 := pk.Int(3), pk.Long(3), pk.VarInt(3), pk.VarLong(3) + for _, item := range [...]pk.Ary{ + {Len: int3, Ary: []pk.Int{1, 2, 3}}, + {Len: long3, Ary: []pk.Int{1, 2, 3}}, + {Len: varint3, Ary: []pk.Int{1, 2, 3}}, + {Len: varlong3, Ary: []pk.Int{1, 2, 3}}, + {Len: &int3, Ary: []pk.Int{1, 2, 3}}, + {Len: &long3, Ary: []pk.Int{1, 2, 3}}, + {Len: &varint3, Ary: []pk.Int{1, 2, 3}}, + {Len: &varlong3, Ary: []pk.Int{1, 2, 3}}, + } { + _, err := item.WriteTo(&buf) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(buf.Bytes(), want) { + t.Fatalf("Ary encoding error: got %#v, want %#v", buf.Bytes(), want) + } + buf.Reset() + } +} diff --git a/save/chunk_test.go b/save/chunk_test.go index 9ea7004..61e5dd7 100644 --- a/save/chunk_test.go +++ b/save/chunk_test.go @@ -86,14 +86,19 @@ func ExampleColumn_send() { _, err := pk.Tuple{ pk.Short(0), // Block count pk.UnsignedByte(bpb), // Bits Per Block - pk.Opt{ + hasPalette, pk.Opt{ Has: &hasPalette, - Field: pk.Ary{ - Len: &paletteLength, - Ary: nil, // TODO: We need translate v.Palette (with type of []Block) to state ID + Field: pk.Tuple{ + paletteLength, pk.Ary{ + Len: &paletteLength, + Ary: nil, // TODO: We need translate v.Palette (with type of []Block) to state ID + }, }, }, // Palette - pk.Ary{Len: &dataArrayLength, Ary: dataArray}, // Data Array + dataArrayLength, pk.Ary{ + Len: &dataArrayLength, + Ary: dataArray, + }, // Data Array }.WriteTo(&buf) if err != nil { panic(err) @@ -110,12 +115,12 @@ func ExampleColumn_send() { pk.Boolean(true), // Full chunk PrimaryBitMask, // PrimaryBitMask pk.NBT(c.Level.Heightmaps), // Heightmaps - pk.Ary{ - Len: &bal, // Biomes array length + bal, pk.Ary{ + Len: bal, // Biomes array length Ary: c.Level.Biomes, // Biomes }, - pk.Ary{ - Len: &size, // Size + size, pk.Ary{ + Len: size, // Size Ary: pk.ByteArray(buf.Bytes()), // Data }, pk.VarInt(0), // Block entities array length