Mike 2 лет назад
Родитель
Сommit
51d850f2ca

+ 7 - 7
handler/auth.go

@@ -83,7 +83,7 @@ func (s AuthService) SignoutChat(ctx context.Context, sess *state.Session, chatI
 // DisableAuth is true, a stub auth key is generated and a successful challenge
 // response is returned.
 func (s AuthService) BUCPChallengeRequestHandler(bodyIn oscar.SNAC_0x17_0x06_BUCPChallengeRequest, newUUIDFn func() uuid.UUID) (oscar.SNACMessage, error) {
-	screenName, exists := bodyIn.GetString(oscar.TLVScreenName)
+	screenName, exists := bodyIn.String(oscar.TLVScreenName)
 	if !exists {
 		return oscar.SNACMessage{}, errors.New("screen name doesn't exist in tlv")
 	}
@@ -107,7 +107,7 @@ func (s AuthService) BUCPChallengeRequestHandler(bodyIn oscar.SNAC_0x17_0x06_BUC
 			SubGroup:  oscar.BUCPLoginResponse,
 		}
 		snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-		snacPayloadOut.AddTLV(oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)))
+		snacPayloadOut.Append(oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)))
 		return oscar.SNACMessage{
 			Frame: snacFrameOut,
 			Body:  snacPayloadOut,
@@ -136,11 +136,11 @@ func (s AuthService) BUCPChallengeRequestHandler(bodyIn oscar.SNAC_0x17_0x06_BUC
 // (oscar.TLVAuthorizationCookie). Else, an error code is set
 // (oscar.TLVErrorSubcode).
 func (s AuthService) BUCPLoginRequestHandler(bodyIn oscar.SNAC_0x17_0x02_BUCPLoginRequest, newUUIDFn func() uuid.UUID, newUserFn func(screenName string) (state.User, error)) (oscar.SNACMessage, error) {
-	screenName, found := bodyIn.GetString(oscar.TLVScreenName)
+	screenName, found := bodyIn.String(oscar.TLVScreenName)
 	if !found {
 		return oscar.SNACMessage{}, errors.New("screen name doesn't exist in tlv")
 	}
-	md5Hash, found := bodyIn.GetSlice(oscar.TLVPasswordHash)
+	md5Hash, found := bodyIn.Slice(oscar.TLVPasswordHash)
 	if !found {
 		return oscar.SNACMessage{}, errors.New("password hash doesn't exist in tlv")
 	}
@@ -167,16 +167,16 @@ func (s AuthService) BUCPLoginRequestHandler(bodyIn oscar.SNAC_0x17_0x02_BUCPLog
 	}
 
 	snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-	snacPayloadOut.AddTLV(oscar.NewTLV(oscar.TLVScreenName, screenName))
+	snacPayloadOut.Append(oscar.NewTLV(oscar.TLVScreenName, screenName))
 
 	if loginOK {
 		sess := s.sessionManager.AddSession(newUUIDFn().String(), screenName)
-		snacPayloadOut.AddTLVList([]oscar.TLV{
+		snacPayloadOut.AppendList([]oscar.TLV{
 			oscar.NewTLV(oscar.TLVReconnectHere, server.Address(s.config.OSCARHost, s.config.BOSPort)),
 			oscar.NewTLV(oscar.TLVAuthorizationCookie, sess.ID()),
 		})
 	} else {
-		snacPayloadOut.AddTLVList([]oscar.TLV{
+		snacPayloadOut.AppendList([]oscar.TLV{
 			oscar.NewTLV(oscar.TLVErrorSubcode, uint16(0x01)),
 		})
 	}

+ 2 - 2
handler/chat.go

@@ -29,7 +29,7 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Se
 			TLVList: inBody.TLVList,
 		},
 	}
-	bodyOut.AddTLV(oscar.NewTLV(oscar.ChatTLVSenderInformation, sess.TLVUserInfo()))
+	bodyOut.Append(oscar.NewTLV(oscar.ChatTLVSenderInformation, sess.TLVUserInfo()))
 
 	_, chatSessMgr, err := s.chatRegistry.Retrieve(chatID)
 	if err != nil {
@@ -43,7 +43,7 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Se
 	})
 
 	var ret *oscar.SNACMessage
-	if _, ackMsg := inBody.GetTLV(oscar.ChatTLVEnableReflectionFlag); ackMsg {
+	if _, ackMsg := inBody.Slice(oscar.ChatTLVEnableReflectionFlag); ackMsg {
 		// reflect the message back to the sender
 		ret = &oscar.SNACMessage{
 			Frame: frameOut,

+ 1 - 1
handler/chat_nav.go

@@ -66,7 +66,7 @@ func (s ChatNavService) RequestChatRightsHandler(_ context.Context, inFrame osca
 // participant. It returns SNAC oscar.ChatNavNavInfo, which contains metadata
 // for the chat room.
 func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.SNACMessage, error) {
-	name, hasName := inBody.GetString(oscar.ChatTLVRoomName)
+	name, hasName := inBody.String(oscar.ChatTLVRoomName)
 	if !hasName {
 		return oscar.SNACMessage{}, errors.New("unable to find chat name")
 	}

+ 6 - 6
handler/chat_test.go

@@ -36,12 +36,12 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ChatTLVPublicWhisperFlag,
-								Val:   []byte{},
+								Tag:   oscar.ChatTLVPublicWhisperFlag,
+								Value: []byte{},
 							},
 							{
-								TType: oscar.ChatTLVEnableReflectionFlag,
-								Val:   []byte{},
+								Tag:   oscar.ChatTLVEnableReflectionFlag,
+								Value: []byte{},
 							},
 						},
 					},
@@ -97,8 +97,8 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ChatTLVPublicWhisperFlag,
-								Val:   []byte{},
+								Tag:   oscar.ChatTLVPublicWhisperFlag,
+								Value: []byte{},
 							},
 						},
 					},

+ 4 - 4
handler/icbm.go

@@ -86,8 +86,8 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Se
 		TLVRestBlock: oscar.TLVRestBlock{
 			TLVList: oscar.TLVList{
 				{
-					TType: 0x0B,
-					Val:   []byte{},
+					Tag:   0x0B,
+					Value: []byte{},
 				},
 			},
 		},
@@ -95,7 +95,7 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Se
 	// copy over TLVs from sender SNAC to recipient SNAC verbatim. this
 	// includes ICBMTLVTagRequestHostAck, which is ignored by the client, as
 	// far as I can tell.
-	clientIM.AddTLVList(inBody.TLVRestBlock.TLVList)
+	clientIM.AppendList(inBody.TLVRestBlock.TLVList)
 
 	s.messageRelayer.RelayToScreenName(ctx, recipSess.ScreenName(), oscar.SNACMessage{
 		Frame: oscar.SNACFrame{
@@ -105,7 +105,7 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Se
 		Body: clientIM,
 	})
 
-	if _, requestedConfirmation := inBody.TLVRestBlock.GetSlice(oscar.ICBMTLVTagRequestHostAck); !requestedConfirmation {
+	if _, requestedConfirmation := inBody.TLVRestBlock.Slice(oscar.ICBMTLVTagRequestHostAck); !requestedConfirmation {
 		// don't ack message
 		return nil, nil
 	}

+ 14 - 14
handler/icbm_test.go

@@ -43,8 +43,8 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagRequestHostAck,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagRequestHostAck,
+								Value: []byte{},
 							},
 						},
 					},
@@ -63,12 +63,12 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagsWantEvents,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagsWantEvents,
+								Value: []byte{},
 							},
 							{
-								TType: oscar.ICBMTLVTagRequestHostAck,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagRequestHostAck,
+								Value: []byte{},
 							},
 						},
 					},
