Selaa lähdekoodia

write table tests for BUCP

Mike 2 vuotta sitten
vanhempi
commit
3a213ec849
7 muutettua tiedostoa jossa 387 lisäystä ja 462 poistoa
  1. 2 1
      cmd/main.go
  2. 22 0
      oscar/tlv.go
  3. 12 19
      server/bucp.go
  4. 349 417
      server/bucp_test.go
  5. 2 2
      server/mgmt_api.go
  6. 0 8
      server/protocol.go
  7. 0 15
      server/session.go

+ 2 - 1
cmd/main.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"fmt"
+	"github.com/google/uuid"
 	"github.com/kelseyhightower/envconfig"
 	"github.com/mkaminski/goaim/server"
 	"io"
@@ -96,7 +97,7 @@ func handleAuthConnection(cfg server.Config, sm *server.SessionManager, fm *serv
 		return
 	}
 
-	err = server.ReceiveAndSendAuthChallenge(cfg, fm, conn, conn, &seq)
+	err = server.ReceiveAndSendAuthChallenge(cfg, fm, conn, conn, &seq, uuid.New)
 	if err != nil {
 		log.Println(err)
 		return

+ 22 - 0
oscar/tlv.go

@@ -6,6 +6,14 @@ import (
 	"io"
 )
 
+const (
+	TLVScreenName          uint16 = 0x01
+	TLVReconnectHere       uint16 = 0x05
+	TLVAuthorizationCookie uint16 = 0x06
+	TLVErrorSubcode        uint16 = 0x08
+	TLVPasswordHash        uint16 = 0x25
+)
+
 type TLVRestBlock struct {
 	TLVList
 }
@@ -111,6 +119,20 @@ func (s TLVList) WriteTLV(w io.Writer) error {
 	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 {

+ 12 - 19
server/bucp.go

@@ -42,7 +42,7 @@ func routeBUCP(snac oscar.SnacFrame) error {
 	return nil
 }
 
-func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io.Writer, sequence *uint32) error {
+func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io.Writer, sequence *uint32, newUUID func() uuid.UUID) error {
 	flap := oscar.FlapFrame{}
 	if err := oscar.Unmarshal(&flap, r); err != nil {
 		return err
@@ -61,7 +61,7 @@ func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io
 	if err := oscar.Unmarshal(&snacPayloadIn, buf); err != nil {
 		return err
 	}
-	screenName, exists := snacPayloadIn.GetString(TLVScreenName)
+	screenName, exists := snacPayloadIn.GetString(oscar.TLVScreenName)
 	if !exists {
 		return errors.New("screen name doesn't exist in tlv")
 	}
@@ -77,11 +77,7 @@ func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io
 		authKey = u.AuthKey
 	case cfg.DisableAuth:
 		// can't find user, generate stub auth key
-		uid, err := uuid.NewUUID()
-		if err != nil {
-			return err
-		}
-		authKey = uid.String()
+		authKey = newUUID().String()
 	default:
 		// can't find user, return login error
 		snacFrameOut := oscar.SnacFrame{
@@ -90,7 +86,7 @@ func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io
 		}
 		snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
 		snacPayloadOut.AddTLV(oscar.TLV{
-			TType: TLVErrorSubcode,
+			TType: oscar.TLVErrorSubcode,
 			Val:   uint16(0x01),
 		})
 		return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
@@ -106,7 +102,7 @@ func ReceiveAndSendAuthChallenge(cfg Config, fm *FeedbagStore, r io.Reader, w io
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 }
 
-func ReceiveAndSendBUCPLoginRequest(cfg Config, sm *SessionManager, fm *FeedbagStore, r io.Reader, w io.Writer, sequence *uint32) error {
+func ReceiveAndSendBUCPLoginRequest(cfg Config, sm *SessionManager, fm *FeedbagStore, r io.Reader, w io.Writer, sequence *uint32, newUUID func() uuid.UUID) error {
 	flap := oscar.FlapFrame{}
 	if err := oscar.Unmarshal(&flap, r); err != nil {
 		return err
@@ -125,11 +121,11 @@ func ReceiveAndSendBUCPLoginRequest(cfg Config, sm *SessionManager, fm *FeedbagS
 	if err := oscar.Unmarshal(&snacPayloadIn, buf); err != nil {
 		return err
 	}
-	screenName, found := snacPayloadIn.GetString(TLVScreenName)
+	screenName, found := snacPayloadIn.GetString(oscar.TLVScreenName)
 	if !found {
 		return errors.New("screen name doesn't exist in tlv")
 	}
-	md5Hash, found := snacPayloadIn.GetSlice(TLVPasswordHash)
+	md5Hash, found := snacPayloadIn.GetSlice(oscar.TLVPasswordHash)
 	if !found {
 		return errors.New("password hash doesn't exist in tlv")
 	}
@@ -157,29 +153,26 @@ func ReceiveAndSendBUCPLoginRequest(cfg Config, sm *SessionManager, fm *FeedbagS
 
 	snacPayloadOut := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
 	snacPayloadOut.AddTLV(oscar.TLV{
-		TType: TLVScreenName,
+		TType: oscar.TLVScreenName,
 		Val:   screenName,
 	})
 
 	if loginOK {
-		sess, err := sm.NewSession(screenName)
-		if err != nil {
-			return err
-		}
+		sess := sm.NewSessionWithSN(newUUID().String(), screenName)
 		snacPayloadOut.AddTLVList([]oscar.TLV{
 			{
-				TType: TLVReconnectHere,
+				TType: oscar.TLVReconnectHere,
 				Val:   Address(cfg.OSCARHost, cfg.BOSPort),
 			},
 			{
-				TType: TLVAuthorizationCookie,
+				TType: oscar.TLVAuthorizationCookie,
 				Val:   sess.ID,
 			},
 		})
 	} else {
 		snacPayloadOut.AddTLVList([]oscar.TLV{
 			{
-				TType: TLVErrorSubcode,
+				TType: oscar.TLVErrorSubcode,
 				Val:   uint16(0x01),
 			},
 		})

+ 349 - 417
server/bucp_test.go

@@ -5,429 +5,361 @@ import (
 	"os"
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/stretchr/testify/assert"
 )
 
-func TestReceiveAndSendAuthChallenge_OK(t *testing.T) {
-	cfg := Config{}
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	newUser := User{
-		ScreenName: "the_screen_name",
-		AuthKey:    "the_auth_key",
-	}
-	if err := newUser.HashPassword("the_password"); err != nil {
-		assert.NoError(t, err)
-	}
-	if err := fs.UpsertUser(newUser); err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x06_BUCPChallengeRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: newUser.ScreenName})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendAuthChallenge(cfg, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPChallengeResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x07_BUCPChallengeResponse{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	assert.Equal(t, newUser.AuthKey, snacPayload.AuthKey)
-}
-
-func TestReceiveAndSendAuthChallenge_BadUser_DisableAuth(t *testing.T) {
-	cfg := Config{
-		DisableAuth: true,
-	}
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x06_BUCPChallengeRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: "bad-user"})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendAuthChallenge(cfg, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPChallengeResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x07_BUCPChallengeResponse{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	assert.NotEmpty(t, snacPayload.AuthKey)
-}
-
-func TestReceiveAndSendAuthChallenge_BadUser_EnableAuth(t *testing.T) {
-	cfg := Config{}
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x06_BUCPChallengeRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: "bad-user"})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendAuthChallenge(cfg, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPLoginResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	_, hasError := snacPayload.GetTLV(TLVErrorSubcode)
-	assert.True(t, hasError)
-}
-
-func TestReceiveAndSendBUCPLoginRequest_OK(t *testing.T) {
-	cfg := Config{}
-	sm := NewSessionManager()
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	newUser := User{
-		ScreenName: "the_screen_name",
-		AuthKey:    "the_auth_key",
-	}
-	if err := newUser.HashPassword("the_password"); err != nil {
-		assert.NoError(t, err)
-	}
-	if err := fs.UpsertUser(newUser); err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVPasswordHash, Val: newUser.PassHash})
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: newUser.ScreenName})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendBUCPLoginRequest(cfg, sm, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
+func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
+	userGoodPwd := User{
+		ScreenName: "sn_user_a",
+		AuthKey:    "auth_key_user",
+	}
+	assert.NoError(t, userGoodPwd.HashPassword("good_pwd"))
+	userBadPwd := userGoodPwd
+	assert.NoError(t, userBadPwd.HashPassword("bad_pwd"))
+
+	cases := []struct {
+		name            string
+		cfg             Config
+		userInDB        User
+		sessionUUID     uuid.UUID
+		inputSNAC       oscar.SNAC_0x17_0x02_BUCPLoginRequest
+		expectSnacFrame oscar.SnacFrame
+		expectSNACBody  oscar.SNAC_0x17_0x03_BUCPLoginResponse
+	}{
+		{
+			name: "login with valid password, expect OK login response",
+			cfg: Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   1234,
+			},
+			userInDB:    userGoodPwd,
+			sessionUUID: uuid.UUID{1, 2, 3},
+			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,
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPLoginResponse,
+			},
+			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(),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "login with bad password, expect OK login response (cfg.DisableAuth=true)",
+			cfg: Config{
+				OSCARHost:   "127.0.0.1",
+				BOSPort:     1234,
+				DisableAuth: true,
+			},
+			userInDB:    userGoodPwd,
+			sessionUUID: uuid.UUID{1, 2, 3},
+			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,
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPLoginResponse,
+			},
+			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(),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "login with bad password, expect failed login response (cfg.DisableAuth=false)",
+			cfg: Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   1234,
+			},
+			userInDB:    userGoodPwd,
+			sessionUUID: uuid.UUID{1, 2, 3},
+			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,
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPLoginResponse,
+			},
+			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						{
+							TType: oscar.TLVScreenName,
+							Val:   userBadPwd.ScreenName,
+						},
+						{
+							TType: oscar.TLVErrorSubcode,
+							Val:   uint16(0x01),
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			//
+			// initialize dependencies
+			//
+			const testFile string = "aim_test.db"
+			defer func() {
+				assert.NoError(t, os.Remove(testFile))
+			}()
+			fs, err := NewFeedbagStore(testFile)
+			if err != nil {
+				assert.NoError(t, err)
+			}
+			assert.NoError(t, fs.InsertUser(tc.userInDB))
+			sm := NewSessionManager()
+			//
+			// send input SNAC
+			//
+			input := &bytes.Buffer{}
+			var seq uint32
+			assert.NoError(t, writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, tc.inputSNAC, &seq, input))
+			//
+			// receive response
+			//
+			output := &bytes.Buffer{}
+			fnNewUUID := func() uuid.UUID {
+				return tc.sessionUUID
+			}
+			assert.NoError(t, ReceiveAndSendBUCPLoginRequest(tc.cfg, sm, fs, input, output, &seq, fnNewUUID))
+			flap := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flap, output))
+			//
+			// verify output SNAC frame
+			//
+			SnacFrame := oscar.SnacFrame{}
+			assert.NoError(t, oscar.Unmarshal(&SnacFrame, output))
+			assert.Equal(t, tc.expectSnacFrame, SnacFrame)
+			//
+			// 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)
+		})
 	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPLoginResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x03_BUCPLoginResponse{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	_, hasError := snacPayload.GetTLV(TLVErrorSubcode)
-	assert.False(t, hasError)
-}
-
-func TestReceiveAndSendBUCPLoginRequest_BadUser_EnableAuth(t *testing.T) {
-	cfg := Config{}
-	sm := NewSessionManager()
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	newUser := User{
-		ScreenName: "the_screen_name",
-		AuthKey:    "the_auth_key",
-	}
-	if err := newUser.HashPassword("the_password"); err != nil {
-		assert.NoError(t, err)
-	}
-	if err := fs.UpsertUser(newUser); err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVPasswordHash, Val: newUser.PassHash})
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: "bad-screen-name"})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendBUCPLoginRequest(cfg, sm, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPLoginResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	_, hasError := snacPayload.GetTLV(TLVErrorSubcode)
-	assert.True(t, hasError)
-}
-
-func TestReceiveAndSendBUCPLoginRequest_BadUser_DisableAuth(t *testing.T) {
-	cfg := Config{
-		DisableAuth: true,
-	}
-	sm := NewSessionManager()
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	newUser := User{
-		ScreenName: "the_screen_name",
-		AuthKey:    "the_auth_key",
-	}
-	if err := newUser.HashPassword("the_password"); err != nil {
-		assert.NoError(t, err)
-	}
-	if err := fs.UpsertUser(newUser); err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVPasswordHash, Val: newUser.PassHash})
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: "bad-screen-name"})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendBUCPLoginRequest(cfg, sm, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPLoginResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
-	}
-	_, hasError := snacPayload.GetTLV(TLVErrorSubcode)
-	assert.False(t, hasError)
 }
 
