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

test ReceiveAndSendServiceRequest

Mike 2 лет назад
Родитель
Сommit
dc313bdaed
4 измененных файлов с 239 добавлено и 124 удалено
  1. 2 39
      server/chat_nav.go
  2. 43 61
      server/oservice.go
  3. 170 0
      server/oservice_test.go
  4. 24 24
      server/protocol.go

+ 2 - 39
server/chat_nav.go

@@ -1,7 +1,6 @@
 package server
 package server
 
 
 import (
 import (
-	"encoding/binary"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -47,44 +46,8 @@ func routeChatNav(sess *Session, cr *ChatRegistry, snac oscar.SnacFrame, r io.Re
 }
 }
 
 
 type ChatCookie struct {
 type ChatCookie struct {
-	Cookie []byte
-	SessID string
-}
-
-func (s *ChatCookie) Read(r io.Reader) error {
-	var l uint16
-	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
-		return err
-	}
-	s.Cookie = make([]byte, l)
-	if _, err := r.Read(s.Cookie); err != nil {
-		return err
-	}
-	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
-		return err
-	}
-	buf := make([]byte, l)
-	if _, err := r.Read(buf); err != nil {
-		return err
-	}
-	s.SessID = string(buf)
-	return nil
-}
-
-func (s ChatCookie) Write(w io.Writer) error {
-	if err := binary.Write(w, binary.BigEndian, uint16(len(s.Cookie))); err != nil {
-		return err
-	}
-	if err := binary.Write(w, binary.BigEndian, s.Cookie); err != nil {
-		return err
-	}
-	if err := binary.Write(w, binary.BigEndian, uint16(len(s.SessID))); err != nil {
-		return err
-	}
-	if err := binary.Write(w, binary.BigEndian, []byte(s.SessID)); err != nil {
-		return err
-	}
-	return nil
+	Cookie []byte `len_prefix:"uint16"`
+	SessID string `len_prefix:"uint16"`
 }
 }
 
 
 func SendAndReceiveNextChatRights(snac oscar.SnacFrame, w io.Writer, sequence *uint32) error {
 func SendAndReceiveNextChatRights(snac oscar.SnacFrame, w io.Writer, sequence *uint32) error {

+ 43 - 61
server/oservice.go

@@ -379,37 +379,11 @@ const (
 )
 )
 
 
 func ReceiveAndSendServiceRequest(cfg Config, cr *ChatRegistry, sess *Session, snac oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 func ReceiveAndSendServiceRequest(cfg Config, cr *ChatRegistry, sess *Session, snac oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
-	fmt.Printf("receiveAndSendServiceRequest read SNAC frame: %+v\n", snac)
-
 	snacPayloadIn := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
 	snacPayloadIn := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
 	if err := oscar.Unmarshal(&snacPayloadIn, r); err != nil {
 	if err := oscar.Unmarshal(&snacPayloadIn, r); err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	// this prevents AIM client from crashing when using the
-	// store/edit email address feature.
-	if _, hasEditBuddy := snacPayloadIn.GetTLV(0x28); hasEditBuddy {
-		snacFrameOut := oscar.SnacFrame{
-			FoodGroup: OSERVICE,
-			SubGroup:  OServiceErr,
-		}
-		snacPayloadOut := oscar.SnacOServiceErr{
-			Code: ErrorCodeNotSupportedByHost,
-		}
-		return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
-	}
-
-	fmt.Printf("receiveAndSendServiceRequest read SNAC body: %+v\n", snacPayloadIn)
-
-	// just say that all the services are offline
-	snacFrameOut := oscar.SnacFrame{
-		FoodGroup: OSERVICE,
-		SubGroup:  OServiceServiceResponse,
-	}
-	snacPayloadOut := oscar.SnacOServiceErr{
-		Code: 0x06,
-	}
-
 	if snacPayloadIn.FoodGroup == CHAT {
 	if snacPayloadIn.FoodGroup == CHAT {
 		roomMeta, ok := snacPayloadIn.GetSlice(0x01)
 		roomMeta, ok := snacPayloadIn.GetSlice(0x01)
 		if !ok {
 		if !ok {
@@ -421,50 +395,58 @@ func ReceiveAndSendServiceRequest(cfg Config, cr *ChatRegistry, sess *Session, s
 			return err
 			return err
 		}
 		}
 
 
-		cookie := ChatCookie{
-			Cookie: roomSnac.Cookie,
-			SessID: sess.ID,
-		}
-		buf := &bytes.Buffer{}
-		if err := cookie.Write(buf); err != nil {
-			return err
-		}
-
 		room, err := cr.Retrieve(string(roomSnac.Cookie))
 		room, err := cr.Retrieve(string(roomSnac.Cookie))
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-
 		room.NewSessionWithSN(sess.ID, sess.ScreenName)
 		room.NewSessionWithSN(sess.ID, sess.ScreenName)
 
 
-		snacPayloadOut := oscar.SNAC_0x01_0x05_OServiceServiceResponse{
-			TLVRestBlock: oscar.TLVRestBlock{
-				TLVList: oscar.TLVList{
-					{
-						TType: OserviceTlvTagsReconnectHere,
-						Val:   Address(cfg.OSCARHost, cfg.ChatPort),
-					},
-					{
-						TType: OserviceTlvTagsLoginCookie,
-						Val:   buf.Bytes(),
-					},
-					{
-						TType: OserviceTlvTagsGroupId,
-						Val:   snacPayloadIn.FoodGroup,
-					},
-					{
-						TType: OserviceTlvTagsSslCertname,
-						Val:   "",
-					},
-					{
-						TType: OserviceTlvTagsSslState,
-						Val:   uint8(0x00),
+		return sendChatRoomServiceInfo(cfg, room, sess, snac, sequence, w)
+	}
+
+	snacFrameOut := oscar.SnacFrame{
+		FoodGroup: OSERVICE,
+		SubGroup:  OServiceErr,
+	}
+	snacPayloadOut := oscar.SnacOServiceErr{
+		Code: ErrorCodeNotSupportedByHost,
+	}
+	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
+}
+
+func sendChatRoomServiceInfo(cfg Config, room ChatRoom, sess *Session, snac oscar.SnacFrame, sequence *uint32, w io.Writer) error {
+	snacFrameOut := oscar.SnacFrame{
+		FoodGroup: OSERVICE,
+		SubGroup:  OServiceServiceResponse,
+	}
+	snacPayloadOut := oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+		TLVRestBlock: oscar.TLVRestBlock{
+			TLVList: oscar.TLVList{
+				{
+					TType: OserviceTlvTagsReconnectHere,
+					Val:   Address(cfg.OSCARHost, cfg.ChatPort),
+				},
+				{
+					TType: OserviceTlvTagsLoginCookie,
+					Val: ChatCookie{
+						Cookie: []byte(room.Cookie),
+						SessID: sess.ID,
 					},
 					},
 				},
 				},
+				{
+					TType: OserviceTlvTagsGroupId,
+					Val:   CHAT,
+				},
+				{
+					TType: OserviceTlvTagsSslCertname,
+					Val:   "",
+				},
+				{
+					TType: OserviceTlvTagsSslState,
+					Val:   uint8(0x00),
+				},
 			},
 			},
-		}
-		return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
+		},
 	}
 	}
-
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }

+ 170 - 0
server/oservice_test.go

@@ -0,0 +1,170 @@
+package server
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"github.com/mkaminski/goaim/oscar"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestReceiveAndSendServiceRequest(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// cfg is the application config
+		cfg Config
+		// chatRoom is the chat room the user connects to
+		chatRoom ChatRoom
+		// userSession is the session of the user requesting the chat service
+		// info
+		userSession *Session
+		// inputSNAC is the SNAC sent by the sender client
+		inputSNAC oscar.SNAC_0x01_0x04_OServiceServiceRequest
+		// expectSNACFrame 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 any
+	}{
+		{
+			name: "request info for ICBM service, return service not defined error",
+			userSession: &Session{
+				ScreenName: "user_screen_name",
+			},
+			inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
+				FoodGroup: ICBM,
+			},
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: OSERVICE,
+				SubGroup:  OServiceErr,
+			},
+			expectSNACBody: oscar.SnacOServiceErr{
+				Code: ErrorCodeNotSupportedByHost,
+			},
+		},
+		{
+			name: "request info for connecting to chat room, return chat service and chat room metadata",
+			cfg: Config{
+				OSCARHost: "127.0.0.1",
+				ChatPort:  1234,
+			},
+			chatRoom: ChatRoom{
+				CreateTime:     time.UnixMilli(0),
+				DetailLevel:    4,
+				Exchange:       8,
+				Cookie:         "the-chat-cookie",
+				InstanceNumber: 16,
+				Name:           "my new chat",
+			},
+			userSession: &Session{
+				ID:         "user-sess-id",
+				ScreenName: "user_screen_name",
+			},
+			inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
+				FoodGroup: CHAT,
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						{
+							TType: 0x01,
+							Val: oscar.SNAC_0x01_0x04_TLVRoomInfo{
+								Exchange:       8,
+								Cookie:         []byte("the-chat-cookie"),
+								InstanceNumber: 16,
+							},
+						},
+					},
+				},
+			},
+			expectSNACFrame: oscar.SnacFrame{
+				FoodGroup: OSERVICE,
+				SubGroup:  OServiceServiceResponse,
+			},
+			expectSNACBody: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						{
+							TType: OserviceTlvTagsReconnectHere,
+							Val:   "127.0.0.1:1234",
+						},
+						{
+							TType: OserviceTlvTagsLoginCookie,
+							Val: ChatCookie{
+								Cookie: []byte("the-chat-cookie"),
+								SessID: "user-sess-id",
+							},
+						},
+						{
+							TType: OserviceTlvTagsGroupId,
+							Val:   CHAT,
+						},
+						{
+							TType: OserviceTlvTagsSslCertname,
+							Val:   "",
+						},
+						{
+							TType: OserviceTlvTagsSslState,
+							Val:   uint8(0x00),
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			//
+			// initialize dependencies
+			//
+			sm := NewMockSessionManager(t)
+			sm.EXPECT().
+				NewSessionWithSN(tc.userSession.ID, tc.userSession.ScreenName).
+				Return(&Session{}).
+				Maybe()
+			tc.chatRoom.SessionManager = sm
+			cr := NewChatRegistry()
+			cr.Register(tc.chatRoom)
+
+			//
+			// send input SNAC
+			//
+			snac := oscar.SnacFrame{
+				FoodGroup: OSERVICE,
+				SubGroup:  OServiceServiceRequest,
+			}
+			input := &bytes.Buffer{}
+			assert.NoError(t, oscar.Marshal(tc.inputSNAC, input))
+			output := &bytes.Buffer{}
+			var seq uint32
+			assert.NoError(t, ReceiveAndSendServiceRequest(tc.cfg, cr, tc.userSession, snac, input, output, &seq))
+
+			//
+			// verify server response
+			//
+			flapFrame := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flapFrame, output))
+
+			snacFrame := oscar.SnacFrame{}
+			assert.NoError(t, oscar.Unmarshal(&snacFrame, output))
+			assert.Equal(t, tc.expectSNACFrame, snacFrame)
+
+			switch expectSNAC := tc.expectSNACBody.(type) {
+			case oscar.SNAC_0x01_0x05_OServiceServiceResponse:
+				assert.NoError(t, expectSNAC.SerializeInPlace())
+				outputSNAC := oscar.SNAC_0x01_0x05_OServiceServiceResponse{}
+				assert.NoError(t, oscar.Unmarshal(&outputSNAC, output))
+				assert.Equal(t, expectSNAC, outputSNAC)
+			case oscar.SnacOServiceErr:
+				outputSNAC := oscar.SnacOServiceErr{}
+				assert.NoError(t, oscar.Unmarshal(&outputSNAC, output))
+				assert.Equal(t, expectSNAC, outputSNAC)
+			default:
+				t.Fatalf("unexpected output SNAC type")
+			}
+			assert.Equalf(t, 0, output.Len(), "the rest of the buffer is unread")
+		})
+	}
+}