@@ -114,8 +114,8 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagsWantEvents,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagsWantEvents,
+								Value: []byte{},
 							},
 						},
 					},
@@ -137,8 +137,8 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagRequestHostAck,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagRequestHostAck,
+								Value: []byte{},
 							},
 						},
 					},
@@ -169,8 +169,8 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagRequestHostAck,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagRequestHostAck,
+								Value: []byte{},
 							},
 						},
 					},
@@ -201,8 +201,8 @@ func TestSendAndReceiveChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: oscar.ICBMTLVTagRequestHostAck,
-								Val:   []byte{},
+								Tag:   oscar.ICBMTLVTagRequestHostAck,
+								Value: []byte{},
 							},
 						},
 					},

+ 4 - 4
handler/locate.go

@@ -51,14 +51,14 @@ func (s LocateService) RightsQueryHandler(_ context.Context, inFrame oscar.SNACF
 // SetInfoHandler sets the user's profile or away message.
 func (s LocateService) SetInfoHandler(ctx context.Context, sess *state.Session, inBody oscar.SNAC_0x02_0x04_LocateSetInfo) error {
 	// update profile
-	if profile, hasProfile := inBody.GetString(oscar.LocateTLVTagsInfoSigData); hasProfile {
+	if profile, hasProfile := inBody.String(oscar.LocateTLVTagsInfoSigData); hasProfile {
 		if err := s.profileManager.SetProfile(sess.ScreenName(), profile); err != nil {
 			return err
 		}
 	}
 
 	// broadcast away message change to buddies
-	if awayMsg, hasAwayMsg := inBody.GetString(oscar.LocateTLVTagsInfoUnavailableData); hasAwayMsg {
+	if awayMsg, hasAwayMsg := inBody.String(oscar.LocateTLVTagsInfoUnavailableData); hasAwayMsg {
 		sess.SetAwayMessage(awayMsg)
 		if err := broadcastArrival(ctx, sess, s.sessionManager, s.feedbagManager); err != nil {
 			return err
@@ -110,14 +110,14 @@ func (s LocateService) UserInfoQuery2Handler(ctx context.Context, sess *state.Se
 		if err != nil {
 			return oscar.SNACMessage{}, err
 		}
-		list.AddTLVList([]oscar.TLV{
+		list.AppendList([]oscar.TLV{
 			oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
 			oscar.NewTLV(oscar.LocateTLVTagsInfoSigData, profile),
 		})
 	}
 
 	if inBody.RequestAwayMessage() {
-		list.AddTLVList([]oscar.TLV{
+		list.AppendList([]oscar.TLV{
 			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableMime, `text/aolrtf; charset="us-ascii"`),
 			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableData, buddySess.AwayMessage()),
 		})

+ 1 - 1
handler/locate_test.go

@@ -471,7 +471,7 @@ func TestLocateService_SetInfoHandler(t *testing.T) {
 					Return(params.users, nil)
 			}
 			profileManager := newMockProfileManager(t)
-			if msg, hasProf := tt.inBody.GetString(oscar.LocateTLVTagsInfoSigData); hasProf {
+			if msg, hasProf := tt.inBody.String(oscar.LocateTLVTagsInfoSigData); hasProf {
 				profileManager.EXPECT().
 					UpsertProfile(tt.userSession.ScreenName(), msg).
 					Return(nil)

+ 2 - 2
handler/oservice.go

@@ -269,7 +269,7 @@ func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *state.Ses
 // to 0x0100, set invisible. Else, return an error for any other value.
 // It returns SNAC oscar.OServiceUserInfoUpdate containing the user's info.
 func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, inFrame oscar.SNACFrame, inBody oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.SNACMessage, error) {
-	if status, hasStatus := inBody.GetUint32(oscar.OServiceUserInfoStatus); hasStatus {
+	if status, hasStatus := inBody.Uint32(oscar.OServiceUserInfoStatus); hasStatus {
 		switch status {
 		case 0x0000:
 			sess.SetInvisible(false)
@@ -341,7 +341,7 @@ func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *st
 		return oscar.SNACMessage{}, server.ErrUnsupportedSubGroup
 	}
 
-	roomMeta, ok := inBody.GetSlice(0x01)
+	roomMeta, ok := inBody.Slice(0x01)
 	if !ok {
 		return oscar.SNACMessage{}, errors.New("missing room info")
 	}

+ 2 - 2
oscar/snacs_test.go

@@ -36,8 +36,8 @@ func TestUnmarshal(t *testing.T) {
 		TLVRestBlock: TLVRestBlock{
 			TLVList: TLVList{
 				{
-					TType: 0x0B,
-					Val:   []byte{1, 2, 3, 4},
+					Tag:   0x0B,
+					Value: []byte{1, 2, 3, 4},
 				},
 			},
 		},

+ 67 - 48
oscar/tlv.go

@@ -14,90 +14,109 @@ const (
 	TLVPasswordHash        uint16 = 0x25
 )
 
+// TLV represents dynamically typed data in the OSCAR protocol. Each message
+// consists of a tag (or key) and a blob value. TLVs are typically grouped
+// together in arrays.
+type TLV struct {
+	Tag   uint16
+	Value []byte `len_prefix:"uint16"`
+}
+
+// NewTLV creates a new instance of TLV.
+func NewTLV(tag uint16, val any) TLV {
+	t := TLV{
+		Tag: tag,
+	}
+	if _, ok := val.([]byte); ok {
+		t.Value = val.([]byte)
+	} else {
+		buf := &bytes.Buffer{}
+		if err := Marshal(val, buf); err != nil {
+			panic(fmt.Sprintf("unable to create TLV: %s", err.Error()))
+		}
+		t.Value = buf.Bytes()
+	}
+	return t
+}
+
+// TLVRestBlock is a type of TLV array that does not have any length
+// information encoded in the blob. This typically means that a given offset in
+// the SNAC payload, the TLV occupies the "rest" of the payload.
 type TLVRestBlock struct {
 	TLVList
 }
 
+// TLVBlock is a type of TLV array that has the TLV element count encoded as a
+// 2-byte value at the beginning of the encoded blob.
 type TLVBlock struct {
 	TLVList `count_prefix:"uint16"`
 }
 
+// TLVLBlock is a type of TLV array that has the TLV blob byte-length encoded
+// as a 2-byte value at the beginning of the encoded blob.
 type TLVLBlock struct {
 	TLVList `len_prefix:"uint16"`
 }
 
+// TLVList is a list of TLV elements. It provides methods to append and access
+// TLVs in the array. It provides methods that decode the data blob into the
+// appropriate type at runtime. The caller assumes the TLV data type at runtime
+// based on the protocol specification. These methods are not safe for
+// read-write access by  multiple goroutines.
 type TLVList []TLV
 
-func (s *TLVList) AddTLV(tlv TLV) {
+// Append adds a TLV to the end of the TLV list.
+func (s *TLVList) Append(tlv TLV) {
 	*s = append(*s, tlv)
 }
 
-func (s *TLVList) AddTLVList(tlvs []TLV) {
+// AppendList adds a TLV list to the end of the TLV list.
+func (s *TLVList) AppendList(tlvs []TLV) {
 	*s = append(*s, tlvs...)
 }
 
-func (s TLVList) GetString(tType uint16) (string, bool) {
-	for _, tlv := range s {
-		if tType == tlv.TType {
-			return string(tlv.Val), true
+// String retrieves the string value of a TLV with a tag value from the TLV
+// list. It returns false if the tag does not exist in the list.
+func (s *TLVList) String(tag uint16) (string, bool) {
+	for _, tlv := range *s {
+		if tag == tlv.Tag {
+			return string(tlv.Value), true
 		}
 	}
 	return "", false
 }
 
-func (s TLVList) GetTLV(tType uint16) (TLV, bool) {
-	for _, tlv := range s {
-		if tType == tlv.TType {
-			return tlv, true
-		}
-	}
-	return TLV{}, false
-}
-
-func (s TLVList) GetSlice(tType uint16) ([]byte, bool) {
-	for _, tlv := range s {
-		if tType == tlv.TType {
-			return tlv.Val, true
+// Slice retrieves the slice value of a TLV with a tag value from the TLV
+// list. It returns false if the tag does not exist in the list.
+func (s *TLVList) Slice(tag uint16) ([]byte, bool) {
+	for _, tlv := range *s {
+		if tag == tlv.Tag {
+			return tlv.Value, true
 		}
 	}
 	return nil, false
 }
 
-func (s TLVList) GetUint16(tType uint16) (uint16, bool) {
-	for _, tlv := range s {
-		if tType == tlv.TType {
-			return binary.BigEndian.Uint16(tlv.Val), true
+// Uint16 retrieves the uint16 value of a TLV with a tag value from the TLV
+// list. It returns false if the tag does not exist in the list. It may panic
+// if the TLV value is not uint16.
+func (s *TLVList) Uint16(tag uint16) (uint16, bool) {
+	for _, tlv := range *s {
+		if tag == tlv.Tag {
+			return binary.BigEndian.Uint16(tlv.Value), true
 		}
 	}
 	return 0, false
 }
 
-func (s TLVList) GetUint32(tType uint16) (uint32, bool) {
-	for _, tlv := range s {
-		if tType == tlv.TType {
-			return binary.BigEndian.Uint32(tlv.Val), true
+// Uint32 retrieves the uint32 value of a TLV with a tag value from the TLV
+// list. It returns false if the tag does not exist in the list. It may panic
+// if the TLV value is not uint32.
+func (s *TLVList) Uint32(tag uint16) (uint32, bool) {
+	for _, tlv := range *s {
+		if tag == tlv.Tag {
+			return binary.BigEndian.Uint32(tlv.Value), true
 		}
 	}
 	return 0, false
 }
-
-func NewTLV(ttype uint16, val any) TLV {
-	t := TLV{
-		TType: ttype,
-	}
-	if _, ok := val.([]byte); ok {
-		t.Val = val.([]byte)
-	} else {
-		buf := &bytes.Buffer{}
-		if err := Marshal(val, buf); err != nil {
-			panic(fmt.Sprintf("unable to create TLV: %s", err.Error()))
-		}
-		t.Val = buf.Bytes()
-	}
-	return t
-}
-
-type TLV struct {
-	TType uint16
-	Val   []byte `len_prefix:"uint16"`
-}

+ 200 - 0
oscar/tlv_test.go

@@ -0,0 +1,200 @@
+package oscar
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestTLVList_Append(t *testing.T) {
+	want := TLVList{
+		{
+			Tag:   0,
+			Value: []byte(`0`),
+		},
+		{
+			Tag:   1,
+			Value: []byte(`1`),
+		},
+		{
+			Tag:   2,
+			Value: []byte(`2`),
+		},
+	}
+
+	have := TLVList{}
+	have.Append(NewTLV(0, []byte(`0`)))
+	have.Append(NewTLV(1, []byte(`1`)))
+	have.Append(NewTLV(2, []byte(`2`)))
+
+	assert.Equal(t, want, have)
+}
+
+func TestTLVList_AppendList(t *testing.T) {
+	want := TLVList{
+		{
+			Tag:   0,
+			Value: []byte(`0`),
+		},
+		{
+			Tag:   1,
+			Value: []byte(`1`),
+		},
+		{
+			Tag:   2,
+			Value: []byte(`2`),
+		},
+	}
+
+	have := TLVList{}
+	have.AppendList([]TLV{
+		NewTLV(0, []byte(`0`)),
+		NewTLV(1, []byte(`1`)),
+		NewTLV(2, []byte(`2`)),
+	})
+
+	assert.Equal(t, want, have)
+}
+
+func TestTLVList_Getters(t *testing.T) {
+	type args struct {
+		tType uint16
+	}
+	tests := []struct {
+		name   string
+		given  []TLV
+		ttype  any
+		lookup func(TLVList) (any, bool)
+		expect any
+		found  bool
+		panic  bool
+	}{
+		{
+			name: "given a TLV of uint32, expect found value",
+			given: []TLV{
+				NewTLV(0, uint32(12)),
+				NewTLV(1, uint32(34)),
+				NewTLV(2, uint32(56)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Uint32(1)
+			},
+			expect: uint32(34),
+			found:  true,
+		},
+		{
+			name: "given a TLV of uint32, expect not found value",
+			given: []TLV{
+				NewTLV(0, uint32(12)),
+				NewTLV(1, uint32(34)),
+				NewTLV(2, uint32(56)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Uint32(3)
+			},
+			expect: uint32(0),
+			found:  false,
+		},
+		{
+			name: "given a TLV of uint16, expect found value",
+			given: []TLV{
+				NewTLV(0, uint16(12)),
+				NewTLV(1, uint16(34)),
+				NewTLV(2, uint16(56)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Uint16(1)
+			},
+			expect: uint16(34),
+			found:  true,
+		},
+		{
+			name: "given a TLV of uint16, expect not found value",
+			given: []TLV{
+				NewTLV(0, uint16(12)),
+				NewTLV(1, uint16(34)),
+				NewTLV(2, uint16(56)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Uint16(3)
+			},
+			expect: uint16(0),
+			found:  false,
+		},
+		{
+			name: "given a TLV of string, expect found value",
+			given: []TLV{
+				NewTLV(0, "12"),
+				NewTLV(1, "34"),
+				NewTLV(2, "56"),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.String(1)
+			},
+			expect: "34",
+			found:  true,
+		},
+		{
+			name: "given a TLV of string, expect not found value",
+			given: []TLV{
+				NewTLV(0, "12"),
+				NewTLV(1, "34"),
+				NewTLV(2, "56"),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.String(3)
+			},
+			expect: "",
+			found:  false,
+		},
+		{
+			name: "given a TLV of slice, expect found value",
+			given: []TLV{
+				NewTLV(0, []byte(`12`)),
+				NewTLV(1, []byte(`34`)),
+				NewTLV(2, []byte(`56`)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Slice(1)
+			},
+			expect: []byte(`34`),
+			found:  true,
+		},
+		{
+			name: "given a TLV of string, expect not found value",
+			given: []TLV{
+				NewTLV(0, []byte(`12`)),
+				NewTLV(1, []byte(`34`)),
+				NewTLV(2, []byte(`56`)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Slice(3)
+			},
+			expect: []byte(nil),
+			found:  false,
+		},
+		{
+			name: "expect a panic when there's a type mismatch",
+			given: []TLV{
+				NewTLV(0, uint16(12)),
+				NewTLV(1, uint16(34)),
+				NewTLV(2, uint16(56)),
+			},
+			lookup: func(l TLVList) (any, bool) {
+				return l.Uint32(1)
+			},
+			panic: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.panic {
+				assert.Panics(t, func() { tt.lookup(tt.given) })
+				return
+			}
+			have, found := tt.lookup(tt.given)
+			assert.Equal(t, tt.expect, have)
+			assert.Equal(t, tt.found, found)
+		})
+	}
+}

+ 1 - 1
server/auth_service.go

@@ -73,7 +73,7 @@ func verifyChatLogin(rw io.ReadWriter) (*ChatCookie, uint32, error) {
 	}
 
 	var ok bool
-	buf, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
+	buf, ok := flap.Slice(oscar.OServiceTLVTagsLoginCookie)
 	if !ok {
 		return nil, 0, errors.New("unable to get session id from payload")
 	}

+ 1 - 1
server/bos_service.go

@@ -58,7 +58,7 @@ func (rt BOSService) handleNewConnection(ctx context.Context, rwc io.ReadWriteCl
 	}
 
 	var ok bool
-	sessionID, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
+	sessionID, ok := flap.Slice(oscar.OServiceTLVTagsLoginCookie)
 	if !ok {
 		rt.Logger.ErrorContext(ctx, "unable to get session id from payload")
 		return

+ 1 - 1
server/chat_nav.go

@@ -55,7 +55,7 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *state.Session,
 		if err != nil {
 			return err
 		}
-		roomName, _ := inBody.GetString(oscar.ChatTLVRoomName)
+		roomName, _ := inBody.String(oscar.ChatTLVRoomName)
 		rt.Logger.InfoContext(ctx, "user started a chat room", slog.String("roomName", roomName))
 		rt.logRequestAndResponse(ctx, inFrame, inBody, outSNAC.Frame, outSNAC.Body)
 		return sendSNAC(outSNAC.Frame, outSNAC.Body, sequence, w)

+ 8 - 8
server/feedbag_test.go

@@ -33,8 +33,8 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -49,8 +49,8 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -68,8 +68,8 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -203,8 +203,8 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},

+ 8 - 8
server/locate_test.go

@@ -56,8 +56,8 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -78,8 +78,8 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -121,8 +121,8 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},
@@ -161,8 +161,8 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
-								TType: 0x01,
-								Val:   []byte{1, 2, 3, 4},
+								Tag:   0x01,
+								Value: []byte{1, 2, 3, 4},
 							},
 						},
 					},

+ 8 - 8
state/session.go

@@ -158,34 +158,34 @@ func (s *Session) userInfo() oscar.TLVList {
 	// sign-in timestamp
 	tlvs := oscar.TLVList{}
 
-	tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoSignonTOD, uint32(s.signonTime.Unix())))
+	tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoSignonTOD, uint32(s.signonTime.Unix())))
 
 	// away message status
 	if s.awayMessage != "" {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoUserFlags, oscar.OServiceUserFlagOSCARFree|oscar.OServiceUserFlagUnavailable))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoUserFlags, oscar.OServiceUserFlagOSCARFree|oscar.OServiceUserFlagUnavailable))
 	} else {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoUserFlags, oscar.OServiceUserFlagOSCARFree))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoUserFlags, oscar.OServiceUserFlagOSCARFree))
 	}
 
 	// invisibility status
 	if s.invisible {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoStatus, oscar.OServiceUserFlagInvisible))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoStatus, oscar.OServiceUserFlagInvisible))
 	} else {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoStatus, uint16(0x0000)))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoStatus, uint16(0x0000)))
 	}
 
 	// idle status
 	if s.idle {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(s.idleTime).Seconds())))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoIdleTime, uint16(s.nowFn().Sub(s.idleTime).Seconds())))
 	} else {
-		tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoIdleTime, uint16(0)))
+		tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoIdleTime, uint16(0)))
 	}
 
 	// capabilities
 	var caps []byte
 	// chat capability
 	caps = append(caps, capChat...)
-	tlvs.AddTLV(oscar.NewTLV(oscar.OServiceUserInfoOscarCaps, caps))
+	tlvs.Append(oscar.NewTLV(oscar.OServiceUserInfoOscarCaps, caps))
 
 	return tlvs
 }