-func TestReceiveAndSendBUCPLoginRequest_BadPassword(t *testing.T) {
-	cfg := Config{}
-	sm := NewSessionManager()
-
-	const testFile string = "aim_test.db"
-	defer func() {
-		if err := os.Remove(testFile); err != nil {
-			assert.NoError(t, err)
-		}
-	}()
-
-	fs, err := NewFeedbagStore(testFile)
-	if err != nil {
-		assert.NoError(t, err)
-	}
-
-	newUser := User{
-		ScreenName: "the_screen_name",
-		AuthKey:    "the_auth_key",
-	}
-	if err := newUser.HashPassword("the_password"); err != nil {
-		assert.NoError(t, err)
-	}
-	if err := fs.UpsertUser(newUser); err != nil {
-		assert.NoError(t, err)
-	}
-
-	input := &bytes.Buffer{}
-
-	snacPayloadOut := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVPasswordHash, Val: []byte("bad_password")})
-	snacPayloadOut.AddTLV(oscar.TLV{TType: TLVScreenName, Val: newUser.ScreenName})
-
-	var seq uint32
-	writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, snacPayloadOut, &seq, input)
-
-	output := &bytes.Buffer{}
-
-	if err := ReceiveAndSendBUCPLoginRequest(cfg, sm, fs, input, output, &seq); err != nil {
-		assert.NoError(t, err)
-	}
-
-	flap := oscar.FlapFrame{}
-	if err := oscar.Unmarshal(&flap, output); err != nil {
-		assert.NoError(t, err)
-	}
-
-	snac := oscar.SnacFrame{}
-	if err := oscar.Unmarshal(&snac, output); err != nil {
-		assert.NoError(t, err)
-	}
-	expectSnacFrame := oscar.SnacFrame{
-		FoodGroup: BUCP,
-		SubGroup:  BUCPLoginResponse,
-	}
-	assert.Equal(t, expectSnacFrame, snac)
-
-	snacPayload := oscar.SNAC_0x17_0x02_BUCPLoginRequest{}
-	if err := oscar.Unmarshal(&snacPayload, output); err != nil {
-		assert.NoError(t, err)
+func TestReceiveAndSendAuthChallenge(t *testing.T) {
+	cases := []struct {
+		name            string
+		cfg             Config
+		userInDB        User
+		fnNewUUID       uuid.UUID
+		inputSNAC       oscar.SNAC_0x17_0x06_BUCPChallengeRequest
+		expectSnacFrame oscar.SnacFrame
+		expectSNACBody  any
+	}{
+		{
+			name: "login with valid username, expect OK login response",
+			cfg: Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   1234,
+			},
+			userInDB: User{
+				ScreenName: "sn_user_a",
+				AuthKey:    "auth_key_user_a",
+			},
+			fnNewUUID: uuid.UUID{1, 2, 3},
+			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						oscar.TLV{
+							TType: oscar.TLVScreenName, Val: "sn_user_a",
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPChallengeResponse,
+			},
+			expectSNACBody: oscar.SNAC_0x17_0x07_BUCPChallengeResponse{
+				AuthKey: "auth_key_user_a",
+			},
+		},
+		{
+			name: "login with invalid username, expect OK login response (cfg.DisableAuth=true)",
+			cfg: Config{
+				OSCARHost:   "127.0.0.1",
+				BOSPort:     1234,
+				DisableAuth: true,
+			},
+			userInDB: User{
+				ScreenName: "sn_user_a",
+				AuthKey:    "auth_key_user_a",
+			},
+			fnNewUUID: uuid.UUID{1, 2, 3},
+			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						oscar.TLV{
+							TType: oscar.TLVScreenName, Val: "sn_user_b",
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPChallengeResponse,
+			},
+			expectSNACBody: oscar.SNAC_0x17_0x07_BUCPChallengeResponse{
+				AuthKey: uuid.UUID{1, 2, 3}.String(),
+			},
+		},
+		{
+			name: "login with invalid username, expect failed login response (cfg.DisableAuth=false)",
+			cfg: Config{
+				OSCARHost: "127.0.0.1",
+				BOSPort:   1234,
+			},
+			userInDB: User{
+				ScreenName: "sn_user_a",
+				AuthKey:    "auth_key_user_a",
+			},
+			fnNewUUID: uuid.UUID{1, 2, 3},
+			inputSNAC: oscar.SNAC_0x17_0x06_BUCPChallengeRequest{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						oscar.TLV{
+							TType: oscar.TLVScreenName, Val: "sn_user_b",
+						},
+					},
+				},
+			},
+			expectSnacFrame: oscar.SnacFrame{
+				FoodGroup: BUCP,
+				SubGroup:  BUCPLoginResponse,
+			},
+			expectSNACBody: oscar.SNAC_0x17_0x03_BUCPLoginResponse{
+				TLVRestBlock: oscar.TLVRestBlock{
+					TLVList: oscar.TLVList{
+						{
+							TType: oscar.TLVErrorSubcode,
+							Val:   uint16(0x01),
+						},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			//
+			// initialize dependencies
+			//
+			const testFile string = "aim_test.db"
+			defer func() {
+				assert.NoError(t, os.Remove(testFile))
+			}()
+			fs, err := NewFeedbagStore(testFile)
+			if err != nil {
+				assert.NoError(t, err)
+			}
+			assert.NoError(t, fs.InsertUser(tc.userInDB))
+			//
+			// send input SNAC
+			//
+			input := &bytes.Buffer{}
+			var seq uint32
+			assert.NoError(t, writeOutSNAC(oscar.SnacFrame{}, oscar.SnacFrame{}, tc.inputSNAC, &seq, input))
+			//
+			// receive response
+			//
+			output := &bytes.Buffer{}
+			fnNewUUID := func() uuid.UUID {
+				return tc.fnNewUUID
+			}
+			assert.NoError(t, ReceiveAndSendAuthChallenge(tc.cfg, fs, input, output, &seq, fnNewUUID))
+			flap := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flap, output))
+			//
+			// verify output SNAC frame
+			//
+			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_0x17_0x07_BUCPChallengeResponse:
+				actual := oscar.SNAC_0x17_0x07_BUCPChallengeResponse{}
+				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)
+			default:
+				t.Fatalf("unexpected output SNAC type")
+			}
+		})
 	}