+ 24 - 24
server/protocol.go

@@ -148,36 +148,36 @@ func VerifyChatLogin(rw io.ReadWriter) (*ChatCookie, uint32, error) {
 	}
 	}
 
 
 	cookie := ChatCookie{}
 	cookie := ChatCookie{}
-	err = cookie.Read(bytes.NewBuffer(buf))
+	err = oscar.Unmarshal(&cookie, bytes.NewBuffer(buf))
 
 
 	return &cookie, seq, err
 	return &cookie, seq, err
 }
 }
 
 
 const (
 const (
 	OSERVICE      uint16 = 0x0001
 	OSERVICE      uint16 = 0x0001
-	LOCATE               = 0x0002
-	BUDDY                = 0x0003
-	ICBM                 = 0x0004
-	ADVERT               = 0x0005
-	INVITE               = 0x0006
-	ADMIN                = 0x0007
-	POPUP                = 0x0008
-	PD                   = 0x0009
-	USER_LOOKUP          = 0x000A
-	STATS                = 0x000B
-	TRANSLATE            = 0x000C
-	CHAT_NAV             = 0x000D
-	CHAT                 = 0x000E
-	ODIR                 = 0x000F
-	BART                 = 0x0010
-	FEEDBAG              = 0x0013
-	ICQ                  = 0x0015
-	BUCP                 = 0x0017
-	ALERT                = 0x0018
-	PLUGIN               = 0x0022
-	UNNAMED_FG_24        = 0x0024
-	MDIR                 = 0x0025
-	ARS                  = 0x044A
+	LOCATE        uint16 = 0x0002
+	BUDDY         uint16 = 0x0003
+	ICBM          uint16 = 0x0004
+	ADVERT        uint16 = 0x0005
+	INVITE        uint16 = 0x0006
+	ADMIN         uint16 = 0x0007
+	POPUP         uint16 = 0x0008
+	PD            uint16 = 0x0009
+	USER_LOOKUP   uint16 = 0x000A
+	STATS         uint16 = 0x000B
+	TRANSLATE     uint16 = 0x000C
+	CHAT_NAV      uint16 = 0x000D
+	CHAT          uint16 = 0x000E
+	ODIR          uint16 = 0x000F
+	BART          uint16 = 0x0010
+	FEEDBAG       uint16 = 0x0013
+	ICQ           uint16 = 0x0015
+	BUCP          uint16 = 0x0017
+	ALERT         uint16 = 0x0018
+	PLUGIN        uint16 = 0x0022
+	UNNAMED_FG_24 uint16 = 0x0024
+	MDIR          uint16 = 0x0025
+	ARS           uint16 = 0x044A
 )
 )
 
 
 type IncomingMessage struct {
 type IncomingMessage struct {