Просмотр исходного кода

remove TLV boilerplate

remove TLV read/write logic, use Unmarshal/Marshal instead
Mike 2 лет назад
Родитель
Сommit
3933d9c370

+ 2 - 23
oscar/decode.go

@@ -15,31 +15,10 @@ func Unmarshal(v any, r io.Reader) error {
 func unmarshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, r io.Reader) error {
 	switch v.Kind() {
 	case reflect.Struct:
-		switch v.Interface().(type) {
-		case TLVRestBlock:
-			val := TLVRestBlock{}
-			if err := val.Read(r); err != nil {
+		for i := 0; i < v.NumField(); i++ {
+			if err := unmarshal(t.Field(i).Type, v.Field(i), t.Field(i).Tag, r); err != nil {
 				return err
 			}
-			v.Set(reflect.ValueOf(val))
-		case TLVLBlock:
-			val := TLVLBlock{}
-			if err := val.Read(r); err != nil {
-				return err
-			}
-			v.Set(reflect.ValueOf(val))
-		case TLVBlock:
-			val := TLVBlock{}
-			if err := val.Read(r); err != nil {
-				return err
-			}
-			v.Set(reflect.ValueOf(val))
-		default:
-			for i := 0; i < v.NumField(); i++ {
-				if err := unmarshal(t.Field(i).Type, v.Field(i), t.Field(i).Tag, r); err != nil {
-					return err
-				}
-			}
 		}
 	case reflect.String:
 		var bufLen int

+ 2 - 17
oscar/encode.go

@@ -15,25 +15,10 @@ func Marshal(v any, w io.Writer) error {
 func marshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, w io.Writer) error {
 	switch t.Kind() {
 	case reflect.Struct:
-		switch sVal := v.Interface().(type) {
-		case TLVRestBlock:
-			if err := sVal.WriteTLV(w); err != nil {
+		for i := 0; i < t.NumField(); i++ {
+			if err := marshal(t.Field(i).Type, v.Field(i), t.Field(i).Tag, w); err != nil {
 				return err
 			}
-		case TLVLBlock:
-			if err := sVal.WriteTLV(w); err != nil {
-				return err
-			}
-		case TLVBlock:
-			if err := sVal.WriteTLV(w); err != nil {
-				return err
-			}
-		default:
-			for i := 0; i < t.NumField(); i++ {
-				if err := marshal(t.Field(i).Type, v.Field(i), t.Field(i).Tag, w); err != nil {
-					return err
-				}
-			}
 		}
 	case reflect.String:
 		if l, ok := tag.Lookup("len_prefix"); ok {

+ 19 - 154
oscar/tlv.go

@@ -3,7 +3,7 @@ package oscar
 import (
 	"bytes"
 	"encoding/binary"
-	"io"
+	"fmt"
 )
 
 const (
@@ -18,86 +18,12 @@ type TLVRestBlock struct {
 	TLVList
 }
 
-// read consumes the remainder of the read buffer
-func (s *TLVRestBlock) Read(r io.Reader) error {
-	for {
-		tlv := TLV{}
-		if err := tlv.Read(r); err != nil {
-			if err == io.EOF {
-				return nil
-			}
-			return err
-		}
-		s.TLVList = append(s.TLVList, tlv)
-	}
-	return nil
-}
-
 type TLVBlock struct {
-	TLVList
-}
-
-// read consumes up to n TLVs, as specified in payload
-func (s *TLVBlock) Read(r io.Reader) error {
-	var tlvCount uint16
-	if err := binary.Read(r, binary.BigEndian, &tlvCount); err != nil {
-		return err
-	}
-	if tlvCount == 0 {
-		return nil
-	}
-	for i := uint16(0); i < tlvCount; i++ {
-		tlv := TLV{}
-		if err := tlv.Read(r); err != nil {
-			return err
-		}
-		s.TLVList = append(s.TLVList, tlv)
-	}
-	return nil
-}
-
-func (s TLVBlock) WriteTLV(w io.Writer) error {
-	if err := binary.Write(w, binary.BigEndian, uint16(len(s.TLVList))); err != nil {
-		return err
-	}
-	return s.TLVList.WriteTLV(w)
+	TLVList `count_prefix:"uint16"`
 }
 
 type TLVLBlock struct {
-	TLVList
-}
-
-// read consumes up to n bytes, as specified in the payload
-func (s *TLVLBlock) Read(r io.Reader) error {
-	var tlvLen uint16
-	if err := binary.Read(r, binary.BigEndian, &tlvLen); err != nil {
-		return err
-	}
-	p := make([]byte, tlvLen)
-	if _, err := r.Read(p); err != nil {
-		return err
-	}
-	buf := bytes.NewBuffer(p)
-	for buf.Len() > 0 {
-		tlv := TLV{}
-		if err := tlv.Read(buf); err != nil {
-			return err
-		}
-		s.TLVList = append(s.TLVList, tlv)
-	}
-	return nil
-}
-
-func (s TLVLBlock) WriteTLV(w io.Writer) error {
-	buf := &bytes.Buffer{}
-	if err := s.TLVList.WriteTLV(buf); err != nil {
-		return err
-	}
-	if err := binary.Write(w, binary.BigEndian, uint16(buf.Len())); err != nil {
-		return err
-	}
-	_, err := w.Write(buf.Bytes())
-	return err
+	TLVList `len_prefix:"uint16"`
 }
 
 type TLVList []TLV
@@ -110,33 +36,10 @@ func (s *TLVList) AddTLVList(tlvs []TLV) {
 	*s = append(*s, tlvs...)
 }
 
-func (s TLVList) WriteTLV(w io.Writer) error {
-	for _, tlv := range s {
-		if err := tlv.WriteTLV(w); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// todo explain what this does
-func (s TLVList) SerializeInPlace() error {
-	for i := range s {
-		buf := &bytes.Buffer{}
-		if err := s[i].WriteTLV(buf); err != nil {
-			return err
-		}
-		if err := s[i].Read(buf); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 func (s TLVList) GetString(tType uint16) (string, bool) {
 	for _, tlv := range s {
 		if tType == tlv.TType {
-			return string(tlv.Val.([]byte)), true
+			return string(tlv.Val), true
 		}
 	}
 	return "", false
@@ -154,7 +57,7 @@ func (s TLVList) GetTLV(tType uint16) (TLV, bool) {
 func (s TLVList) GetSlice(tType uint16) ([]byte, bool) {
 	for _, tlv := range s {
 		if tType == tlv.TType {
-			return tlv.Val.([]byte), true
+			return tlv.Val, true
 		}
 	}
 	return nil, false
@@ -163,67 +66,29 @@ func (s TLVList) GetSlice(tType uint16) ([]byte, bool) {
 func (s TLVList) GetUint32(tType uint16) (uint32, bool) {
 	for _, tlv := range s {
 		if tType == tlv.TType {
-			return binary.BigEndian.Uint32(tlv.Val.([]byte)), true
+			return binary.BigEndian.Uint32(tlv.Val), true
 		}
 	}
 	return 0, false
 }
 
-type TLV struct {
-	TType uint16
-	Val   any
-}
-
-func (t TLV) WriteTLV(w io.Writer) error {
-	if err := binary.Write(w, binary.BigEndian, t.TType); err != nil {
-		return err
+func NewTLV(ttype uint16, val any) TLV {
+	t := TLV{
+		TType: ttype,
 	}
-
-	var valLen uint16
-	val := t.Val
-
-	switch t.Val.(type) {
-	case uint8:
-		valLen = 1
-	case uint16:
-		valLen = 2
-	case uint32:
-		valLen = 4
-	case []uint16:
-		valLen = uint16(len(t.Val.([]uint16)) * 2)
-	case []byte:
-		valLen = uint16(len(t.Val.([]byte)))
-	case string:
-		valLen = uint16(len(t.Val.(string)))
-		val = []byte(t.Val.(string))
-	default:
+	if _, ok := val.([]byte); ok {
+		t.Val = val.([]byte)
+	} else {
 		buf := &bytes.Buffer{}
-		if err := Marshal(t.Val, buf); err != nil {
-			return err
+		if err := Marshal(val, buf); err != nil {
+			panic(fmt.Sprintf("unable to create TLV: %s", err.Error()))
 		}
-		valLen = uint16(buf.Len())
-		val = buf.Bytes()
-	}
-
-	if err := binary.Write(w, binary.BigEndian, valLen); err != nil {
-		return err
+		t.Val = buf.Bytes()
 	}
-
-	return binary.Write(w, binary.BigEndian, val)
+	return t
 }
 
-func (t *TLV) Read(r io.Reader) error {
-	if err := binary.Read(r, binary.BigEndian, &t.TType); err != nil {
-		return err
-	}
-	var tlvValLen uint16
-	if err := binary.Read(r, binary.BigEndian, &tlvValLen); err != nil {
-		return err
-	}
-	buf := make([]byte, tlvValLen)
-	if _, err := r.Read(buf); err != nil {
-		return err
-	}
-	t.Val = buf
-	return nil
+type TLV struct {
+	TType uint16
+	Val   []byte `len_prefix:"uint16"`
 }

+ 5 - 20
server/bucp.go

@@ -68,10 +68,7 @@ func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io
 			SubGroup:  BUCPLoginResponse,
 		}
 		snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-		snacPayloadOut.AddTLV(oscar.TLV{
-			TType: oscar.TLVErrorSubcode,
-			Val:   uint16(0x01),
-		})
+		snacPayloadOut.AddTLV(oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)))
 		return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 	}
 
@@ -135,29 +132,17 @@ func ReceiveAndSendBUCPLoginRequest(cfg Config, sm SessionManager, fm *FeedbagSt
 	}
 
 	snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-	snacPayloadOut.AddTLV(oscar.TLV{
-		TType: oscar.TLVScreenName,
-		Val:   screenName,
-	})
+	snacPayloadOut.AddTLV(oscar.NewTLV(oscar.TLVScreenName, screenName))
 
 	if loginOK {
 		sess := sm.NewSessionWithSN(newUUID().String(), screenName)
 		snacPayloadOut.AddTLVList([]oscar.TLV{
-			{
-				TType: oscar.TLVReconnectHere,
-				Val:   Address(cfg.OSCARHost, cfg.BOSPort),
-			},
-			{
-				TType: oscar.TLVAuthorizationCookie,
-				Val:   sess.ID,
-			},
+			oscar.NewTLV(oscar.TLVReconnectHere, Address(cfg.OSCARHost, cfg.BOSPort)),
+			oscar.NewTLV(oscar.TLVAuthorizationCookie, sess.ID),
 		})
 	} else {
 		snacPayloadOut.AddTLVList([]oscar.TLV{
-			{
-				TType: oscar.TLVErrorSubcode,
-				Val:   uint16(0x01),
-			},
+			oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
 		})
 	}
 

+ 18 - 65
server/bucp_test.go

@@ -39,12 +39,8 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVPasswordHash, Val: userGoodPwd.PassHash,
-						},
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: userGoodPwd.ScreenName,
-						},
+						oscar.NewTLV(oscar.TLVPasswordHash, userGoodPwd.PassHash),
+						oscar.NewTLV(oscar.TLVScreenName, userGoodPwd.ScreenName),
 					},
 				},
 			},
@@ -55,18 +51,9 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: oscar.TLVScreenName,
-							Val:   userGoodPwd.ScreenName,
-						},
-						{
-							TType: oscar.TLVReconnectHere,
-							Val:   "127.0.0.1:1234",
-						},
-						{
-							TType: oscar.TLVAuthorizationCookie,
-							Val:   uuid.UUID{1, 2, 3}.String(),
-						},
+						oscar.NewTLV(oscar.TLVScreenName, userGoodPwd.ScreenName),
+						oscar.NewTLV(oscar.TLVReconnectHere, "127.0.0.1:1234"),
+						oscar.NewTLV(oscar.TLVAuthorizationCookie, uuid.UUID{1, 2, 3}.String()),
 					},
 				},
 			},
@@ -83,12 +70,8 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVPasswordHash, Val: userBadPwd.PassHash,
-						},
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: userBadPwd.ScreenName,
-						},
+						oscar.NewTLV(oscar.TLVPasswordHash, userBadPwd.PassHash),
+						oscar.NewTLV(oscar.TLVScreenName, userBadPwd.ScreenName),
 					},
 				},
 			},
@@ -99,18 +82,9 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: oscar.TLVScreenName,
-							Val:   userBadPwd.ScreenName,
-						},
-						{
-							TType: oscar.TLVReconnectHere,
-							Val:   "127.0.0.1:1234",
-						},
-						{
-							TType: oscar.TLVAuthorizationCookie,
-							Val:   uuid.UUID{1, 2, 3}.String(),
-						},
+						oscar.NewTLV(oscar.TLVScreenName, userBadPwd.ScreenName),
+						oscar.NewTLV(oscar.TLVReconnectHere, "127.0.0.1:1234"),
+						oscar.NewTLV(oscar.TLVAuthorizationCookie, uuid.UUID{1, 2, 3}.String()),
 					},
 				},
 			},
@@ -126,12 +100,8 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x02_BUCPLoginRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVPasswordHash, Val: userBadPwd.PassHash,
-						},
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: userBadPwd.ScreenName,
-						},
+						oscar.NewTLV(oscar.TLVPasswordHash, userBadPwd.PassHash),
+						oscar.NewTLV(oscar.TLVScreenName, userBadPwd.ScreenName),
 					},
 				},
 			},