-	_, hasError := snacPayload.GetTLV(TLVErrorSubcode)
-	assert.True(t, hasError)
 }

+ 2 - 2
server/mgmt_api.go

@@ -17,7 +17,7 @@ func StartManagementAPI(fs *FeedbagStore) {
 			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
 		}
 	})
-
+	//todo make port configurable
 	port := 8080
 	fmt.Printf("Server is running on :%d...\n", port)
 	if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
@@ -51,7 +51,7 @@ func createUser(fs *FeedbagStore, w http.ResponseWriter, r *http.Request) {
 		http.Error(w, err.Error(), http.StatusBadRequest)
 		return
 	}
-
+	// todo does the request contain authkey?
 	newUser.HashPassword(newUser.Password)
 	if err := fs.InsertUser(newUser.User); err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)

+ 0 - 8
server/protocol.go

@@ -56,14 +56,6 @@ var (
 	CapChat, _ = uuid.MustParse("748F2420-6287-11D1-8222-444553540000").MarshalBinary()
 )
 
-const (
-	TLVScreenName          uint16 = 0x01
-	TLVReconnectHere       uint16 = 0x05
-	TLVAuthorizationCookie uint16 = 0x06
-	TLVErrorSubcode        uint16 = 0x08
-	TLVPasswordHash        uint16 = 0x25
-)
-
 type Config struct {
 	OSCARHost   string `envconfig:"OSCAR_HOST" required:"true"`
 	OSCARPort   int    `envconfig:"OSCAR_PORT" default:"5190"`

+ 0 - 15
server/session.go

@@ -3,7 +3,6 @@ package server
 import (
 	"errors"
 	"fmt"
-	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
 	"sync"
 	"time"
@@ -293,20 +292,6 @@ func makeSession() *Session {
 	}
 }
 
-func (s *SessionManager) NewSession(screenName string) (*Session, error) {
-	s.mapMutex.Lock()
-	defer s.mapMutex.Unlock()
-	id, err := uuid.NewUUID()
-	if err != nil {
-		return nil, err
-	}
-	sess := makeSession()
-	sess.ID = id.String()
-	sess.ScreenName = screenName
-	s.store[sess.ID] = sess
-	return sess, nil
-}
-
 func (s *SessionManager) NewSessionWithSN(sessID string, screenName string) *Session {
 	s.mapMutex.Lock()
 	defer s.mapMutex.Unlock()