mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2025-02-28 10:22:35 -05:00
store messages sent through ICBM
This commit is contained in:
parent
ed375e315c
commit
3e69ba3e4c
9 changed files with 280 additions and 8 deletions
|
@ -6,7 +6,6 @@ import (
|
|||
"aim-oscar/util"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -73,9 +72,8 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
|||
}
|
||||
|
||||
onlineSnac := oscar.NewSNAC(1, 0xf)
|
||||
uin := fmt.Sprint(user.UIN)
|
||||
onlineSnac.Data.WriteUint8(uint8(len(uin)))
|
||||
onlineSnac.Data.WriteString(uin)
|
||||
onlineSnac.Data.WriteUint8(uint8(len(user.Username)))
|
||||
onlineSnac.Data.WriteString(user.Username)
|
||||
onlineSnac.Data.WriteUint16(0) // warning level
|
||||
|
||||
user.Status = "active"
|
||||
|
|
105
0x04_ICBM.go
105
0x04_ICBM.go
|
@ -1,10 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"aim-oscar/models"
|
||||
"aim-oscar/oscar"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"log"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
|
@ -85,6 +87,109 @@ func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC)
|
|||
channelFlap.Data.WriteBinary(channelSnac)
|
||||
session.Send(channelFlap)
|
||||
|
||||
return ctx, nil
|
||||
|
||||
// Client wants to send a message to someone through the server
|
||||
case 0x06:
|
||||
user := models.UserFromContext(ctx)
|
||||
if user == nil {
|
||||
return ctx, errors.New("context should have User")
|
||||
}
|
||||
|
||||
msgID, _ := snac.Data.ReadUint64()
|
||||
msgChannel, _ := snac.Data.ReadUint16()
|
||||
to, _ := snac.Data.ReadLPString()
|
||||
|
||||
if msgChannel != 1 {
|
||||
log.Printf("Message for unsupported channel %d", msgChannel)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not unmarshal message tlvs")
|
||||
}
|
||||
|
||||
messageTLV := oscar.FindTLV(tlvs, 0x2)
|
||||
if messageTLV == nil {
|
||||
return ctx, errors.New("missing messageTLV 0x2")
|
||||
}
|
||||
|
||||
// Parse fragment (array of required capabilities, yawn)
|
||||
messageTLVData := oscar.Buffer{}
|
||||
messageTLVData.Write(messageTLV.Data)
|
||||
|
||||
fragmentNum, err := messageTLVData.ReadUint8()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read fragment identifier")
|
||||
} else if fragmentNum != 5 {
|
||||
return ctx, errors.New("expected first fragment identifier to be 5")
|
||||
}
|
||||
|
||||
fragmentVersion, err := messageTLVData.ReadUint8()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read fragment version")
|
||||
} else if fragmentVersion != 1 {
|
||||
return ctx, errors.New("expected first fragment version to be 1")
|
||||
}
|
||||
|
||||
fragmentLength, err := messageTLVData.ReadUint16()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read fragment data length")
|
||||
}
|
||||
|
||||
// Skip over all the capabilities
|
||||
messageTLVData.Seek(int(fragmentLength))
|
||||
|
||||
// This should be the start of the message contents fragment
|
||||
fragmentNum, err = messageTLVData.ReadUint8()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read fragment identifier")
|
||||
} else if fragmentNum != 1 {
|
||||
return ctx, errors.New("expected second fragment identifier to be 1")
|
||||
}
|
||||
|
||||
fragmentVersion, err = messageTLVData.ReadUint8()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read fragment version")
|
||||
} else if fragmentVersion != 1 {
|
||||
return ctx, errors.New("expected second fragment version to be 1")
|
||||
}
|
||||
|
||||
fragmentLength, err = messageTLVData.ReadUint16()
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read second fragment data length")
|
||||
}
|
||||
|
||||
// Skip over the charset + language
|
||||
messageTLVData.Seek(4)
|
||||
|
||||
messageContents := make([]byte, fragmentLength-4)
|
||||
n, err := messageTLVData.Read(messageContents)
|
||||
if err != nil {
|
||||
return ctx, errors.Wrap(err, "could not read message contents from fragment")
|
||||
}
|
||||
if n < int(fragmentLength)-4 {
|
||||
return ctx, errors.New("read insufficient data from message fragment")
|
||||
}
|
||||
|
||||
if err = models.InsertMessage(ctx, db, msgID, user.Username, to, string(messageContents)); err != nil {
|
||||
return ctx, errors.Wrap(err, "could not insert message")
|
||||
}
|
||||
|
||||
// The Client usually wants a response that the server got the message. It checks that the message
|
||||
// back has the same message ID that was sent and the user it was sent to.
|
||||
ackTLV := oscar.FindTLV(tlvs, 3)
|
||||
if ackTLV != nil {
|
||||
ackSnac := oscar.NewSNAC(4, 0xc)
|
||||
ackSnac.Data.WriteUint64(msgID)
|
||||
ackSnac.Data.WriteUint16(2)
|
||||
ackSnac.Data.WriteLPString(user.Username)
|
||||
ackFlap := oscar.NewFLAP(2)
|
||||
ackFlap.Data.WriteBinary(ackSnac)
|
||||
return ctx, session.Send(ackFlap)
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ const CIPHER_LENGTH = 64
|
|||
const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
|
||||
|
||||
type AuthorizationCookie struct {
|
||||
UIN int
|
||||
UIN int64
|
||||
X string
|
||||
}
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -53,7 +53,7 @@ func main() {
|
|||
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
|
||||
|
||||
// Register our DB models
|
||||
db.RegisterModel((*models.User)(nil))
|
||||
db.RegisterModel((*models.User)(nil), (*models.Message)(nil))
|
||||
|
||||
// dev: load in fixtures to test against
|
||||
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
|
||||
|
|
33
models/Message.go
Normal file
33
models/Message.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
bun.BaseModel `bun:"table:messages"`
|
||||
MessageID uint64 `bun:",pk,notnull,unique"`
|
||||
From string
|
||||
To string
|
||||
Contents string
|
||||
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
|
||||
DeliveredAt time.Time `bun:",nullzero"`
|
||||
}
|
||||
|
||||
func InsertMessage(ctx context.Context, db *bun.DB, messageId uint64, from string, to string, contents string) error {
|
||||
msg := &Message{
|
||||
MessageID: messageId,
|
||||
From: from,
|
||||
To: to,
|
||||
Contents: contents,
|
||||
}
|
||||
if _, err := db.NewInsert().Model(msg).Exec(ctx); err != nil {
|
||||
return errors.Wrap(err, "could not update user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
type User struct {
|
||||
bun.BaseModel `bun:"table:users"`
|
||||
UIN int `bun:",pk,autoincrement"`
|
||||
UIN int64 `bun:",pk,autoincrement"`
|
||||
Email string `bun:",unique"`
|
||||
Username string `bun:",unique"`
|
||||
Password string
|
||||
|
@ -43,7 +43,7 @@ func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, er
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func UserByUIN(ctx context.Context, db *bun.DB, uin int) (*User, error) {
|
||||
func UserByUIN(ctx context.Context, db *bun.DB, uin int64) (*User, error) {
|
||||
user := new(User)
|
||||
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
|
|
|
@ -3,3 +3,5 @@
|
|||
- username: toof
|
||||
password: bar
|
||||
email: toof@plot.club
|
||||
- model: Message
|
||||
rows: []
|
||||
|
|
79
oscar/buf.go
79
oscar/buf.go
|
@ -4,12 +4,86 @@ import (
|
|||
"aim-oscar/util"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Buffer is a handy byte slice that reads from the front and writes on the end
|
||||
type Buffer struct {
|
||||
d []byte
|
||||
}
|
||||
|
||||
// Seek moves the read cursor forward. If the cursor is beyond the length of the byte
|
||||
// slice then the buffer slice is just replaced with an empty slice
|
||||
func (b *Buffer) Seek(n int) {
|
||||
if n > len(b.d) {
|
||||
b.d = make([]byte, 0)
|
||||
return
|
||||
}
|
||||
b.d = b.d[n:]
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(d []byte) (int, error) {
|
||||
if len(d) > len(b.d) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
n := copy(d, b.d[0:len(d)])
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadUint8() (uint8, error) {
|
||||
if len(b.d) < 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
ret := uint8(b.d[0])
|
||||
b.d = b.d[1:]
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadUint16() (uint16, error) {
|
||||
if len(b.d) < 2 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
ret := binary.BigEndian.Uint16(b.d[0:2])
|
||||
b.d = b.d[2:]
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadUint32() (uint32, error) {
|
||||
if len(b.d) < 4 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
ret := binary.BigEndian.Uint32(b.d[0:4])
|
||||
b.d = b.d[4:]
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) ReadUint64() (uint64, error) {
|
||||
if len(b.d) < 8 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
ret := binary.BigEndian.Uint64(b.d[0:8])
|
||||
b.d = b.d[8:]
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// ReadLPString reads a length-prefixed string. The first byte should be the string length
|
||||
// followed by that many bytes. Returns io.EOF if there are less bytes than indicated.
|
||||
func (b *Buffer) ReadLPString() (string, error) {
|
||||
length, err := b.ReadUint8()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(b.d) < int(length) {
|
||||
return "", io.EOF
|
||||
}
|
||||
|
||||
str := string(b.d[:length])
|
||||
b.d = b.d[length:]
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteUint8(x uint8) {
|
||||
b.d = append(b.d, x)
|
||||
}
|
||||
|
@ -33,6 +107,11 @@ func (b *Buffer) WriteString(x string) {
|
|||
b.d = append(b.d, []byte(x)...)
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteLPString(x string) {
|
||||
b.WriteUint8(uint8(len(x)))
|
||||
b.WriteString(x)
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(x []byte) (int, error) {
|
||||
b.d = append(b.d, x...)
|
||||
return len(x), nil
|
||||
|
|
55
oscar/buf_test.go
Normal file
55
oscar/buf_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package oscar
|
||||
|
||||
import "testing"
|
||||
|
||||
func fail(t *testing.T, e error, method string) {
|
||||
if e != nil {
|
||||
t.Errorf("invalid read from %s: %s", method, e.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
b := Buffer{}
|
||||
|
||||
b.WriteUint8(uint8(1))
|
||||
b.WriteUint16(uint16(2))
|
||||
b.WriteUint32(uint32(3))
|
||||
b.WriteUint64(uint64(4))
|
||||
|
||||
x1, err := b.ReadUint8()
|
||||
fail(t, err, "ReadUint8")
|
||||
if x1 != 1 {
|
||||
t.Errorf("expected ReadUint8 to read 1, got %d", x1)
|
||||
}
|
||||
|
||||
x2, err := b.ReadUint16()
|
||||
fail(t, err, "ReadUint16")
|
||||
if x2 != 2 {
|
||||
t.Errorf("expected ReadUint16 to read 2, got %d", x2)
|
||||
}
|
||||
|
||||
x3, err := b.ReadUint32()
|
||||
fail(t, err, "ReadUint32")
|
||||
if x3 != 3 {
|
||||
t.Errorf("expected ReadUint32 to read 3, got %d", x3)
|
||||
}
|
||||
|
||||
x4, err := b.ReadUint64()
|
||||
fail(t, err, "ReadUint64")
|
||||
if x4 != 4 {
|
||||
t.Errorf("expected ReadUint64 to read 4, got %d", x4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferLPString(t *testing.T) {
|
||||
b := Buffer{}
|
||||
|
||||
expectedStr := "This is a long string"
|
||||
b.WriteLPString(expectedStr)
|
||||
|
||||
str, err := b.ReadLPString()
|
||||
fail(t, err, "ReadLPString")
|
||||
if str != expectedStr {
|
||||
t.Errorf("expected to read %s, got %s", expectedStr, str)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue