Browse Source

migrate FlapClient to wire package and export it

Now that FlapClient is public, you can import FlapClient into a go
project. This allows you to create AIM clients, including AIM bots.
Mike 2 năm trước cách đây
mục cha
commit
15ea0eff41

+ 5 - 6
server/oscar/alert.go

@@ -56,13 +56,12 @@ func (rt AlertServer) Start() {
 }
 
 func (rt AlertServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
-	flapc := &flapClient{
-		r:        rwc,
-		sequence: 100,
-		w:        rwc,
-	}
+	flapc := wire.NewFlapClient(100, rwc, rwc)
 
-	flap, err := flapc.SignonHandshake()
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return err
+	}
+	flap, err := flapc.ReceiveSignonFrame()
 	if err != nil {
 		return err
 	}

+ 1 - 3
server/oscar/alert_test.go

@@ -57,9 +57,7 @@ func TestAlertServer_handleNewConnection(t *testing.T) {
 		assert.NoError(t, wire.Unmarshal(&body, buf))
 
 		// send the first request that should get relayed to BOSRouter.Handle
-		flapc := flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		frame = wire.SNACFrame{
 			FoodGroup: wire.OService,
 			SubGroup:  wire.OServiceClientOnline,

+ 6 - 7
server/oscar/auth.go

@@ -62,12 +62,11 @@ func (rt AuthServer) Start() {
 func (rt AuthServer) handleNewConnection(rwc io.ReadWriteCloser) error {
 	defer rwc.Close()
 
-	flapc := flapClient{
-		r:        rwc,
-		sequence: 100,
-		w:        rwc,
+	flapc := wire.NewFlapClient(100, rwc, rwc)
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return err
 	}
-	signonFrame, err := flapc.SignonHandshake()
+	signonFrame, err := flapc.ReceiveSignonFrame()
 	if err != nil {
 		return err
 	}
@@ -79,7 +78,7 @@ func (rt AuthServer) handleNewConnection(rwc io.ReadWriteCloser) error {
 	return rt.processBUCPAuth(flapc, err)
 }
 
-func (rt AuthServer) processFLAPAuth(signonFrame wire.FLAPSignonFrame, flapc flapClient) error {
+func (rt AuthServer) processFLAPAuth(signonFrame wire.FLAPSignonFrame, flapc *wire.FlapClient) error {
 	tlv, err := rt.AuthService.FLAPLogin(signonFrame, uuid.New, state.NewStubUser)
 	if err != nil {
 		return err
@@ -87,7 +86,7 @@ func (rt AuthServer) processFLAPAuth(signonFrame wire.FLAPSignonFrame, flapc fla
 	return flapc.SendSignoffFrame(tlv)
 }
 
-func (rt AuthServer) processBUCPAuth(flapc flapClient, err error) error {
+func (rt AuthServer) processBUCPAuth(flapc *wire.FlapClient, err error) error {
 	challengeRequest := wire.SNAC_0x17_0x06_BUCPChallengeRequest{}
 	if err := flapc.ReceiveSNAC(&wire.SNACFrame{}, &challengeRequest); err != nil {
 		return err

+ 1 - 4
server/oscar/auth_test.go

@@ -41,10 +41,7 @@ func TestBUCPAuthService_handleNewConnection(t *testing.T) {
 		assert.NoError(t, err)
 
 		// > send SNAC_0x17_0x06_BUCPChallengeRequest
-		flapc := flapClient{
-			r: serverReader,
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, serverReader, serverWriter)
 		frame := wire.SNACFrame{
 			FoodGroup: wire.BUCP,
 			SubGroup:  wire.BUCPChallengeRequest,

+ 5 - 6
server/oscar/bos.go

@@ -61,13 +61,12 @@ func (rt BOSServer) Start() {
 }
 
 func (rt BOSServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
-	flapc := &flapClient{
-		r:        rwc,
-		sequence: 100,
-		w:        rwc,
-	}
+	flapc := wire.NewFlapClient(100, rwc, rwc)
 
-	flap, err := flapc.SignonHandshake()
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return err
+	}
+	flap, err := flapc.ReceiveSignonFrame()
 	if err != nil {
 		return err
 	}

+ 4 - 5
server/oscar/bos_test.go

@@ -7,10 +7,11 @@ import (
 	"log/slog"
 	"testing"
 
-	"github.com/mk6i/retro-aim-server/state"
-	"github.com/mk6i/retro-aim-server/wire"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
+
+	"github.com/mk6i/retro-aim-server/state"
+	"github.com/mk6i/retro-aim-server/wire"
 )
 
 // pipeRWC provides a mock for ReadWriteCloser that uses pipes instead of TCP
@@ -70,9 +71,7 @@ func TestBOSService_handleNewConnection(t *testing.T) {
 		assert.NoError(t, wire.Unmarshal(&body, buf))
 
 		// send the first request that should get relayed to BOSRouter.Handle
-		flapc := flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		frame = wire.SNACFrame{
 			FoodGroup: wire.OService,
 			SubGroup:  wire.OServiceClientOnline,

+ 4 - 5
server/oscar/chat.go

@@ -53,12 +53,11 @@ func (rt ChatServer) Start() {
 }
 
 func (rt ChatServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
-	flapc := &flapClient{
-		r:        rwc,
-		sequence: 100,
-		w:        rwc,
+	flapc := wire.NewFlapClient(100, rwc, rwc)
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return err
 	}
-	flap, err := flapc.SignonHandshake()
+	flap, err := flapc.ReceiveSignonFrame()
 	if err != nil {
 		return err
 	}

+ 5 - 6
server/oscar/chat_nav.go

@@ -55,13 +55,12 @@ func (rt ChatNavServer) Start() {
 }
 
 func (rt ChatNavServer) handleNewConnection(ctx context.Context, rwc io.ReadWriteCloser) error {
-	flapc := &flapClient{
-		r:        rwc,
-		sequence: 100,
-		w:        rwc,
-	}
+	flapc := wire.NewFlapClient(100, rwc, rwc)
 
-	flap, err := flapc.SignonHandshake()
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return err
+	}
+	flap, err := flapc.ReceiveSignonFrame()
 	if err != nil {
 		return err
 	}

+ 1 - 3
server/oscar/chat_nav_test.go

@@ -57,9 +57,7 @@ func TestChatNavServer_handleNewConnection(t *testing.T) {
 		assert.NoError(t, wire.Unmarshal(&body, buf))
 
 		// send the first request that should get relayed to Handler
-		flapc := flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		frame = wire.SNACFrame{
 			FoodGroup: wire.OService,
 			SubGroup:  wire.OServiceClientOnline,

+ 1 - 3
server/oscar/chat_test.go

@@ -57,9 +57,7 @@ func TestChatService_handleNewConnection(t *testing.T) {
 		assert.NoError(t, wire.Unmarshal(&body, buf))
 
 		// send the first request that should get relayed to BOSRouter.Handle
-		flapc := flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		frame = wire.SNACFrame{
 			FoodGroup: wire.Chat,
 			SubGroup:  wire.ChatNavNavInfo,

+ 1 - 138
server/oscar/connection.go

@@ -19,143 +19,6 @@ type incomingMessage struct {
 	payload *bytes.Buffer
 }
 
-type flapClient struct {
-	sequence uint32
-	w        io.Writer
-	r        io.Reader
-}
-
-func (f *flapClient) SignonHandshake() (wire.FLAPSignonFrame, error) {
-	// send FLAPFrameSignon to client
-	flap := wire.FLAPFrame{
-		StartMarker:   42,
-		FrameType:     wire.FLAPFrameSignon,
-		Sequence:      uint16(f.sequence),
-		PayloadLength: 4, // size of FLAPSignonFrame
-	}
-	if err := wire.Marshal(flap, f.w); err != nil {
-		return wire.FLAPSignonFrame{}, err
-	}
-	flapSignonFrameOut := wire.FLAPSignonFrame{
-		FLAPVersion: 1,
-	}
-	if err := wire.Marshal(flapSignonFrameOut, f.w); err != nil {
-		return wire.FLAPSignonFrame{}, err
-	}
-
-	// receive FLAPFrameSignon from client
-	flap = wire.FLAPFrame{}
-	if err := wire.Unmarshal(&flap, f.r); err != nil {
-		return wire.FLAPSignonFrame{}, err
-	}
-	buf, err := flap.ReadBody(f.r)
-	if err != nil {
-		return wire.FLAPSignonFrame{}, err
-	}
-	flapSignonFrameIn := wire.FLAPSignonFrame{}
-	if err := wire.Unmarshal(&flapSignonFrameIn, buf); err != nil {
-		return wire.FLAPSignonFrame{}, err
-	}
-
-	f.sequence++
-
-	return flapSignonFrameIn, nil
-}
-
-// SendSignoffFrame sends a sign-off FLAP frame with attached TLVs as the last
-// request sent in the FLAP auth flow. This is unrelated to the Disconnect()
-// method, which sends a sign-off frame to terminate a BOS connection.
-// todo: combine this method with Disconnect()
-func (f *flapClient) SendSignoffFrame(tlvs wire.TLVRestBlock) error {
-	tlvBuf := &bytes.Buffer{}
-	if err := wire.Marshal(tlvs, tlvBuf); err != nil {
-		return err
-	}
-
-	flap := wire.FLAPFrame{
-		StartMarker:   42,
-		FrameType:     wire.FLAPFrameSignoff,
-		Sequence:      uint16(f.sequence),
-		PayloadLength: uint16(tlvBuf.Len()),
-	}
-
-	if err := wire.Marshal(flap, f.w); err != nil {
-		return err
-	}
-
-	expectLen := tlvBuf.Len()
-	c, err := f.w.Write(tlvBuf.Bytes())
-	if err != nil {
-		return err
-	}
-	if c != expectLen {
-		panic("did not write the expected # of bytes")
-	}
-
-	f.sequence++
-	return nil
-}
-
-func (f *flapClient) SendSNAC(frame wire.SNACFrame, body any) error {
-	snacBuf := &bytes.Buffer{}
-	if err := wire.Marshal(frame, snacBuf); err != nil {
-		return err
-	}
-	if err := wire.Marshal(body, snacBuf); err != nil {
-		return err
-	}
-
-	flap := wire.FLAPFrame{
-		StartMarker:   42,
-		FrameType:     wire.FLAPFrameData,
-		Sequence:      uint16(f.sequence),
-		PayloadLength: uint16(snacBuf.Len()),
-	}
-
-	if err := wire.Marshal(flap, f.w); err != nil {
-		return err
-	}
-
-	expectLen := snacBuf.Len()
-	c, err := f.w.Write(snacBuf.Bytes())
-	if err != nil {
-		return err
-	}
-	if c != expectLen {
-		panic("did not write the expected # of bytes")
-	}
-
-	f.sequence++
-	return nil
-}
-
-func (f *flapClient) ReceiveSNAC(frame *wire.SNACFrame, body any) error {
-	flap := wire.FLAPFrame{}
-	if err := wire.Unmarshal(&flap, f.r); err != nil {
-		return err
-	}
-	buf, err := flap.ReadBody(f.r)
-	if err != nil {
-		return err
-	}
-	if err := wire.Unmarshal(frame, buf); err != nil {
-		return err
-	}
-	return wire.Unmarshal(body, buf)
-}
-
-func (f *flapClient) Disconnect() error {
-	// gracefully disconnect so that the client does not try to
-	// reconnect when the connection closes.
-	flap := wire.FLAPFrame{
-		StartMarker:   42,
-		FrameType:     wire.FLAPFrameSignoff,
-		Sequence:      uint16(f.sequence),
-		PayloadLength: uint16(0),
-	}
-	return wire.Marshal(flap, f.w)
-}
-
 func sendInvalidSNACErr(frameIn wire.SNACFrame, rw ResponseWriter) error {
 	frameOut := wire.SNACFrame{
 		FoodGroup: frameIn.FoodGroup,
@@ -200,7 +63,7 @@ func consumeFLAPFrames(r io.Reader, msgCh chan incomingMessage, errCh chan error
 // or when the session closes.
 //
 // todo: this method has too many params and should be folded into a new type
-func dispatchIncomingMessages(ctx context.Context, sess *state.Session, flapc *flapClient, r io.Reader, logger *slog.Logger, router Handler, config config.Config) error {
+func dispatchIncomingMessages(ctx context.Context, sess *state.Session, flapc *wire.FlapClient, r io.Reader, logger *slog.Logger, router Handler, config config.Config) error {
 	// buffered so that the go routine has room to exit
 	msgCh := make(chan incomingMessage, 1)
 	readErrCh := make(chan error, 1)

+ 6 - 11
server/oscar/connection_test.go

@@ -7,11 +7,12 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+
 	"github.com/mk6i/retro-aim-server/config"
 	"github.com/mk6i/retro-aim-server/state"
 	"github.com/mk6i/retro-aim-server/wire"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/mock"
 )
 
 func TestHandleChatConnection_MessageRelay(t *testing.T) {
@@ -23,9 +24,7 @@ func TestHandleChatConnection_MessageRelay(t *testing.T) {
 	serverReader, _ := io.Pipe()
 	clientReader, serverWriter := io.Pipe()
 	go func() {
-		flapc := &flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		err := dispatchIncomingMessages(context.Background(), sess, flapc, serverReader, slog.Default(), nil, config.Config{})
 		assert.NoError(t, err)
 	}()
@@ -145,16 +144,12 @@ func TestHandleChatConnection_ClientRequest(t *testing.T) {
 	serverReader, clientWriter := io.Pipe()
 	clientReader, serverWriter := io.Pipe()
 	go func() {
-		flapc := &flapClient{
-			w: serverWriter,
-		}
+		flapc := wire.NewFlapClient(0, nil, serverWriter)
 		assert.NoError(t, dispatchIncomingMessages(context.Background(), sess, flapc, serverReader, slog.Default(), router, config.Config{}))
 	}()
 
 	// send client messages
-	flapc := flapClient{
-		w: clientWriter,
-	}
+	flapc := wire.NewFlapClient(0, nil, clientWriter)
 	for _, msg := range inboundMsgs {
 		err := flapc.SendSNAC(msg.Frame, msg.Body)
 		assert.NoError(t, err)

+ 170 - 0
wire/frames.go

@@ -51,3 +51,173 @@ type SNACMessage struct {
 	Frame SNACFrame
 	Body  any
 }
+
+// NewFlapClient creates a new FLAP client instance. startSeq is the initial
+// sequence value, which is typically 0. r receives FLAP messages, w writes
+// FLAP messages.
+func NewFlapClient(startSeq uint32, r io.Reader, w io.Writer) *FlapClient {
+	return &FlapClient{
+		sequence: startSeq,
+		r:        r,
+		w:        w,
+	}
+}
+
+// FlapClient sends and receive FLAP frames to and from the server. It ensures
+// that the message sequence numbers are properly incremented after sending
+// each successive message.
+type FlapClient struct {
+	sequence uint32
+	r        io.Reader
+	w        io.Writer
+}
+
+// SendSignonFrame sends a signon FLAP frame containing a list of TLVs to
+// authenticate or initiate a session.
+func (f *FlapClient) SendSignonFrame(tlvs []TLV) error {
+	signonFrame := FLAPSignonFrame{
+		FLAPVersion: 1,
+	}
+	if len(tlvs) > 0 {
+		signonFrame.AppendList(tlvs)
+	}
+	buf := &bytes.Buffer{}
+	if err := Marshal(signonFrame, buf); err != nil {
+		return err
+	}
+
+	flap := FLAPFrame{
+		StartMarker:   42,
+		FrameType:     FLAPFrameSignon,
+		Sequence:      uint16(f.sequence),
+		PayloadLength: uint16(buf.Len()),
+	}
+	if err := Marshal(flap, f.w); err != nil {
+		return err
+	}
+
+	if _, err := f.w.Write(buf.Bytes()); err != nil {
+		return err
+	}
+
+	f.sequence++
+
+	return nil
+}
+
+// ReceiveSignonFrame receives a signon FLAP response message.
+func (f *FlapClient) ReceiveSignonFrame() (FLAPSignonFrame, error) {
+	flap := FLAPFrame{}
+	if err := Unmarshal(&flap, f.r); err != nil {
+		return FLAPSignonFrame{}, err
+	}
+
+	buf, err := flap.ReadBody(f.r)
+	if err != nil {
+		return FLAPSignonFrame{}, err
+	}
+
+	signonFrame := FLAPSignonFrame{}
+	if err := Unmarshal(&signonFrame, buf); err != nil {
+		return FLAPSignonFrame{}, err
+	}
+
+	return signonFrame, nil
+}
+
+// SendSignoffFrame sends a sign-off FLAP frame with attached TLVs as the last
+// request sent in the FLAP auth flow. This is unrelated to the Disconnect()
+// method, which sends a sign-off frame to terminate a BOS connection.
+// todo: combine this method with Disconnect()
+func (f *FlapClient) SendSignoffFrame(tlvs TLVRestBlock) error {
+	tlvBuf := &bytes.Buffer{}
+	if err := Marshal(tlvs, tlvBuf); err != nil {
+		return err
+	}
+
+	flap := FLAPFrame{
+		StartMarker:   42,
+		FrameType:     FLAPFrameSignoff,
+		Sequence:      uint16(f.sequence),
+		PayloadLength: uint16(tlvBuf.Len()),
+	}
+
+	if err := Marshal(flap, f.w); err != nil {
+		return err
+	}
+
+	expectLen := tlvBuf.Len()
+	c, err := f.w.Write(tlvBuf.Bytes())
+	if err != nil {
+		return err
+	}
+	if c != expectLen {
+		panic("did not write the expected # of bytes")
+	}
+
+	f.sequence++
+	return nil
+}
+
+// SendSNAC sends a SNAC message wrapped in a FLAP frame.
+func (f *FlapClient) SendSNAC(frame SNACFrame, body any) error {
+	snacBuf := &bytes.Buffer{}
+	if err := Marshal(frame, snacBuf); err != nil {
+		return err
+	}
+	if err := Marshal(body, snacBuf); err != nil {
+		return err
+	}
+
+	flap := FLAPFrame{
+		StartMarker:   42,
+		FrameType:     FLAPFrameData,
+		Sequence:      uint16(f.sequence),
+		PayloadLength: uint16(snacBuf.Len()),
+	}
+
+	if err := Marshal(flap, f.w); err != nil {
+		return err
+	}
+
+	expectLen := snacBuf.Len()
+	c, err := f.w.Write(snacBuf.Bytes())
+	if err != nil {
+		return err
+	}
+	if c != expectLen {
+		panic("did not write the expected # of bytes")
+	}
+
+	f.sequence++
+	return nil
+}
+
+// ReceiveSNAC receives a SNAC message wrapped in a FLAP frame.
+func (f *FlapClient) ReceiveSNAC(frame *SNACFrame, body any) error {
+	flap := FLAPFrame{}
+	if err := Unmarshal(&flap, f.r); err != nil {
+		return err
+	}
+	buf, err := flap.ReadBody(f.r)
+	if err != nil {
+		return err
+	}
+	if err := Unmarshal(frame, buf); err != nil {
+		return err
+	}
+	return Unmarshal(body, buf)
+}
+
+// Disconnect sends a signoff FLAP frame.
+func (f *FlapClient) Disconnect() error {
+	// gracefully disconnect so that the client does not try to
+	// reconnect when the connection closes.
+	flap := FLAPFrame{
+		StartMarker:   42,
+		FrameType:     FLAPFrameSignoff,
+		Sequence:      uint16(f.sequence),
+		PayloadLength: uint16(0),
+	}
+	return Marshal(flap, f.w)
+}