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

add helpers for dealing with ICBM message text

Mike 2 лет назад
Родитель
Сommit
a4d8d7203b
3 измененных файлов с 101 добавлено и 11 удалено
  1. 2 2
      foodgroup/icbm.go
  2. 7 7
      foodgroup/icbm_test.go
  3. 92 2
      wire/snacs.go

+ 2 - 2
foodgroup/icbm.go

@@ -111,7 +111,7 @@ func (s ICBMService) ChannelMsgToHost(ctx context.Context, sess *state.Session,
 		},
 	}
 	// copy over TLVs from sender SNAC to recipient SNAC verbatim. this
-	// includes ICBMTLVTagRequestHostAck, which is ignored by the client, as
+	// includes ICBMTLVRequestHostAck, which is ignored by the client, as
 	// far as I can tell.
 	clientIM.AppendList(inBody.TLVRestBlock.TLVList)
 
@@ -123,7 +123,7 @@ func (s ICBMService) ChannelMsgToHost(ctx context.Context, sess *state.Session,
 		Body: clientIM,
 	})
 
-	if _, requestedConfirmation := inBody.TLVRestBlock.Slice(wire.ICBMTLVTagRequestHostAck); !requestedConfirmation {
+	if _, requestedConfirmation := inBody.TLVRestBlock.Slice(wire.ICBMTLVRequestHostAck); !requestedConfirmation {
 		// don't ack message
 		return nil, nil
 	}

+ 7 - 7
foodgroup/icbm_test.go

@@ -44,7 +44,7 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagRequestHostAck,
+								Tag:   wire.ICBMTLVRequestHostAck,
 								Value: []byte{},
 							},
 						},
@@ -64,11 +64,11 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagsWantEvents,
+								Tag:   wire.ICBMTLVWantEvents,
 								Value: []byte{},
 							},
 							{
-								Tag:   wire.ICBMTLVTagRequestHostAck,
+								Tag:   wire.ICBMTLVRequestHostAck,
 								Value: []byte{},
 							},
 						},
@@ -115,7 +115,7 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagsWantEvents,
+								Tag:   wire.ICBMTLVWantEvents,
 								Value: []byte{},
 							},
 						},
@@ -138,7 +138,7 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagRequestHostAck,
+								Tag:   wire.ICBMTLVRequestHostAck,
 								Value: []byte{},
 							},
 						},
@@ -170,7 +170,7 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagRequestHostAck,
+								Tag:   wire.ICBMTLVRequestHostAck,
 								Value: []byte{},
 							},
 						},
@@ -202,7 +202,7 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: wire.TLVList{
 							{
-								Tag:   wire.ICBMTLVTagRequestHostAck,
+								Tag:   wire.ICBMTLVRequestHostAck,
 								Value: []byte{},
 							},
 						},

+ 92 - 2
wire/snacs.go

@@ -3,6 +3,7 @@ package wire
 import (
 	"bytes"
 	"errors"
+	"fmt"
 )
 
 // ErrUnsupportedFoodGroup indicates that a foodgroup value is either invalid
@@ -486,10 +487,44 @@ const (
 	ICBMClientEvent        uint16 = 0x0014
 	ICBMSinReply           uint16 = 0x0017
 
-	ICBMTLVTagRequestHostAck uint16 = 0x03
-	ICBMTLVTagsWantEvents    uint16 = 0x0B
+	ICBMTLVAOLIMData      uint16 = 0x02
+	ICBMTLVRequestHostAck uint16 = 0x03
+	ICBMTLVAutoResponse   uint16 = 0x04
+	ICBMTLVData           uint16 = 0x05
+	ICBMTLVStore          uint16 = 0x06
+	ICBMTLVICQblob        uint16 = 0x07
+	ICBMTLVAvatarInfo     uint16 = 0x08
+	ICBMTLVWantAvatar     uint16 = 0x09
+	ICBMTLVMultiUser      uint16 = 0x0A
+	ICBMTLVWantEvents     uint16 = 0x0B
+	ICBMTLVSubscriptions  uint16 = 0x0C
+	ICBMTLVBART           uint16 = 0x0D
+	ICBMTLVHostImID       uint16 = 0x10
+	ICBMTLVHostImArgs     uint16 = 0x11
+	ICBMTLVSendTime       uint16 = 0x16
+	ICBMTLVFriendlyName   uint16 = 0x17
+	ICBMTLVAnonymous      uint16 = 0x18
+	ICBMTLVWidgetName     uint16 = 0x19
+
+	ICBMMessageEncodingASCII   uint16 = 0x00 // ANSI ASCII -- ISO 646
+	ICBMMessageEncodingUnicode uint16 = 0x02 // ISO 10646.USC-2 Unicode
+	ICBMMessageEncodingLatin1  uint16 = 0x03 // ISO 8859-1
 )
 
