Skip to content

Commit

Permalink
make chat work for AIM 2.x-3.5
Browse files Browse the repository at this point in the history
- implement SNAC(0x0D,0x03) RequestExchangeInfo
- rearrange TLV ordering in SNAC(0x0E,0x05) ChatChannelMsgToHost so
  that screen names appear correctly in chat window.
  • Loading branch information
mk6i committed Jun 1, 2024
1 parent bc2e8bc commit e78cfc3
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The following features are supported:
- [x] Away Messages
- [x] Buddy Icons (v4.x, v5.x)
- [x] Buddy List
- [x] Chat Rooms (v4.x, v5.x)
- [x] Chat Rooms
- [x] Instant Messaging
- [x] User Profiles
- [x] Blocking (v3.5+)
Expand Down
17 changes: 14 additions & 3 deletions foodgroup/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package foodgroup

import (
"context"
"errors"

"github.com/mk6i/retro-aim-server/state"
"github.com/mk6i/retro-aim-server/wire"
Expand Down Expand Up @@ -29,20 +30,30 @@ func (s ChatService) ChannelMsgToHost(ctx context.Context, sess *state.Session,
FoodGroup: wire.Chat,
SubGroup: wire.ChatChannelMsgToClient,
}

msg, hasMessage := inBody.Slice(wire.ChatTLVMessageInformation)
if !hasMessage {
return nil, errors.New("SNAC(0x0E,0x05) does not contain a message TLV")
}

bodyOut := wire.SNAC_0x0E_0x06_ChatChannelMsgToClient{
Cookie: inBody.Cookie,
Channel: inBody.Channel,
TLVRestBlock: wire.TLVRestBlock{
TLVList: inBody.TLVList,
TLVList: wire.TLVList{
// The order of these TLVs matters for AIM 2.x. if out of
// order, screen names do not appear with each chat message.
wire.NewTLV(wire.ChatTLVSenderInformation, sess.TLVUserInfo()),
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVMessageInformation, msg),
},
},
}
bodyOut.Append(wire.NewTLV(wire.ChatTLVSenderInformation, sess.TLVUserInfo()))

_, chatSessMgr, err := s.chatRegistry.Retrieve(sess.ChatRoomCookie())
if err != nil {
return nil, err
}

// send message to all the participants except sender
chatSessMgr.(ChatMessageRelayer).RelayToAllExcept(ctx, sess, wire.SNACMessage{
Frame: frameOut,
Expand Down
51 changes: 37 additions & 14 deletions foodgroup/chat_nav.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import (
"github.com/mk6i/retro-aim-server/wire"
)

var defaultExchangeCfg = wire.TLVBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatRoomTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatRoomTLVClassPerms, uint16(0x0010)),
wire.NewTLV(wire.ChatRoomTLVMaxNameLen, uint16(100)),
wire.NewTLV(wire.ChatRoomTLVFlags, uint16(15)),
wire.NewTLV(wire.ChatRoomTLVRoomName, "default exchange"),
wire.NewTLV(wire.ChatRoomTLVNavCreatePerms, uint8(2)),
wire.NewTLV(wire.ChatRoomTLVCharSet1, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang1, "en"),
wire.NewTLV(wire.ChatRoomTLVCharSet2, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang2, "en"),
},
}

// NewChatNavService creates a new instance of NewChatNavService.
func NewChatNavService(logger *slog.Logger, chatRegistry *state.ChatRegistry, newChatRoom func() state.ChatRoom, newChatSessMgr func() SessionManager) *ChatNavService {
return &ChatNavService{
Expand Down Expand Up @@ -44,20 +59,7 @@ func (s ChatNavService) RequestChatRights(_ context.Context, inFrame wire.SNACFr
wire.NewTLV(wire.ChatNavTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
Identifier: 4,
TLVBlock: wire.TLVBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatRoomTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatRoomTLVClassPerms, uint16(0x0010)),
wire.NewTLV(wire.ChatRoomTLVMaxNameLen, uint16(100)),
wire.NewTLV(wire.ChatRoomTLVFlags, uint16(15)),
wire.NewTLV(wire.ChatRoomTLVRoomName, "default exchange"),
wire.NewTLV(wire.ChatRoomTLVNavCreatePerms, uint8(2)),
wire.NewTLV(wire.ChatRoomTLVCharSet1, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang1, "en"),
wire.NewTLV(wire.ChatRoomTLVCharSet2, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang2, "en"),
},
},
TLVBlock: defaultExchangeCfg,
}),
},
},
Expand Down Expand Up @@ -143,3 +145,24 @@ func (s ChatNavService) RequestRoomInfo(_ context.Context, inFrame wire.SNACFram
},
}, nil
}

