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

test TestReceiveAndSendFeedbagQuery and TestReceiveAndSendFeedbagQueryIfModified

Mike 2 лет назад
Родитель
Сommit
6363bd2345
3 измененных файлов с 319 добавлено и 37 удалено
  1. 29 1
      oscar/decode.go
  2. 30 36
      server/feedbag.go
  3. 260 0
      server/feedbag_test.go

+ 29 - 1
oscar/decode.go

@@ -94,8 +94,8 @@ func unmarshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, r io.Read
 		}
 		}
 		v.Set(reflect.ValueOf(l))
 		v.Set(reflect.ValueOf(l))
 	case reflect.Slice:
 	case reflect.Slice:
-		var bufLen int
 		if lenTag, ok := tag.Lookup("len_prefix"); ok {
 		if lenTag, ok := tag.Lookup("len_prefix"); ok {
+			var bufLen int
 			switch lenTag {
 			switch lenTag {
 			case "uint8":
 			case "uint8":
 				var l uint8
 				var l uint8
@@ -127,6 +127,34 @@ func unmarshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, r io.Read
 				slice = reflect.Append(slice, reflect.ValueOf(v1).Elem())
 				slice = reflect.Append(slice, reflect.ValueOf(v1).Elem())
 			}
 			}
 			v.Set(slice)
 			v.Set(slice)
+		} else if countTag, ok := tag.Lookup("count_prefix"); ok {
+			var count int
+			switch countTag {
+			case "uint8":
+				var l uint8
+				if err := binary.Read(r, binary.BigEndian, &l); err != nil {
+					return err
+				}
+				count = int(l)
+			case "uint16":
+				var l uint16
+				if err := binary.Read(r, binary.BigEndian, &l); err != nil {
+					return err
+				}
+				count = int(l)
+			default:
+				panic("count not set")
+			}
+
+			slice := reflect.New(v.Type()).Elem()
+			for i := 0; i < count; i++ {
+				v1 := reflect.New(v.Type().Elem()).Interface()
+				if err := Unmarshal(v1, r); err != nil {
+					return err
+				}
+				slice = reflect.Append(slice, reflect.ValueOf(v1).Elem())
+			}
+			v.Set(slice)
 		} else {
 		} else {
 			slice := reflect.New(v.Type()).Elem()
 			slice := reflect.New(v.Type()).Elem()
 			for {
 			for {

+ 30 - 36
server/feedbag.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"fmt"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/oscar"
 	"io"
 	"io"
+	"time"
 )
 )
 
 
 const (
 const (
@@ -12,6 +13,7 @@ const (
 	FeedbagRightsQuery                     = 0x0002
 	FeedbagRightsQuery                     = 0x0002
 	FeedbagQuery                           = 0x0004
 	FeedbagQuery                           = 0x0004
 	FeedbagQueryIfModified                 = 0x0005
 	FeedbagQueryIfModified                 = 0x0005
+	FeedbagReply                           = 0x0006
 	FeedbagUse                             = 0x0007
 	FeedbagUse                             = 0x0007
 	FeedbagInsertItem                      = 0x0008
 	FeedbagInsertItem                      = 0x0008
 	FeedbagUpdateItem                      = 0x0009
 	FeedbagUpdateItem                      = 0x0009
@@ -309,76 +311,68 @@ func SendAndReceiveFeedbagRightsQuery(snac oscar.SnacFrame, r io.Reader, w io.Wr
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
 
 
-func ReceiveAndSendFeedbagQuery(sess *Session, fm *FeedbagStore, snac oscar.SnacFrame, w io.Writer, sequence *uint32) error {
-	fmt.Printf("receiveAndSendFeedbagQuery read SNAC frame: %+v\n", snac)
-
+func ReceiveAndSendFeedbagQuery(sess *Session, fm FeedbagManager, snac oscar.SnacFrame, w io.Writer, sequence *uint32) error {
 	fb, err := fm.Retrieve(sess.ScreenName)
 	fb, err := fm.Retrieve(sess.ScreenName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	var lastModified uint32
+	lm := time.UnixMilli(0)
+
 	if len(fb) > 0 {
 	if len(fb) > 0 {
-		lm, err := fm.LastModified(sess.ScreenName)
+		lm, err = fm.LastModified(sess.ScreenName)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		lastModified = uint32(lm.Unix())
 	}
 	}
 
 
 	snacFrameOut := oscar.SnacFrame{
 	snacFrameOut := oscar.SnacFrame{
-		FoodGroup: 0x13,
-		SubGroup:  0x06,
+		FoodGroup: FEEDBAG,
+		SubGroup:  FeedbagReply,
 	}
 	}
 	snacPayloadOut := oscar.SNAC_0x13_0x06_FeedbagReply{
 	snacPayloadOut := oscar.SNAC_0x13_0x06_FeedbagReply{
 		Version:    0,
 		Version:    0,
 		Items:      fb,
 		Items:      fb,
-		LastUpdate: lastModified,
+		LastUpdate: uint32(lm.Unix()),
 	}
 	}
 
 
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
 
 
-func ReceiveAndSendFeedbagQueryIfModified(sess *Session, fm *FeedbagStore, snac oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
-	fmt.Printf("ReceiveAndSendFeedbagQueryIfModified read SNAC frame: %+v\n", snac)
-
+func ReceiveAndSendFeedbagQueryIfModified(sess *Session, fm FeedbagManager, snac oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	snacPayloadIn := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{}
 	snacPayloadIn := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{}
 	if err := oscar.Unmarshal(&snacPayloadIn, r); err != nil {
 	if err := oscar.Unmarshal(&snacPayloadIn, r); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	fmt.Printf("ReceiveAndSendFeedbagQueryIfModified read SNAC: %+v\n", snacPayloadIn)
-
 	fb, err := fm.Retrieve(sess.ScreenName)
 	fb, err := fm.Retrieve(sess.ScreenName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	lm, err := fm.LastModified(sess.ScreenName)
-	if err != nil {
-		return err
-	}
+	lm := time.UnixMilli(0)
 
 
-	//if lm.Before(time.Unix(int64(snacPayloadIn.lastUpdate), 0)) {
-	//todo not sure this works right now
-	//	snacFrameOut := snacFrame{
-	//		FoodGroup: 0x13,
-	//		SubGroup:  0x0F,
-	//	}
-	//	lm, err := fm.LastModified(sess.ScreenName)
-	//	if err != nil {
-	//		return err
-	//	}
-	//	snacPayloadOut := SNAC_0x13_0x05_FeedbagQueryIfModified{
-	//		lastUpdate: uint32(lm.Unix()),
-	//		count:      uint8(len(fb)),
-	//	}
-	//	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
-	//}
+	if len(fb) > 0 {
+		lm, err = fm.LastModified(sess.ScreenName)
+		if err != nil {
+			return err
+		}
+		if lm.Before(time.Unix(int64(snacPayloadIn.LastUpdate), 0)) {
+			snacFrameOut := oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReplyNotModified,
+			}
+			snacPayloadOut := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				LastUpdate: uint32(lm.Unix()),
+				Count:      uint8(len(fb)),
+			}
+			return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
+		}
+	}
 
 
 	snacFrameOut := oscar.SnacFrame{
 	snacFrameOut := oscar.SnacFrame{
-		FoodGroup: 0x13,
-		SubGroup:  0x06,
+		FoodGroup: FEEDBAG,
+		SubGroup:  FeedbagReply,
 	}
 	}
 	snacPayloadOut := oscar.SNAC_0x13_0x06_FeedbagReply{
 	snacPayloadOut := oscar.SNAC_0x13_0x06_FeedbagReply{
 		Version:    0,
 		Version:    0,

+ 260 - 0
server/feedbag_test.go

@@ -0,0 +1,260 @@
+package server
+
+import (
+	"bytes"
+	"github.com/mkaminski/goaim/oscar"
+	"github.com/stretchr/testify/assert"
+	"testing"
+	"time"
+)
+
+func TestReceiveAndSendFeedbagQuery(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// screenName is the buddy list owner
+		screenName string
+		// feedbagItems is the list of items in user's buddy list
+		feedbagItems []oscar.FeedbagItem
+		// lastModified is the time the buddy list was last changed
+		lastModified time.Time
+		// inputSNAC is the SNAC frame sent from the server to the recipient
+		// client
+		expectSNACFrame oscar.SnacFrame
+		// expectSNACBody is the SNAC payload sent from the server to the
+		// recipient client
+		expectSNACBody oscar.SNAC_0x13_0x06_FeedbagReply
+	}{
+		{
+			name:         "retrieve empty feedbag",
+			screenName:   "sender-screen-name",
+			feedbagItems: []oscar.FeedbagItem{},
+			lastModified: time.UnixMilli(0),
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReply,
+			},
+			expectSNACBody: oscar.SNAC_0x13_0x06_FeedbagReply{},
+		},
+		{
+			name:       "retrieve feedbag with items",
+			screenName: "sender-screen-name",
+			feedbagItems: []oscar.FeedbagItem{
+				{
+					Name: "buddy_1",
+				},
+				{
+					Name: "buddy_2",
+				},
+			},
+			lastModified: time.UnixMilli(1696472198082),
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReply,
+			},
+			expectSNACBody: oscar.SNAC_0x13_0x06_FeedbagReply{
+				Version: 0,
+				Items: []oscar.FeedbagItem{
+					{
+						Name: "buddy_1",
+					},
+					{
+						Name: "buddy_2",
+					},
+				},
+				LastUpdate: uint32(time.UnixMilli(1696472198082).Unix()),
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			//
+			// initialize dependencies
+			//
+			fm := NewMockFeedbagManager(t)
+			fm.EXPECT().
+				Retrieve(tc.screenName).
+				Return(tc.feedbagItems, nil).
+				Maybe()
+			fm.EXPECT().
+				LastModified(tc.screenName).
+				Return(tc.lastModified, nil).
+				Maybe()
+			//
+			// send input SNAC
+			//
+			var seq uint32
+			snac := oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagQuery,
+			}
+			senderSession := &Session{
+				ScreenName: tc.screenName,
+			}
+			output := &bytes.Buffer{}
+			assert.NoError(t, ReceiveAndSendFeedbagQuery(senderSession, fm, snac, output, &seq))
+			//
+			// verify output
+			//
+			flap := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flap, output))
+			snacFrame := oscar.SnacFrame{}
+			assert.NoError(t, oscar.Unmarshal(&snacFrame, output))
+			assert.Equal(t, tc.expectSNACFrame, snacFrame)
+			//
+			// verify output SNAC body
+			//
+			actual := oscar.SNAC_0x13_0x06_FeedbagReply{}
+			assert.NoError(t, oscar.Unmarshal(&actual, output))
+			assert.Equal(t, tc.expectSNACBody, actual)
+		})
+	}
+}
+
+func TestReceiveAndSendFeedbagQueryIfModified(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// screenName is the buddy list owner
+		screenName string
+		// feedbagItems is the list of items in user's buddy list
+		feedbagItems []oscar.FeedbagItem
+		// lastModified is the time the buddy list was last changed
+		lastModified time.Time
+		// inputSNAC is the SNAC frame sent from the server to the recipient
+		// client
+		expectSNACFrame oscar.SnacFrame
+		// inputSNAC is the SNAC sent by the sender client
+		inputSNAC oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
+		// expectSNACBody is the SNAC payload sent from the server to the
+		// recipient client
+		expectSNACBody any
+	}{
+		{
+			name:         "retrieve empty feedbag",
+			screenName:   "sender-screen-name",
+			feedbagItems: []oscar.FeedbagItem{},
+			lastModified: time.UnixMilli(0),
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReply,
+			},
+			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				LastUpdate: uint32(time.UnixMilli(100000).Unix()),
+			},
+			expectSNACBody: oscar.SNAC_0x13_0x06_FeedbagReply{},
+		},
+		{
+			name:       "retrieve feedbag with items",
+			screenName: "sender-screen-name",
+			feedbagItems: []oscar.FeedbagItem{
+				{
+					Name: "buddy_1",
+				},
+				{
+					Name: "buddy_2",
+				},
+			},
+			lastModified: time.UnixMilli(200000),
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReply,
+			},
+			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				LastUpdate: uint32(time.UnixMilli(100000).Unix()),
+			},
+			expectSNACBody: oscar.SNAC_0x13_0x06_FeedbagReply{
+				Version: 0,
+				Items: []oscar.FeedbagItem{
+					{
+						Name: "buddy_1",
+					},
+					{
+						Name: "buddy_2",
+					},
+				},
+				LastUpdate: uint32(time.UnixMilli(200000).Unix()),
+			},
+		},
+		{
+			name:       "retrieve not-modified response",
+			screenName: "sender-screen-name",
+			feedbagItems: []oscar.FeedbagItem{
+				{
+					Name: "buddy_1",
+				},
+				{
+					Name: "buddy_2",
+				},
+			},
+			lastModified: time.UnixMilli(100000),
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagReplyNotModified,
+			},
+			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				LastUpdate: uint32(time.UnixMilli(200000).Unix()),
+			},
+			expectSNACBody: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				LastUpdate: uint32(time.UnixMilli(100000).Unix()),
+				Count:      2,
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			//
+			// initialize dependencies
+			//
+			fm := NewMockFeedbagManager(t)
+			fm.EXPECT().
+				Retrieve(tc.screenName).
+				Return(tc.feedbagItems, nil).
+				Maybe()
+			fm.EXPECT().
+				LastModified(tc.screenName).
+				Return(tc.lastModified, nil).
+				Maybe()
+			//
+			// send input SNAC
+			//
+			input := &bytes.Buffer{}
+			var seq uint32
+			assert.NoError(t, oscar.Marshal(tc.inputSNAC, input))
+			output := &bytes.Buffer{}
+			snac := oscar.SnacFrame{
+				FoodGroup: FEEDBAG,
+				SubGroup:  FeedbagQuery,
+			}
+			senderSession := &Session{
+				ScreenName: tc.screenName,
+			}
+			assert.NoError(t, ReceiveAndSendFeedbagQueryIfModified(senderSession, fm, snac, input, output, &seq))
+			//
+			// verify output
+			//
+			flap := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flap, output))
+			snacFrame := oscar.SnacFrame{}
+			assert.NoError(t, oscar.Unmarshal(&snacFrame, output))
+			assert.Equal(t, tc.expectSNACFrame, snacFrame)
+			//
+			// verify output SNAC body
+			//
+			switch v := tc.expectSNACBody.(type) {
+			case oscar.SNAC_0x13_0x06_FeedbagReply:
+				outputSNAC := oscar.SNAC_0x13_0x06_FeedbagReply{}
+				assert.NoError(t, oscar.Unmarshal(&outputSNAC, output))
+				assert.Equal(t, v, outputSNAC)
+			case oscar.SNAC_0x13_0x05_FeedbagQueryIfModified:
+				outputSNAC := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{}
+				assert.NoError(t, oscar.Unmarshal(&outputSNAC, output))
+				assert.Equal(t, v, outputSNAC)
+			default:
+				t.Fatalf("unexpected output SNAC type")
+			}
+		})
+	}
+}