+// ICBMFragment represents an ICBM message component.
+type ICBMFragment struct {
+	ID      uint8
+	Version uint8
+	Payload []byte `len_prefix:"uint16"`
+}
+
+// ICBMMessage represents the text component of an ICBM message.
+type ICBMMessage struct {
+	Charset  uint16
+	Language uint16
+	Text     []byte
+}
+
 type SNAC_0x04_0x02_ICBMAddParameters struct {
 	Channel              uint16
 	ICBMFlags            uint32
@@ -515,6 +550,35 @@ type SNAC_0x04_0x06_ICBMChannelMsgToHost struct {
 	TLVRestBlock
 }
 
+// ComposeMessage inserts message text into SNAC(0x04,0x06). It populates TLV
+// 0x02 with fragments that contain the message text and requisite metadata.
+func (m *SNAC_0x04_0x06_ICBMChannelMsgToHost) ComposeMessage(text string) error {
+	msg := ICBMMessage{
+		Charset:  ICBMMessageEncodingASCII,
+		Language: 0, // not clear what this means, but it works
+		Text:     []byte(text),
+	}
+	msgBuf := bytes.Buffer{}
+	if err := Marshal(msg, &msgBuf); err != nil {
+		return fmt.Errorf("unable to marshal ICBM message: %w", err)
+	}
+
+	m.Append(NewTLV(ICBMTLVAOLIMData, []ICBMFragment{
+		{
+			ID:      5, // 5 = capabilities
+			Version: 1,
+			Payload: []byte{1, 1, 2}, // 1 = text
+		},
+		{
+			ID:      1, // 1 = message text
+			Version: 1,
+			Payload: msgBuf.Bytes(),
+		},
+	}))
+
+	return nil
+}
+
 type SNAC_0x04_0x07_ICBMChannelMsgToClient struct {
 	Cookie    uint64
 	ChannelID uint16
@@ -522,6 +586,32 @@ type SNAC_0x04_0x07_ICBMChannelMsgToClient struct {
 	TLVRestBlock
 }
 
+// ExtractMessageText extracts the message text from SNAC(0x04,0x07).
+func (s SNAC_0x04_0x07_ICBMChannelMsgToClient) ExtractMessageText() (string, error) {
+	fragment, ok := s.Slice(ICBMTLVAOLIMData)
+	if !ok {
+		return "", errors.New("ICBM message does not contain a fragment")
+	}
+
+	var frags []ICBMFragment
+	if err := Unmarshal(&frags, bytes.NewBuffer(fragment)); err != nil {
+		return "", fmt.Errorf("unable to unmarshal ICBM fragment: %w", err)
+	}
+
+	for _, frag := range frags {
+		if frag.ID == 1 { // 1 = message text
+			msg := ICBMMessage{}
+			err := Unmarshal(&msg, bytes.NewBuffer(frag.Payload))
+			if err != nil {
+				err = fmt.Errorf("unable to unmarshal ICBM message: %w", err)
+			}
+			return string(msg.Text), err
+		}
+	}
+
+	return "", errors.New("unable to find message fragment")
+}
+
 type SNAC_0x04_0x08_ICBMEvilRequest struct {
 	SendAs     uint16
 	ScreenName string `len_prefix:"uint8"`