@@ -142,14 +112,8 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: oscar.TLVScreenName,
-							Val:   userBadPwd.ScreenName,
-						},
-						{
-							TType: oscar.TLVErrorSubcode,
-							Val:   uint16(0x01),
-						},
+						oscar.NewTLV(oscar.TLVScreenName, userBadPwd.ScreenName),
+						oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
 					},
 				},
 			},
@@ -196,7 +160,6 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 			//
 			// verify output SNAC body
 			//
-			assert.NoError(t, tc.expectSNACBody.SerializeInPlace())
 			actual := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
 			assert.NoError(t, oscar.Unmarshal(&actual, output))
 			assert.Equal(t, tc.expectSNACBody, actual)
@@ -229,9 +192,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: "sn_user_a",
-						},
+						oscar.NewTLV(oscar.TLVScreenName, "sn_user_a"),
 					},
 				},
 			},
@@ -258,9 +219,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: "sn_user_b",
-						},
+						oscar.NewTLV(oscar.TLVScreenName, "sn_user_b"),
 					},
 				},
 			},
@@ -286,9 +245,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						oscar.TLV{
-							TType: oscar.TLVScreenName, Val: "sn_user_b",
-						},
+						oscar.NewTLV(oscar.TLVScreenName, "sn_user_b"),
 					},
 				},
 			},
