From 6500fbeaab50c9fa0e589d0e9e442dde943a4c9d Mon Sep 17 00:00:00 2001 From: Tnze Date: Fri, 9 Jul 2021 16:23:26 +0800 Subject: [PATCH] Document append. SNBT encoding bug fix (TagList) --- nbt/decode.go | 18 ++++--- nbt/encode.go | 53 +++++++++---------- nbt/example_test.go | 4 +- nbt/rawmsg.go | 6 +++ nbt/snbt.go | 32 +++++++----- nbt/snbt_decode.go | 30 +++++------ nbt/snbt_decode_test.go | 112 +++++++++++++++++++++++----------------- 7 files changed, 139 insertions(+), 116 deletions(-) diff --git a/nbt/decode.go b/nbt/decode.go index fbb2182..72e5240 100644 --- a/nbt/decode.go +++ b/nbt/decode.go @@ -9,6 +9,8 @@ import ( "reflect" ) +// Unmarshal decode binary NBT data and fill into v +// This is a shortcut to `NewDecoder(bytes.NewReader(data)).Decode(v)`. func Unmarshal(data []byte, v interface{}) error { _, err := NewDecoder(bytes.NewReader(data)).Decode(v) return err @@ -34,7 +36,7 @@ func (d *Decoder) Decode(v interface{}) (string, error) { } if c := d.checkCompressed(tagType); c != "" { - return tagName, fmt.Errorf("nbt: unknown Tag, maybe need %s", c) + return tagName, fmt.Errorf("nbt: unknown Tag, maybe compressed by %s, uncompress it first", c) } // We decode val not val.Elem because the NBTDecoder interface @@ -46,14 +48,16 @@ func (d *Decoder) Decode(v interface{}) (string, error) { return tagName, nil } -// check the first byte and return if it use compress +// checkCompressed check if the first byte is compress head func (d *Decoder) checkCompressed(head byte) (compress string) { - if head == 0x1f { //gzip - compress = "gzip" - } else if head == 0x78 { //zlib - compress = "zlib" + switch head { + case 0x1f: + return "gzip" + case 0x78: + return "zlib" + default: + return "" } - return } // ErrEND error will be returned when reading a NBT with only Tag_End diff --git a/nbt/encode.go b/nbt/encode.go index b01db77..a7ee4ff 100644 --- a/nbt/encode.go +++ b/nbt/encode.go @@ -32,13 +32,20 @@ func NewEncoder(w io.Writer) *Encoder { // In most cases, the root tag typed TagCompound and the tag name is empty string, // but any other type is allowed just because there is valid technically. Once if // you should pass an string into this, you should get a TagString. +// +// Normally, any slice or array typed Go value will be encoded as TagList, +// expect `[]int8`, `[]int32`, `[]int64`, `[]uint8`, `[]uint32` and `[]uint64`, +// which TagByteArray, TagIntArray and TagLongArray. +// To force encode them as TagList, add a struct field tag. +// You haven't ability to encode them as TagList as root element at this time, +// issue or pull-request is welcome. func (e *Encoder) Encode(v interface{}, tagName string) error { val := reflect.ValueOf(v) return e.marshal(val, getTagType(val), tagName) } func (e *Encoder) marshal(val reflect.Value, tagType byte, tagName string) error { - if err := e.writeHeader(val, tagType, tagName); err != nil { + if err := e.writeTag(tagType, tagName); err != nil { return err } if val.CanInterface() { @@ -49,21 +56,6 @@ func (e *Encoder) marshal(val reflect.Value, tagType byte, tagName string) error return e.writeValue(val, tagType) } -func (e *Encoder) writeHeader(val reflect.Value, tagType byte, tagName string) (err error) { - if tagType == TagList { - var eleType byte - if val.Len() > 0 { - eleType = getTagType(val.Index(0)) - } else { - eleType = getTagTypeByType(val.Type().Elem()) - } - err = e.writeListHeader(eleType, tagName, val.Len(), true) - } else { - err = e.writeTag(tagType, tagName) - } - return err -} - func (e *Encoder) writeValue(val reflect.Value, tagType byte) error { switch tagType { default: @@ -107,6 +99,16 @@ func (e *Encoder) writeValue(val reflect.Value, tagType byte) error { } case TagList: + var eleType byte + if val.Len() > 0 { + eleType = getTagType(val.Index(0)) + } else { + eleType = getTagTypeByType(val.Type().Elem()) + } + if err := e.writeListHeader(eleType, val.Len()); err != nil { + return err + } + for i := 0; i < val.Len(); i++ { arrVal := val.Index(i) err := e.writeValue(arrVal, getTagType(arrVal)) @@ -227,10 +229,10 @@ type tagProps struct { Type byte } -func parseTag(f reflect.StructField, v reflect.Value, tagName string) tagProps { - result := tagProps{} - result.Name = tagName - if result.Name == "" { +func parseTag(f reflect.StructField, v reflect.Value, tagName string) (result tagProps) { + if tagName != "" { + result.Name = tagName + } else { result.Name = f.Name } @@ -240,11 +242,11 @@ func parseTag(f reflect.StructField, v reflect.Value, tagName string) tagProps { if IsArrayTag(result.Type) { result.Type = TagList // for expanding the array to a standard list } else { - panic("list is only supported for array types (byte, int, long)") + panic("list is only supported for array types ([]byte, []int, []long)") } } - return result + return } func (e *Encoder) writeTag(tagType byte, tagName string) error { @@ -259,12 +261,7 @@ func (e *Encoder) writeTag(tagType byte, tagName string) error { return err } -func (e *Encoder) writeListHeader(elementType byte, tagName string, n int, writeTag bool) (err error) { - if writeTag { - if err = e.writeTag(TagList, tagName); err != nil { - return - } - } +func (e *Encoder) writeListHeader(elementType byte, n int) (err error) { if _, err = e.w.Write([]byte{elementType}); err != nil { return } diff --git a/nbt/example_test.go b/nbt/example_test.go index 500f70e..a0a4c1f 100644 --- a/nbt/example_test.go +++ b/nbt/example_test.go @@ -79,9 +79,9 @@ func ExampleEncoder_Encode_tagCompound() { // 0a 00 00 08 00 04 6e 61 6d 65 00 04 54 6e 7a 65 00 } -func ExampleEncoder_WriteSNBT() { +func ExampleEncoder_writeSNBT() { var buf bytes.Buffer - if err := NewEncoder(&buf).WriteSNBT(`{ name: [Tnze, "Xi_Xi_Mi"]}`); err != nil { + if err := NewEncoder(&buf).Encode(StringifiedMessage(`{ name: [Tnze, "Xi_Xi_Mi"]}`), ""); err != nil { panic(err) } fmt.Printf("% 02x ", buf.Bytes()) diff --git a/nbt/rawmsg.go b/nbt/rawmsg.go index 89ce955..ed46e75 100644 --- a/nbt/rawmsg.go +++ b/nbt/rawmsg.go @@ -9,6 +9,9 @@ import ( ) // RawMessage stores the raw binary data of NBT. +// This is usable if you wanna store an unknown NBT data and parse it later. +// Notice that this struct doesn't store the tag name. To convert RawMessage to valid NBT binary value: +// Encoder.Encode(RawMessage, Name) = []byte{ Type (1 byte) | n (2 byte) | Name (n byte) | Data}. type RawMessage struct { Type byte Data []byte @@ -38,6 +41,8 @@ func (m *RawMessage) Decode(tagType byte, r DecoderReader) error { return nil } +// String convert the data into the SNBT(Stringified NBT) format. +// The output is valid for using in in-game command. func (m RawMessage) String() string { if m.Type == TagEnd { return "TagEnd" @@ -53,6 +58,7 @@ func (m RawMessage) String() string { return sb.String() } +// Unmarshal decode the data into v. func (m RawMessage) Unmarshal(v interface{}) error { d := NewDecoder(bytes.NewReader(m.Data)) val := reflect.ValueOf(v) diff --git a/nbt/snbt.go b/nbt/snbt.go index 9480558..dd0d1c5 100644 --- a/nbt/snbt.go +++ b/nbt/snbt.go @@ -10,28 +10,32 @@ import ( type StringifiedMessage string -func (m StringifiedMessage) TagType() (tagType byte) { +func (m StringifiedMessage) TagType() byte { d := decodeState{data: []byte(m)} d.scan.reset() d.scanWhile(scanSkipSpace) switch d.opcode { + default: + return TagEnd + case scanBeginLiteral: start := d.readIndex() if d.scanWhile(scanContinue); d.opcode == scanError { - return + return TagEnd } literal := d.data[start:d.readIndex()] - tagType, _ = parseLiteral(literal) + tagType, _ := parseLiteral(literal) + return tagType case scanBeginCompound: - tagType = TagCompound + return TagCompound case scanBeginList: d.scanWhile(scanSkipSpace) if d.opcode == scanBeginLiteral { start := d.readIndex() if d.scanWhile(scanContinue); d.opcode == scanError { - return + return TagEnd } literal := d.data[start:d.readIndex()] if d.opcode == scanSkipSpace { @@ -40,18 +44,16 @@ func (m StringifiedMessage) TagType() (tagType byte) { if d.opcode == scanListType { switch literal[0] { case 'B': - tagType = TagByteArray + return TagByteArray case 'I': - tagType = TagIntArray + return TagIntArray case 'L': - tagType = TagLongArray + return TagLongArray } } - } else { - tagType = TagList } + return TagList } - return } func (m StringifiedMessage) Encode(w io.Writer) error { @@ -227,11 +229,15 @@ func writeEscapeStr(sb *strings.Builder, str string) { sc := strings.Count(str, `'`) if dc > sc { sb.WriteString("'") - strings.NewReplacer(`'`, `\'`, `\`, `\\`).WriteString(sb, str) + if _, err := strings.NewReplacer(`'`, `\'`, `\`, `\\`).WriteString(sb, str); err != nil { + panic(err) + } sb.WriteString("'") } else { sb.WriteString(`"`) - strings.NewReplacer(`"`, `\"`, `\`, `\\`).WriteString(sb, str) + if _, err := strings.NewReplacer(`"`, `\"`, `\`, `\\`).WriteString(sb, str); err != nil { + panic(err) + } sb.WriteString(`"`) } return diff --git a/nbt/snbt_decode.go b/nbt/snbt_decode.go index b7d4dab..44d4e09 100644 --- a/nbt/snbt_decode.go +++ b/nbt/snbt_decode.go @@ -16,12 +16,6 @@ type decodeState struct { const phasePanicMsg = "SNBT decoder out of sync - data changing underfoot?" -func (e *Encoder) WriteSNBT(snbt string) error { - d := decodeState{data: []byte(snbt)} - d.scan.reset() - return writeValue(e, &d, true, "") -} - func writeValue(e *Encoder, d *decodeState, writeTag bool, tagName string) error { d.scanWhile(scanSkipSpace) switch d.opcode { @@ -53,7 +47,12 @@ func writeValue(e *Encoder, d *decodeState, writeTag bool, tagName string) error return writeCompoundPayload(e, d) case scanBeginList: - _, err := writeListOrArray(e, d, writeTag, tagName) + if writeTag { + if err := e.writeTag(TagList, tagName); err != nil { + return err + } + } + _, err := writeListOrArray(e, d) return err } } @@ -140,10 +139,10 @@ func writeCompoundPayload(e *Encoder, d *decodeState) error { return err } -func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) (tagType byte, err error) { +func writeListOrArray(e *Encoder, d *decodeState) (tagType byte, err error) { d.scanWhile(scanSkipSpace) if d.opcode == scanEndValue { // ']', empty TAG_List - err = e.writeListHeader(TagEnd, tagName, 0, writeTag) + err = e.writeListHeader(TagEnd, 0) d.scanNext() return TagList, err } @@ -182,11 +181,6 @@ func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) default: return TagList, d.error("unknown Array type") } - if writeTag { - if err = e.writeTag(tagType, tagName); err != nil { - return - } - } if d.opcode == scanSkipSpace { d.scanWhile(scanSkipSpace) } @@ -292,7 +286,7 @@ func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) literal = d.data[start:d.readIndex()] } - if err := e.writeListHeader(tagType, tagName, count, writeTag); err != nil { + if err := e.writeListHeader(tagType, count); err != nil { return tagType, err } if _, err := e.w.Write(buf.Bytes()); err != nil { @@ -307,7 +301,7 @@ func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) if d.opcode != scanBeginList { return TagList, d.error("different TagType in List") } - elemType, err = writeListOrArray(e2, d, false, "") + elemType, err = writeListOrArray(e2, d) if err != nil { return tagType, err } @@ -329,7 +323,7 @@ func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) d.scanNext() } - if err = e.writeListHeader(elemType, tagName, count, writeTag); err != nil { + if err = e.writeListHeader(elemType, count); err != nil { return } if _, err = e.w.Write(buf.Bytes()); err != nil { @@ -368,7 +362,7 @@ func writeListOrArray(e *Encoder, d *decodeState, writeTag bool, tagName string) d.scanNext() } - if err = e.writeListHeader(TagCompound, tagName, count, writeTag); err != nil { + if err = e.writeListHeader(TagCompound, count); err != nil { return } diff --git a/nbt/snbt_decode_test.go b/nbt/snbt_decode_test.go index 059d414..62e3519 100644 --- a/nbt/snbt_decode_test.go +++ b/nbt/snbt_decode_test.go @@ -6,56 +6,69 @@ import ( "testing" ) -func TestEncoder_WriteSNBT(t *testing.T) { +type testCase struct { + snbt StringifiedMessage + tagType byte + data []byte +} + +var testCases = []testCase{ + {`10b`, TagByte, []byte{1, 0, 0, 10}}, + {`12S`, TagShort, []byte{2, 0, 0, 0, 12}}, + {`0`, TagInt, []byte{3, 0, 0, 0, 0, 0, 0}}, + {`12L`, TagLong, []byte{4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12}}, + + {`""`, TagString, []byte{8, 0, 0, 0, 0}}, + {`'""' `, TagString, []byte{8, 0, 0, 0, 2, '"', '"'}}, + {`"ab\"c\""`, TagString, []byte{8, 0, 0, 0, 5, 'a', 'b', '"', 'c', '"'}}, + {` "1\\23"`, TagString, []byte{8, 0, 0, 0, 4, '1', '\\', '2', '3'}}, + + {`{}`, TagCompound, []byte{10, 0, 0, 0}}, + {`{a:1b}`, TagCompound, []byte{10, 0, 0, 1, 0, 1, 'a', 1, 0}}, + {`{ a : 1b }`, TagCompound, []byte{10, 0, 0, 1, 0, 1, 'a', 1, 0}}, + {`{b:1,2:c}`, TagCompound, []byte{10, 0, 0, 3, 0, 1, 'b', 0, 0, 0, 1, 8, 0, 1, '2', 0, 1, 'c', 0}}, + {`{c:{d:{}}}`, TagCompound, []byte{10, 0, 0, 10, 0, 1, 'c', 10, 0, 1, 'd', 0, 0, 0}}, + {`{h:{},"i":{}}`, TagCompound, []byte{10, 0, 0, 10, 0, 1, 'h', 0, 10, 0, 1, 'i', 0, 0}}, + + {`[]`, TagList, []byte{9, 0, 0, 0, 0, 0, 0, 0}}, + {`[1b,2b,3b]`, TagList, []byte{9, 0, 0, 1, 0, 0, 0, 3, 1, 2, 3}}, + {`[ 1b , 2b , 3b ]`, TagList, []byte{9, 0, 0, 1, 0, 0, 0, 3, 1, 2, 3}}, + {`[a,"b",'c']`, TagList, []byte{9, 0, 0, 8, 0, 0, 0, 3, 0, 1, 'a', 0, 1, 'b', 0, 1, 'c'}}, + {`[{},{a:1b},{}]`, TagList, []byte{9, 0, 0, 10, 0, 0, 0, 3, 0, 1, 0, 1, 'a', 1, 0, 0}}, + {`[ { } , { a : 1b } , { } ] `, TagList, []byte{9, 0, 0, 10, 0, 0, 0, 3, 0, 1, 0, 1, 'a', 1, 0, 0}}, + {`[[],[]]`, TagList, []byte{9, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, + + {`[B; ]`, TagByteArray, []byte{7, 0, 0, 0, 0, 0, 0}}, + {`[B; 1b ,2B,3B]`, TagByteArray, []byte{7, 0, 0, 0, 0, 0, 3, 1, 2, 3}}, + {`[I;]`, TagIntArray, []byte{11, 0, 0, 0, 0, 0, 0}}, + {`[I; 1, 2 ,3]`, TagIntArray, []byte{11, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}}, + {`[L;]`, TagLongArray, []byte{12, 0, 0, 0, 0, 0, 0}}, + {`[ L; 1L,2L,3L]`, TagLongArray, []byte{12, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}}, + + {`{d:[]}`, TagCompound, []byte{10, 0, 0, 9, 0, 1, 'd', 0, 0, 0, 0, 0, 0}}, + {`{e:[]}`, TagCompound, []byte{10, 0, 0, 9, 0, 1, 'e', 0, 0, 0, 0, 0, 0}}, + {`{f:[], g:[]}`, TagCompound, []byte{10, 0, 0, 9, 0, 1, 'f', 0, 0, 0, 0, 0, 9, 0, 1, 'g', 0, 0, 0, 0, 0, 0}}, +} + +func TestStringifiedMessage_TagType(t *testing.T) { + for i := range testCases { + got := testCases[i].snbt.TagType() + if want := testCases[i].tagType; got != want { + t.Errorf("TagType assert for %s error: want % 02X, got % 02X", testCases[i].snbt, want, got) + } + } +} + +func TestEncoder_writeSNBT(t *testing.T) { var buf bytes.Buffer e := NewEncoder(&buf) - testCases := []struct { - snbt string - nbt []byte - }{ - {`10b`, []byte{1, 0, 0, 10}}, - {`12S`, []byte{2, 0, 0, 0, 12}}, - {`0`, []byte{3, 0, 0, 0, 0, 0, 0}}, - {`12L`, []byte{4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12}}, - - {`""`, []byte{8, 0, 0, 0, 0}}, - {`'""' `, []byte{8, 0, 0, 0, 2, '"', '"'}}, - {`"ab\"c\""`, []byte{8, 0, 0, 0, 5, 'a', 'b', '"', 'c', '"'}}, - {` "1\\23"`, []byte{8, 0, 0, 0, 4, '1', '\\', '2', '3'}}, - - {`{}`, []byte{10, 0, 0, 0}}, - {`{a:1b}`, []byte{10, 0, 0, 1, 0, 1, 'a', 1, 0}}, - {`{ a : 1b }`, []byte{10, 0, 0, 1, 0, 1, 'a', 1, 0}}, - {`{b:1,2:c}`, []byte{10, 0, 0, 3, 0, 1, 'b', 0, 0, 0, 1, 8, 0, 1, '2', 0, 1, 'c', 0}}, - {`{c:{d:{}}}`, []byte{10, 0, 0, 10, 0, 1, 'c', 10, 0, 1, 'd', 0, 0, 0}}, - {`{h:{},"i":{}}`, []byte{10, 0, 0, 10, 0, 1, 'h', 0, 10, 0, 1, 'i', 0, 0}}, - - {`[]`, []byte{9, 0, 0, 0, 0, 0, 0, 0}}, - {`[1b,2b,3b]`, []byte{9, 0, 0, 1, 0, 0, 0, 3, 1, 2, 3}}, - {`[ 1b , 2b , 3b ]`, []byte{9, 0, 0, 1, 0, 0, 0, 3, 1, 2, 3}}, - {`[a,"b",'c']`, []byte{9, 0, 0, 8, 0, 0, 0, 3, 0, 1, 'a', 0, 1, 'b', 0, 1, 'c'}}, - {`[{},{a:1b},{}]`, []byte{9, 0, 0, 10, 0, 0, 0, 3, 0, 1, 0, 1, 'a', 1, 0, 0}}, - {`[ { } , { a : 1b } , { } ] `, []byte{9, 0, 0, 10, 0, 0, 0, 3, 0, 1, 0, 1, 'a', 1, 0, 0}}, - {`[[],[]]`, []byte{9, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - - {`[B; ]`, []byte{7, 0, 0, 0, 0, 0, 0}}, - {`[B; 1b ,2B,3B]`, []byte{7, 0, 0, 0, 0, 0, 3, 1, 2, 3}}, - {`[I;]`, []byte{11, 0, 0, 0, 0, 0, 0}}, - {`[I; 1, 2 ,3]`, []byte{11, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3}}, - {`[L;]`, []byte{12, 0, 0, 0, 0, 0, 0}}, - {`[ L; 1L,2L,3L]`, []byte{12, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3}}, - - {`{d:[]}`, []byte{10, 0, 0, 9, 0, 1, 'd', 0, 0, 0, 0, 0, 0}}, - {`{e:[]}`, []byte{10, 0, 0, 9, 0, 1, 'e', 0, 0, 0, 0, 0, 0}}, - {`{f:[], g:[]}`, []byte{10, 0, 0, 9, 0, 1, 'f', 0, 0, 0, 0, 0, 9, 0, 1, 'g', 0, 0, 0, 0, 0, 0}}, - } for i := range testCases { buf.Reset() - if err := e.WriteSNBT(testCases[i].snbt); err != nil { + if err := e.Encode(testCases[i].snbt, ""); err != nil { t.Errorf("Convert SNBT %q error: %v", testCases[i].snbt, err) continue } - want := testCases[i].nbt + want := testCases[i].data got := buf.Bytes() if !bytes.Equal(want, got) { t.Errorf("Convert SNBT %q wrong:\nwant: % 02X\ngot: % 02X", testCases[i].snbt, want, got) @@ -67,7 +80,7 @@ func TestEncoder_WriteSNBT_bigTest(t *testing.T) { var buf bytes.Buffer e := NewEncoder(&buf) - err := e.WriteSNBT(bigTestSNBT) + err := e.Encode(StringifiedMessage(bigTestSNBT), "") if err != nil { t.Error(err) } @@ -77,7 +90,7 @@ func BenchmarkEncoder_WriteSNBT_bigTest(b *testing.B) { var buf bytes.Buffer e := NewEncoder(&buf) for i := 0; i < b.N; i++ { - err := e.WriteSNBT(bigTestSNBT) + err := e.Encode(StringifiedMessage(bigTestSNBT), "") if err != nil { b.Fatal(err) } @@ -91,20 +104,23 @@ func Test_WriteSNBT_nestingList(t *testing.T) { // Our maximum supported nesting depth is 10000. // The nesting depth of 10001 is 10000 - err := e.WriteSNBT(strings.Repeat("[", 10001) + strings.Repeat("]", 10001)) + s := strings.Repeat("[", 10001) + strings.Repeat("]", 10001) + err := e.Encode(StringifiedMessage(s), "") if err != nil { t.Error(err) } // Following code should return error instant of panic. buf.Reset() - err = e.WriteSNBT(strings.Repeat("[", 10002) + strings.Repeat("]", 10002)) + s = strings.Repeat("[", 10002) + strings.Repeat("]", 10002) + err = e.Encode(StringifiedMessage(s), "") if err == nil { t.Error("Exceeded the maximum depth of support, but no error was reported") } // Panic test buf.Reset() - err = e.WriteSNBT(strings.Repeat("[", 20000) + strings.Repeat("]", 20000)) + s = strings.Repeat("[", 20000) + strings.Repeat("]", 20000) + err = e.Encode(StringifiedMessage(s), "") if err == nil { t.Error("Exceeded the maximum depth of support, but no error was reported") }