func (s ChatNavService) ExchangeInfo(_ context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) wire.SNACMessage {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.ChatNav,
SubGroup: wire.ChatNavNavInfo,
RequestID: inFrame.RequestID,
},
Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
TLVRestBlock: wire.TLVRestBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatNavTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
Identifier: inBody.Exchange,
TLVBlock: defaultExchangeCfg,
}),
},
},
},
}
}
44 changes: 44 additions & 0 deletions foodgroup/chat_nav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,47 @@ func TestChatNavService_RequestChatRights(t *testing.T) {

assert.Equal(t, want, have)
}

func TestChatNavService_ExchangeInfo(t *testing.T) {
svc := NewChatNavService(nil, nil, nil, nil)

frame := wire.SNACFrame{RequestID: 1234}
snac := wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo{
Exchange: 4,
}
have := svc.ExchangeInfo(nil, frame, snac)

want := wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.ChatNav,
SubGroup: wire.ChatNavNavInfo,
RequestID: frame.RequestID,
},
Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{
TLVRestBlock: wire.TLVRestBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatNavTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatNavTLVExchangeInfo, wire.SNAC_0x0D_0x09_TLVExchangeInfo{
Identifier: snac.Exchange,
TLVBlock: wire.TLVBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatRoomTLVMaxConcurrentRooms, uint8(10)),
wire.NewTLV(wire.ChatRoomTLVClassPerms, uint16(0x0010)),
wire.NewTLV(wire.ChatRoomTLVMaxNameLen, uint16(100)),
wire.NewTLV(wire.ChatRoomTLVFlags, uint16(15)),
wire.NewTLV(wire.ChatRoomTLVRoomName, "default exchange"),
wire.NewTLV(wire.ChatRoomTLVNavCreatePerms, uint8(2)),
wire.NewTLV(wire.ChatRoomTLVCharSet1, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang1, "en"),
wire.NewTLV(wire.ChatRoomTLVCharSet2, "us-ascii"),
wire.NewTLV(wire.ChatRoomTLVLang2, "en"),
},
},
}),
},
},
},
}

assert.Equal(t, want, have)
}
24 changes: 19 additions & 5 deletions foodgroup/chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Tag: wire.ChatTLVEnableReflectionFlag,
Value: []byte{},
},
{
Tag: wire.ChatTLVMessageInformation,
Value: []byte{},
},
},
},
},
Expand All @@ -70,10 +74,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Channel: 14,
TLVRestBlock: wire.TLVRestBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVEnableReflectionFlag, []byte{}),
wire.NewTLV(wire.ChatTLVSenderInformation,
newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVMessageInformation, []byte{}),
},
},
},
Expand All @@ -89,9 +93,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Channel: 14,
TLVRestBlock: wire.TLVRestBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatTLVSenderInformation,
newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVEnableReflectionFlag, []byte{}),
wire.NewTLV(wire.ChatTLVSenderInformation, newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
wire.NewTLV(wire.ChatTLVMessageInformation, []byte{}),
},
},
},
Expand All @@ -114,6 +119,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Tag: wire.ChatTLVPublicWhisperFlag,
Value: []byte{},
},
{
Tag: wire.ChatTLVMessageInformation,
Value: []byte{},
},
},
},
},
Expand All @@ -135,9 +144,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Channel: 14,
TLVRestBlock: wire.TLVRestBlock{
TLVList: wire.TLVList{
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVSenderInformation,
newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
wire.NewTLV(wire.ChatTLVPublicWhisperFlag, []byte{}),
wire.NewTLV(wire.ChatTLVMessageInformation, []byte{}),
},
},
},
Expand All @@ -160,6 +170,10 @@ func TestChatService_ChannelMsgToHost(t *testing.T) {
Tag: wire.ChatTLVPublicWhisperFlag,
Value: []byte{},
},
{
Tag: wire.ChatTLVMessageInformation,
Value: []byte{},
},
},
},
},
Expand Down
11 changes: 11 additions & 0 deletions server/oscar/handler/chat_nav.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

type ChatNavService interface {
CreateRoom(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error)
ExchangeInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) wire.SNACMessage
RequestChatRights(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
RequestRoomInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error)
}
Expand All @@ -38,6 +39,16 @@ func (rt ChatNavHandler) RequestChatRights(ctx context.Context, _ *state.Session
return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
}