@@ -299,10 +256,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: oscar.TLVErrorSubcode,
-							Val:   uint16(0x01),
-						},
+						oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
 					},
 				},
 			},
@@ -354,7 +308,6 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 				assert.NoError(t, oscar.Unmarshal(&actual, output))
 				assert.Equal(t, v, actual)
 			case oscar.SNAC_0x17_0x03_BUCPLoginResponse:
-				assert.NoError(t, v.SerializeInPlace())
 				actual := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
 				assert.NoError(t, oscar.Unmarshal(&actual, output))
 				assert.Equal(t, v, actual)

+ 4 - 16
server/buddy.go

@@ -46,22 +46,10 @@ func (s BuddyService) RightsQueryHandler() XMessage {
 		snacOut: oscar.SNAC_0x03_0x03_BuddyRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x01,
-						Val:   uint16(100),
-					},
-					{
-						TType: 0x02,
-						Val:   uint16(100),
-					},
-					{
-						TType: 0x03,
-						Val:   uint16(100),
-					},
-					{
-						TType: 0x04,
-						Val:   uint16(100),
-					},
+					oscar.NewTLV(0x01, uint16(100)),
+					oscar.NewTLV(0x02, uint16(100)),
+					oscar.NewTLV(0x03, uint16(100)),
+					oscar.NewTLV(0x04, uint16(100)),
 				},
 			},
 		},

+ 4 - 5
server/chat.go

@@ -52,16 +52,15 @@ func (s ChatService) ChannelMsgToHostHandler(sess *Session, sm SessionManager, s
 			TLVList: snacPayloadIn.TLVList,
 		},
 	}
-	snacPayloadOut.AddTLV(oscar.TLV{
-		TType: oscar.ChatTLVSenderInformation,
-		Val: oscar.TLVUserInfo{
+	snacPayloadOut.AddTLV(
+		oscar.NewTLV(oscar.ChatTLVSenderInformation, oscar.TLVUserInfo{
 			ScreenName:   sess.ScreenName,
 			WarningLevel: sess.GetWarning(),
 			TLVBlock: oscar.TLVBlock{
 				TLVList: sess.GetUserInfo(),
 			},
-		},
-	})
+		}),
+	)
 
 	// send message to all the participants except sender
 	sm.BroadcastExcept(sess, XMessage{

+ 30 - 66
server/chat_nav.go

@@ -71,52 +71,22 @@ func (s ChatNavService) RequestChatRightsHandler() XMessage {
 		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x02,
-						Val:   uint8(10),
-					},
-					{
-						TType: 0x03,
-						Val: oscar.SNAC_0x0D_0x09_TLVExchangeInfo{
-							Identifier: 4,
-							TLVBlock: oscar.TLVBlock{
-								TLVList: oscar.TLVList{
-									{
-										TType: 0x0002,
-										Val:   uint16(0x0010),
-									},
-									{
-										TType: 0x00c9,
-										Val:   uint16(15),
-									},
-									{
-										TType: 0x00d3,
-										Val:   "default Exchange",
-									},
-									{
-										TType: 0x00d5,
-										Val:   uint8(2),
-									},
-									{
-										TType: 0xd6,
-										Val:   "us-ascii",
-									},
-									{
-										TType: 0xd7,
-										Val:   "en",
-									},
-									{
-										TType: 0xd8,
-										Val:   "us-ascii",
-									},
-									{
-										TType: 0xd9,
-										Val:   "en",
-									},
-								},
+					oscar.NewTLV(0x02, uint8(10)),
+					oscar.NewTLV(0x03, oscar.SNAC_0x0D_0x09_TLVExchangeInfo{
+						Identifier: 4,
+						TLVBlock: oscar.TLVBlock{
+							TLVList: oscar.TLVList{
+								oscar.NewTLV(0x0002, uint16(0x0010)),
+								oscar.NewTLV(0x00c9, uint16(15)),
+								oscar.NewTLV(0x00d3, "default Exchange"),
+								oscar.NewTLV(0x00d5, uint8(2)),
+								oscar.NewTLV(0xd6, "us-ascii"),
+								oscar.NewTLV(0xd7, "en"),
+								oscar.NewTLV(0xd8, "us-ascii"),
+								oscar.NewTLV(0xd9, "en"),
 							},
 						},
-					},
+					}),
 				},
 			},
 		},
@@ -157,18 +127,15 @@ func (s ChatNavService) CreateRoomHandler(sess *Session, cr *ChatRegistry, newCh
 		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: oscar.ChatNavTLVRoomInfo,
-						Val: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
-							Exchange:       snacPayloadIn.Exchange,
-							Cookie:         room.Cookie,
-							InstanceNumber: snacPayloadIn.InstanceNumber,
-							DetailLevel:    snacPayloadIn.DetailLevel,
-							TLVBlock: oscar.TLVBlock{
-								TLVList: room.TLVList(),
-							},
+					oscar.NewTLV(oscar.ChatNavTLVRoomInfo, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
+						Exchange:       snacPayloadIn.Exchange,
+						Cookie:         room.Cookie,
+						InstanceNumber: snacPayloadIn.InstanceNumber,
+						DetailLevel:    snacPayloadIn.DetailLevel,
+						TLVBlock: oscar.TLVBlock{
+							TLVList: room.TLVList(),
 						},
-					},
+					}),
 				},
 			},
 		},