func (rt ChatNavHandler) RequestExchangeInfo(ctx context.Context, _ *state.Session, inFrame wire.SNACFrame, r io.Reader, rw oscar.ResponseWriter) error {
inBody := wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo{}
if err := wire.Unmarshal(&inBody, r); err != nil {
return err
}
outSNAC := rt.ChatNavService.ExchangeInfo(ctx, inFrame, inBody)
rt.LogRequestAndResponse(ctx, inFrame, inBody, outSNAC.Frame, outSNAC.Body)
return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
}

func (rt ChatNavHandler) RequestRoomInfo(ctx context.Context, _ *state.Session, inFrame wire.SNACFrame, r io.Reader, rw oscar.ResponseWriter) error {
inBody := wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{}
if err := wire.Unmarshal(&inBody, r); err != nil {
Expand Down
36 changes: 36 additions & 0 deletions server/oscar/handler/chat_nav_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,39 @@ func TestChatNavHandler_RequestRoomInfo(t *testing.T) {

assert.NoError(t, h.RequestRoomInfo(nil, nil, input.Frame, buf, ss))
}

func TestChatNavHandler_RequestExchangeInfo(t *testing.T) {
input := wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.ChatNav,
SubGroup: wire.ChatNavRequestExchangeInfo,
},
Body: wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo{
Exchange: 4,
},
}
output := wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.ChatNav,
SubGroup: wire.ChatNavNavInfo,
},
Body: wire.SNAC_0x0D_0x09_ChatNavNavInfo{},
}

svc := newMockChatNavService(t)
svc.EXPECT().
ExchangeInfo(mock.Anything, input.Frame, input.Body).
Return(output)

h := NewChatNavHandler(svc, slog.Default())

ss := newMockResponseWriter(t)
ss.EXPECT().
SendSNAC(output.Frame, output.Body).
Return(nil)

buf := &bytes.Buffer{}
assert.NoError(t, wire.Marshal(input.Body, buf))

assert.NoError(t, h.RequestExchangeInfo(nil, nil, input.Frame, buf, ss))
}
48 changes: 48 additions & 0 deletions server/oscar/handler/mock_chat_nav_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions server/oscar/handler/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func NewBOSRouter(h Handlers) oscar.Router {

router.Register(wire.ChatNav, wire.ChatNavCreateRoom, h.ChatNavHandler.CreateRoom)
router.Register(wire.ChatNav, wire.ChatNavRequestChatRights, h.ChatNavHandler.RequestChatRights)
router.Register(wire.ChatNav, wire.ChatNavRequestExchangeInfo, h.ChatNavHandler.RequestExchangeInfo)
router.Register(wire.ChatNav, wire.ChatNavRequestRoomInfo, h.ChatNavHandler.RequestRoomInfo)

router.Register(wire.Feedbag, wire.FeedbagDeleteItem, h.FeedbagHandler.DeleteItem)
Expand Down Expand Up @@ -110,6 +111,7 @@ func NewChatNavRouter(h Handlers) oscar.Router {

router.Register(wire.ChatNav, wire.ChatNavCreateRoom, h.ChatNavHandler.CreateRoom)
router.Register(wire.ChatNav, wire.ChatNavRequestChatRights, h.ChatNavHandler.RequestChatRights)
router.Register(wire.ChatNav, wire.ChatNavRequestExchangeInfo, h.ChatNavHandler.RequestExchangeInfo)
router.Register(wire.ChatNav, wire.ChatNavRequestRoomInfo, h.ChatNavHandler.RequestRoomInfo)

router.Register(wire.OService, wire.OServiceClientOnline, h.OServiceChatNavHandler.ClientOnline)
Expand Down
5 changes: 5 additions & 0 deletions wire/snacs.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,10 @@ const (
ChatNavTLVRoomInfo uint16 = 0x0004
)

type SNAC_0x0D_0x03_ChatNavRequestExchangeInfo struct {
Exchange uint16
}

type SNAC_0x0D_0x04_ChatNavRequestRoomInfo struct {
Exchange uint16
Cookie string `len_prefix:"uint8"`
Expand Down Expand Up @@ -755,6 +759,7 @@ const (

ChatTLVPublicWhisperFlag uint16 = 0x01
ChatTLVSenderInformation uint16 = 0x03
ChatTLVMessageInformation uint16 = 0x05
ChatTLVEnableReflectionFlag uint16 = 0x06

// referenced from protocols/oscar/family_chatnav.c in lib purple
Expand Down

0 comments on commit e78cfc3

Please sign in to comment.