@@ -189,18 +156,15 @@ func (s ChatNavService) RequestRoomInfoHandler(cr *ChatRegistry, snacPayloadIn o
 		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x04,
-						Val: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
-							Exchange:       4,
-							Cookie:         room.Cookie,
-							InstanceNumber: 100,
-							DetailLevel:    2,
-							TLVBlock: oscar.TLVBlock{
-								TLVList: room.TLVList(),
-							},
+					oscar.NewTLV(0x04, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
+						Exchange:       4,
+						Cookie:         room.Cookie,
+						InstanceNumber: 100,
+						DetailLevel:    2,
+						TLVBlock: oscar.TLVBlock{
+							TLVList: room.TLVList(),
 						},
-					},
+					}),
 				},
 			},
 		},

+ 8 - 21
server/chat_nav_test.go

@@ -43,15 +43,11 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 		DetailLevel:    3,
 		TLVBlock: oscar.TLVBlock{
 			TLVList: oscar.TLVList{
-				{
-					TType: oscar.ChatTLVRoomName,
-					Val:   "the-chat-room-name",
-				},
+				oscar.NewTLV(oscar.ChatTLVRoomName, "the-chat-room-name"),
 			},
 		},
 	}
 	svc := ChatNavService{}
-	assert.NoError(t, inputSNAC.SerializeInPlace())
 	outputSNAC, err := svc.CreateRoomHandler(userSess, cr, crf, inputSNAC)
 	assert.NoError(t, err)
 
@@ -82,9 +78,9 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: oscar.ChatNavTLVRoomInfo,
-						Val: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
+					oscar.NewTLV(
+						oscar.ChatNavTLVRoomInfo,
+						oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
 							Exchange:       chatRoom.Exchange,
 							Cookie:         chatRoom.Cookie,
 							InstanceNumber: chatRoom.InstanceNumber,
@@ -93,7 +89,7 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 								TLVList: chatRoom.TLVList(),
 							},
 						},
-					},
+					),
 				},
 			},
 		},
@@ -132,10 +128,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x02,
-								Val:   uint8(10),
-							},
+							oscar.NewTLV(0x02, uint8(10)),
 						},
 					},
 				},
@@ -160,10 +153,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x02,
-								Val:   uint8(10),
-							},
+							oscar.NewTLV(0x02, uint8(10)),
 						},
 					},
 				},
@@ -188,10 +178,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x02,
-								Val:   uint8(10),
-							},
+							oscar.NewTLV(0x02, uint8(10)),
 						},
 					},
 				},

+ 13 - 37
server/chat_test.go

@@ -53,20 +53,10 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.ChatTLVPublicWhisperFlag,
-								Val:   []byte{},
-							},
-							{
-								TType: oscar.ChatTLVEnableReflectionFlag,
-								Val:   []byte{},
-							},
-							{
-								TType: oscar.ChatTLVSenderInformation,
-								Val: newTestSession(Session{
-									ScreenName: "user_sending_chat_msg",
-								}).GetTLVUserInfo(),
-							},
+							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
+							oscar.NewTLV(oscar.ChatTLVEnableReflectionFlag, []byte{}),
+							oscar.NewTLV(oscar.ChatTLVSenderInformation,
+								newTestSession(Session{ScreenName: "user_sending_chat_msg"}).GetTLVUserInfo()),
 						},
 					},
 				},
@@ -81,20 +71,11 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.ChatTLVPublicWhisperFlag,
-								Val:   []byte{},
-							},
-							{
-								TType: oscar.ChatTLVEnableReflectionFlag,
-								Val:   []byte{},
-							},
-							{
-								TType: oscar.ChatTLVSenderInformation,
-								Val: newTestSession(Session{
-									ScreenName: "user_sending_chat_msg",
-								}).GetTLVUserInfo(),
-							},
+							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
+							oscar.NewTLV(oscar.ChatTLVEnableReflectionFlag, []byte{}),
+							oscar.NewTLV(oscar.ChatTLVSenderInformation, newTestSession(Session{
+								ScreenName: "user_sending_chat_msg",
+							}).GetTLVUserInfo()),
 						},
 					},
 				},
@@ -127,16 +108,11 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.ChatTLVPublicWhisperFlag,
-								Val:   []byte{},
-							},
-							{
-								TType: oscar.ChatTLVSenderInformation,
-								Val: newTestSession(Session{
+							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
+							oscar.NewTLV(oscar.ChatTLVSenderInformation,
+								newTestSession(Session{
 									ScreenName: "user_sending_chat_msg",
-								}).GetTLVUserInfo(),
-							},
+								}).GetTLVUserInfo()),
 						},
 					},
 				},

+ 33 - 66
server/feedbag.go

@@ -111,72 +111,39 @@ func (s FeedbagService) RightsQueryHandler() XMessage {
 		snacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x03,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x04,
-						Val: []uint16{
-							0x3D,
-							0x3D,
-							0x64,
-							0x64,
-							0x01,
-							0x01,
-							0x32,
-							0x00,
-							0x00,
-							0x03,
-							0x00,
-							0x00,
-							0x00,
-							0x80,
-							0xFF,
-							0x14,
-							0xC8,
-							0x01,
-							0x00,
-							0x01,
-							0x00,
-						},
-					},
-					{
-						TType: 0x05,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x06,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x07,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x08,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x09,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x0A,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x0C,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x0D,
-						Val:   uint16(200),
-					},
-					{
-						TType: 0x0E,
-						Val:   uint16(100),
-					},
+					oscar.NewTLV(0x03, uint16(200)),
+					oscar.NewTLV(0x04, []uint16{
+						0x3D,
+						0x3D,
+						0x64,
+						0x64,
+						0x01,
+						0x01,
+						0x32,
+						0x00,
+						0x00,
+						0x03,
+						0x00,
+						0x00,
+						0x00,
+						0x80,
+						0xFF,
+						0x14,
+						0xC8,
+						0x01,
+						0x00,
+						0x01,
+						0x00,
+					}),
+					oscar.NewTLV(0x05, uint16(200)),
+					oscar.NewTLV(0x06, uint16(200)),
+					oscar.NewTLV(0x07, uint16(200)),
+					oscar.NewTLV(0x08, uint16(200)),
+					oscar.NewTLV(0x09, uint16(200)),
+					oscar.NewTLV(0x0A, uint16(200)),
+					oscar.NewTLV(0x0C, uint16(200)),
+					oscar.NewTLV(0x0D, uint16(200)),
+					oscar.NewTLV(0x0E, uint16(100)),
 				},
 			},
 		},

+ 2 - 2
server/feedbag_store.go

@@ -191,7 +191,7 @@ func (f *FeedbagStore) Retrieve(screenName string) ([]oscar.FeedbagItem, error)
 		if err := rows.Scan(&item.GroupID, &item.ItemID, &item.ClassID, &item.Name, &attrs); err != nil {
 			return nil, err
 		}
-		err = item.TLVLBlock.Read(bytes.NewBuffer(attrs))
+		err = oscar.Unmarshal(&item.TLVLBlock, bytes.NewBuffer(attrs))
 		if err != nil {
 			return items, err
 		}
@@ -223,7 +223,7 @@ func (f *FeedbagStore) Upsert(screenName string, items []oscar.FeedbagItem) erro
 	for _, item := range items {
 
 		buf := &bytes.Buffer{}
-		if err := item.TLVLBlock.WriteTLV(buf); err != nil {
+		if err := oscar.Marshal(item.TLVLBlock, buf); err != nil {
 			return err
 		}
 

+ 1 - 4
server/feedbag_store_test.go

@@ -79,10 +79,7 @@ func TestFeedbagDelete(t *testing.T) {
 			Name:    "spimmer1234",
 			TLVLBlock: oscar.TLVLBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x01,
-						Val:   uint16(1000),
-					},
+					oscar.NewTLV(0x01, uint16(1000)),
 				},
 			},
 		},

+ 9 - 36
server/locate.go

@@ -80,26 +80,11 @@ func (s LocateService) RightsQueryHandler() XMessage {
 		snacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: 0x01,
-						Val:   uint16(1000),
-					},
-					{
-						TType: 0x02,
-						Val:   uint16(1000),
-					},
-					{
-						TType: 0x03,
-						Val:   uint16(1000),
-					},
-					{
-						TType: 0x04,
-						Val:   uint16(1000),
-					},
-					{
-						TType: 0x05,
-						Val:   uint16(1000),
-					},
+					oscar.NewTLV(0x01, uint16(1000)),
+					oscar.NewTLV(0x02, uint16(1000)),
+					oscar.NewTLV(0x03, uint16(1000)),
+					oscar.NewTLV(0x04, uint16(1000)),
+					oscar.NewTLV(0x05, uint16(1000)),
 				},
 			},
 		},
@@ -165,27 +150,15 @@ func (s LocateService) UserInfoQuery2Handler(sess *Session, sm SessionManager, f
 			return XMessage{}, err
 		}
 		list.AddTLVList([]oscar.TLV{
-			{
-				TType: oscar.LocateTLVTagsInfoSigMime,
-				Val:   `text/aolrtf; charset="us-ascii"`,
-			},
-			{
-				TType: oscar.LocateTLVTagsInfoSigData,
-				Val:   profile,
-			},
+			oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
+			oscar.NewTLV(oscar.LocateTLVTagsInfoSigData, profile),
 		})
 	}
 
 	if snacPayloadIn.RequestAwayMessage() {
 		list.AddTLVList([]oscar.TLV{
-			{
-				TType: oscar.LocateTLVTagsInfoUnavailableMime,
-				Val:   `text/aolrtf; charset="us-ascii"`,
-			},
-			{
-				TType: oscar.LocateTLVTagsInfoUnavailableData,
-				Val:   buddySess.GetAwayMessage(),
-			},
+			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableMime, `text/aolrtf; charset="us-ascii"`),
+			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableData, buddySess.GetAwayMessage()),
 		})
 	}
 

+ 7 - 28
server/locate_test.go

@@ -116,14 +116,8 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					}).GetTLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.LocateTLVTagsInfoSigMime,
-								Val:   `text/aolrtf; charset="us-ascii"`,
-							},
-							{
-								TType: oscar.LocateTLVTagsInfoSigData,
-								Val:   "this is my profile!",
-							},
+							oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
+							oscar.NewTLV(oscar.LocateTLVTagsInfoSigData, "this is my profile!"),
 						},
 					},
 				},
@@ -171,14 +165,8 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					}).GetTLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.LocateTLVTagsInfoSigMime,
-								Val:   `text/aolrtf; charset="us-ascii"`,
-							},
-							{
-								TType: oscar.LocateTLVTagsInfoSigData,
-								Val:   "this is my profile!",
-							},
+							oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
+							oscar.NewTLV(oscar.LocateTLVTagsInfoSigData, "this is my profile!"),
 						},
 					},
 				},
@@ -218,14 +206,8 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					}).GetTLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.LocateTLVTagsInfoUnavailableMime,
-								Val:   `text/aolrtf; charset="us-ascii"`,
-							},
-							{
-								TType: oscar.LocateTLVTagsInfoUnavailableData,
-								Val:   "this is my away message!",
-							},
+							oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableMime, `text/aolrtf; charset="us-ascii"`),
+							oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableData, "this is my away message!"),
 						},
 					},
 				},
@@ -339,10 +321,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 				snacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x01,
-								Val:   uint16(1000),
-							},
+							oscar.NewTLV(0x01, uint16(1000)),
 						},
 					},
 				},

+ 8 - 23
server/oservice.go

@@ -296,29 +296,14 @@ func (s OServiceService) ServiceRequestHandler(cfg Config, cr *ChatRegistry, ses
 		snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
-					{
-						TType: oscar.OServiceTLVTagsReconnectHere,
-						Val:   Address(cfg.OSCARHost, cfg.ChatPort),
-					},
-					{
-						TType: oscar.OServiceTLVTagsLoginCookie,
-						Val: ChatCookie{
-							Cookie: []byte(room.Cookie),
-							SessID: sess.ID,
-						},
-					},
-					{
-						TType: oscar.OServiceTLVTagsGroupID,
-						Val:   oscar.CHAT,
-					},
-					{
-						TType: oscar.OServiceTLVTagsSSLCertName,
-						Val:   "",
-					},
-					{
-						TType: oscar.OServiceTLVTagsSSLState,
-						Val:   uint8(0x00),
-					},
+					oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, Address(cfg.OSCARHost, cfg.ChatPort)),
+					oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, ChatCookie{
+						Cookie: []byte(room.Cookie),
+						SessID: sess.ID,
+					}),
+					oscar.NewTLV(oscar.OServiceTLVTagsGroupID, oscar.CHAT),
+					oscar.NewTLV(oscar.OServiceTLVTagsSSLCertName, ""),
+					oscar.NewTLV(oscar.OServiceTLVTagsSSLState, uint8(0x00)),
 				},
 			},
 		},

+ 21 - 52
server/oservice_test.go

@@ -61,14 +61,11 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				FoodGroup: oscar.CHAT,
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: 0x01,
-							Val: oscar.SNAC_0x01_0x04_TLVRoomInfo{
-								Exchange:       8,
-								Cookie:         []byte("the-chat-cookie"),
-								InstanceNumber: 16,
-							},
-						},
+						oscar.NewTLV(0x01, oscar.SNAC_0x01_0x04_TLVRoomInfo{
+							Exchange:       8,
+							Cookie:         []byte("the-chat-cookie"),
+							InstanceNumber: 16,
+						}),
 					},
 				},
 			},
@@ -80,29 +77,14 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: oscar.OServiceTLVTagsReconnectHere,
-								Val:   "127.0.0.1:1234",
-							},
-							{
-								TType: oscar.OServiceTLVTagsLoginCookie,
-								Val: ChatCookie{
-									Cookie: []byte("the-chat-cookie"),
-									SessID: "user-sess-id",
-								},
-							},
-							{
-								TType: oscar.OServiceTLVTagsGroupID,
-								Val:   oscar.CHAT,
-							},
-							{
-								TType: oscar.OServiceTLVTagsSSLCertName,
-								Val:   "",
-							},
-							{
-								TType: oscar.OServiceTLVTagsSSLState,
-								Val:   uint8(0x00),
-							},
+							oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
+							oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, ChatCookie{
+								Cookie: []byte("the-chat-cookie"),
+								SessID: "user-sess-id",
+							}),
+							oscar.NewTLV(oscar.OServiceTLVTagsGroupID, oscar.CHAT),
+							oscar.NewTLV(oscar.OServiceTLVTagsSSLCertName, ""),
+							oscar.NewTLV(oscar.OServiceTLVTagsSSLState, uint8(0x00)),
 						},
 					},
 				},
@@ -123,14 +105,11 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				FoodGroup: oscar.CHAT,
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{
-						{
-							TType: 0x01,
-							Val: oscar.SNAC_0x01_0x04_TLVRoomInfo{
-								Exchange:       8,
-								Cookie:         []byte("the-chat-cookie"),
-								InstanceNumber: 16,
-							},
-						},
+						oscar.NewTLV(0x01, oscar.SNAC_0x01_0x04_TLVRoomInfo{
+							Exchange:       8,
+							Cookie:         []byte("the-chat-cookie"),
+							InstanceNumber: 16,
+						}),
 					},
 				},
 			},
@@ -156,7 +135,6 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 			//
 			// send input SNAC
 			//
-			assert.NoError(t, tc.inputSNAC.SerializeInPlace())
 			svc := OServiceService{}
 			outputSNAC, err := svc.ServiceRequestHandler(tc.cfg, cr, tc.userSession, tc.inputSNAC)
 			assert.ErrorIs(t, err, tc.expectErr)
@@ -225,10 +203,7 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x01,
-								Val:   uint16(1000),
-							},
+							oscar.NewTLV(0x01, uint16(1000)),
 						},
 					},
 				},
@@ -273,10 +248,7 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 				snacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
-							},
+							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
 						},
 					},
 				},
@@ -352,10 +324,7 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 				snacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
-							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
-							},
+							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
 						},
 					},
 				},

+ 23 - 59
server/session.go

@@ -102,56 +102,41 @@ func (s *Session) GetTLVUserInfo() oscar.TLVUserInfo {
 	}
 }
 
-func (s *Session) GetUserInfo() []oscar.TLV {
+func (s *Session) GetUserInfo() oscar.TLVList {
 	s.Mutex.RLock()
 	defer s.Mutex.RUnlock()
 
 	// sign-in timestamp
-	tlvs := []oscar.TLV{
-		{
-			TType: 0x03,
-			Val:   uint32(s.SignonTime.Unix()),
-		},
-	}
+	tlvs := oscar.TLVList{}
+
+	tlvs.AddTLV(oscar.NewTLV(0x03, uint32(s.SignonTime.Unix())))
 
 	// away message status
-	userFlags := oscar.TLV{
-		TType: 0x01,
-		Val:   uint16(0x0010), // AIM client
-	}
 	if s.AwayMessage != "" {
-		userFlags.Val = userFlags.Val.(uint16) | uint16(0x0020)
+		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)|uint16(0x0020)))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)))
 	}
-	tlvs = append(tlvs, userFlags)
 
 	// invisibility status
-	status := oscar.TLV{
-		TType: 0x06,
-		Val:   uint16(0x0000),
-	}
 	if s.invisible {
-		status.Val = status.Val.(uint16) | uint16(0x0100)
+		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0100)))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0000)))
 	}
-	tlvs = append(tlvs, status)
 
 	// idle status
-	idle := oscar.TLV{
-		TType: 0x04,
-		Val:   uint16(0),
-	}
 	if s.idle {
-		idle.Val = uint16(time.Now().Sub(s.idleTime).Seconds())
+		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(time.Now().Sub(s.idleTime).Seconds())))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(0)))
 	}
-	tlvs = append(tlvs, idle)
 
 	// capabilities
-	caps := oscar.TLV{
-		TType: 0x0D,
-		Val:   []byte{},
-	}
+	var caps []byte
 	// chat capability
-	caps.Val = append(caps.Val.([]byte), CapChat...)
-	tlvs = append(tlvs, caps)
+	caps = append(caps, CapChat...)
+	tlvs.AddTLV(oscar.NewTLV(0x0D, caps))
 
 	return tlvs
 }
@@ -333,34 +318,13 @@ type ChatRoom struct {
 
 func (c ChatRoom) TLVList() []oscar.TLV {
 	return []oscar.TLV{
-		{
-			TType: 0x00c9,
-			Val:   uint16(15),
-		},
-		{
-			TType: 0x00ca,
-			Val:   uint32(c.CreateTime.Unix()),
-		},
-		{
-			TType: 0x00d1,
-			Val:   uint16(1024),
-		},
-		{
-			TType: 0x00d2,
-			Val:   uint16(100),
-		},
-		{
-			TType: 0x00d5,
-			Val:   uint8(2),
-		},
-		{
-			TType: 0x006a,
-			Val:   c.Name,
-		},
-		{
-			TType: 0x00d3,
-			Val:   c.Name,
-		},
+		oscar.NewTLV(0x00c9, uint16(15)),
+		oscar.NewTLV(0x00ca, uint32(c.CreateTime.Unix())),
+		oscar.NewTLV(0x00d1, uint16(1024)),
+		oscar.NewTLV(0x00d2, uint16(100)),
+		oscar.NewTLV(0x00d5, uint8(2)),
+		oscar.NewTLV(0x006a, c.Name),
+		oscar.NewTLV(0x00d3, c.Name),
 	}
 }