services are set up, client shows buddy list

This commit is contained in:
Artem Titoulenko 2021-12-18 16:48:13 -05:00
parent 197e5a2f62
commit 06d3b84017
10 changed files with 379 additions and 47 deletions

View file

@ -0,0 +1,113 @@
package main
import (
"aim-oscar/models"
"aim-oscar/oscar"
"aim-oscar/util"
"context"
"encoding/binary"
"fmt"
"time"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
var versions map[uint16]uint16
func init() {
versions = make(map[uint16]uint16)
versions[1] = 3
versions[4] = 1
}
type GenericServiceControls struct{}
func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
session, _ := oscar.SessionFromContext(ctx)
switch snac.Header.Subtype {
// Client wants to know the rate limits for all services
case 0x06:
rateSnac := oscar.NewSNAC(1, 7)
rateSnac.Data.WriteUint16(1) // one rate class
// Define a Rate Class
rc := oscar.Buffer{}
rc.WriteUint16(1) // ID
rc.WriteUint32(80) // Window Size
rc.WriteUint32(2500) // Clear level
rc.WriteUint32(2000) // Alert level
rc.WriteUint32(1500) // Limit level
rc.WriteUint32(800) // Disconnect level
rc.WriteUint32(3400) // Current level (fake)
rc.WriteUint32(6000) // Max level
rc.WriteUint32(0) // Last time ?
rc.WriteUint8(0) // Current state ?
rateSnac.Data.Write(rc.Bytes())
// Define a Rate Group
rg := oscar.Buffer{}
rg.WriteUint16(1) // ID
// TODO: make actual rate groups instead of this hack. I can't tell which subtypes are supported so
// make it set rate limits for everything family for all subtypes under 0x21.
rg.WriteUint16(2 * 0x21) // Number of rate groups
for family := range versions {
for subtype := 0; subtype < 0x21; subtype++ {
rg.WriteUint16(family)
rg.WriteUint16(uint16(subtype))
}
}
rateSnac.Data.Write(rg.Bytes())
rateFlap := oscar.NewFLAP(2)
rateFlap.Data.WriteBinary(rateSnac)
return ctx, session.Send(rateFlap)
// Client wants their own online information
case 0x0e:
user := models.UserFromContext(ctx)
if user == nil {
return ctx, errors.New("expecting user in context")
}
onlineSnac := oscar.NewSNAC(1, 0xf)
uin := fmt.Sprint(user.UIN)
onlineSnac.Data.WriteUint8(uint8(len(uin)))
onlineSnac.Data.WriteString(uin)
onlineSnac.Data.WriteUint16(0) // warning level
tlvs := []*oscar.TLV{
oscar.NewTLV(0x01, util.Dword(0x80)), // User Class
oscar.NewTLV(0x06, util.Dword(0x0001|0x0100)), // User Status (TODO: update status in DB)
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
oscar.NewTLV(0x0f, util.Dword(0x0)), // Idle Time (TODO: track idle time)
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
oscar.NewTLV(0x01e, util.Dword(0x0)), // Unknown value
oscar.NewTLV(0x05, util.Dword(uint32(time.Now().Unix()))), // Member since
}
onlineSnac.Data.WriteUint16(uint16(len(tlvs)))
for _, tlv := range tlvs {
onlineSnac.Data.WriteBinary(tlv)
}
onlineFlap := oscar.NewFLAP(2)
onlineFlap.Data.WriteBinary(onlineSnac)
return ctx, session.Send(onlineFlap)
// Client wants to know the versions of all of the services offered
case 0x17:
versionsSnac := oscar.NewSNAC(1, 0x18)
for family, version := range versions {
versionsSnac.Data.WriteUint16(family)
versionsSnac.Data.WriteUint16(version)
}
versionsFlap := oscar.NewFLAP(2)
versionsFlap.Data.WriteBinary(versionsSnac)
return ctx, session.Send(versionsFlap)
}
return ctx, nil
}

92
0x04_ICBM.go Normal file
View file

@ -0,0 +1,92 @@
package main
import (
"aim-oscar/oscar"
"bytes"
"context"
"encoding/binary"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
type ICBM struct{}
type icbmKey string
func (s icbmKey) String() string {
return "icbm-" + string(s)
}
var (
channelKey = icbmKey("channel")
)
func NewContextWithChannel(ctx context.Context, c *channel) context.Context {
return context.WithValue(ctx, channelKey, c)
}
func ChannelFromContext(ctx context.Context) *channel {
s := ctx.Value(channelKey)
if s == nil {
return nil
}
return s.(*channel)
}
type channel struct {
ID uint16
MessageFlags uint32
MaxMessageSnacSize uint16
MaxSenderWarningLevel uint16
MaxReceiverWarningLevel uint16
MinimumMessageInterval uint16
Unknown uint16
}
func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
session, _ := oscar.SessionFromContext(ctx)
switch snac.Header.Subtype {
// Client is telling us about their ICBM capabilities
case 0x02:
/*
xx xx word channel to setup
xx xx xx xx dword message flags
xx xx word max message snac size
xx xx word max sender warning level
xx xx word max receiver warning level
xx xx word minimum message interval (sec)
00 00 word unknown parameter (also seen 03 E8)
*/
channel := channel{}
r := bytes.NewReader(snac.Data.Bytes())
if err := binary.Read(r, binary.BigEndian, &channel); err != nil {
return ctx, errors.Wrap(err, "could not read channel settings")
}
newCtx := NewContextWithChannel(ctx, &channel)
return newCtx, nil
// Client asks about the ICBM capabilities we set for them
case 0x04:
channel := ChannelFromContext(ctx)
channelSnac := oscar.NewSNAC(4, 5)
channelSnac.Data.WriteUint16(uint16(channel.ID))
channelSnac.Data.WriteUint32(channel.MessageFlags)
channelSnac.Data.WriteUint16(channel.MaxMessageSnacSize)
channelSnac.Data.WriteUint16(channel.MaxSenderWarningLevel)
channelSnac.Data.WriteUint16(channel.MaxReceiverWarningLevel)
channelSnac.Data.WriteUint16(channel.MinimumMessageInterval)
channelSnac.Data.WriteUint16(channel.Unknown)
channelFlap := oscar.NewFLAP(2)
channelFlap.Data.WriteBinary(channelSnac)
session.Send(channelFlap)
return ctx, nil
}
return ctx, nil
}

View file

@ -21,7 +21,48 @@ import (
const CIPHER_LENGTH = 64 const CIPHER_LENGTH = 64
const AIM_MD5_STRING = "AOL Instant Messenger (SM)" const AIM_MD5_STRING = "AOL Instant Messenger (SM)"
type AuthorizationRegistrationService struct { type AuthorizationCookie struct {
UIN int
X string
}
type AuthorizationRegistrationService struct{}
func AuthenticateFLAPCookie(ctx context.Context, db *bun.DB, flap *oscar.FLAP) (*models.User, error) {
// Otherwise this is a protocol negotiation from the client. They're likely trying to connect
// and sending a cookie to verify who they are.
tlvs, err := oscar.UnmarshalTLVs(flap.Data.Bytes()[4:])
if err != nil {
return nil, errors.Wrap(err, "authentication request missing TLVs")
}
cookieTLV := oscar.FindTLV(tlvs, 0x6)
if cookieTLV == nil {
return nil, errors.New("authentication request missing Cookie TLV 0x6")
}
auth := AuthorizationCookie{}
if err := json.Unmarshal(cookieTLV.Data, &auth); err != nil {
return nil, errors.Wrap(err, "could not unmarshal cookie")
}
user, err := models.UserByUIN(ctx, db, auth.UIN)
if err != nil {
return nil, errors.Wrap(err, "could not get User by UIN")
}
h := md5.New()
io.WriteString(h, user.Cipher)
io.WriteString(h, user.Password)
io.WriteString(h, AIM_MD5_STRING)
expectedPasswordHash := fmt.Sprintf("%x", h.Sum(nil))
// Make sure the hash passed in matches the one from the DB
if expectedPasswordHash != auth.X {
return nil, errors.New("unexpected cookie hash")
}
return user, nil
} }
func (a *AuthorizationRegistrationService) GenerateCipher() string { func (a *AuthorizationRegistrationService) GenerateCipher() string {
@ -33,7 +74,12 @@ func (a *AuthorizationRegistrationService) GenerateCipher() string {
return base32.StdEncoding.EncodeToString(randomBytes)[:CIPHER_LENGTH] return base32.StdEncoding.EncodeToString(randomBytes)[:CIPHER_LENGTH]
} }
func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar.Session, snac *oscar.SNAC) error { func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
session, err := oscar.SessionFromContext(ctx)
if err != nil {
util.PanicIfError(err)
}
switch snac.Header.Subtype { switch snac.Header.Subtype {
// Request MD5 Auth Key // Request MD5 Auth Key
case 0x06: case 0x06:
@ -42,14 +88,13 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
usernameTLV := oscar.FindTLV(tlvs, 1) usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil { if usernameTLV == nil {
return errors.New("missing username TLV") return ctx, errors.New("missing username TLV")
} }
// Fetch the user // Fetch the user
ctx := context.Background()
user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data)) user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data))
if err != nil { if err != nil {
return err return ctx, err
} }
if user == nil { if user == nil {
snac := oscar.NewSNAC(0x17, 0x03) snac := oscar.NewSNAC(0x17, 0x03)
@ -57,13 +102,13 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := oscar.NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return ctx, session.Send(resp)
} }
// Create cipher for this user // Create cipher for this user
user.Cipher = a.GenerateCipher() user.Cipher = a.GenerateCipher()
if err = user.Update(ctx, db); err != nil { if err = user.Update(ctx, db); err != nil {
return err return ctx, err
} }
snac := oscar.NewSNAC(0x17, 0x07) snac := oscar.NewSNAC(0x17, 0x07)
@ -72,7 +117,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
resp := oscar.NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return ctx, session.Send(resp)
// Client Authorization Request // Client Authorization Request
case 0x02: case 0x02:
@ -81,14 +126,14 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
usernameTLV := oscar.FindTLV(tlvs, 1) usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil { if usernameTLV == nil {
return errors.New("missing username TLV 0x1") return ctx, errors.New("missing username TLV 0x1")
} }
username := string(usernameTLV.Data) username := string(usernameTLV.Data)
ctx := context.Background() ctx := context.Background()
user, err := models.UserByUsername(ctx, db, username) user, err := models.UserByUsername(ctx, db, username)
if err != nil { if err != nil {
return err return ctx, err
} }
if user == nil { if user == nil {
@ -97,12 +142,12 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := oscar.NewFLAP(2) resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac) resp.Data.WriteBinary(snac)
return session.Send(resp) return ctx, session.Send(resp)
} }
passwordHashTLV := oscar.FindTLV(tlvs, 0x25) passwordHashTLV := oscar.FindTLV(tlvs, 0x25)
if passwordHashTLV == nil { if passwordHashTLV == nil {
return errors.New("missing password hash TLV 0x25") return ctx, errors.New("missing password hash TLV 0x25")
} }
// Compute password has that we expect the client to send back if the password was right // Compute password has that we expect the client to send back if the password was right
@ -123,7 +168,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
// Tell them to leave // Tell them to leave
discoFlap := oscar.NewFLAP(4) discoFlap := oscar.NewFLAP(4)
return session.Send(discoFlap) return ctx, session.Send(discoFlap)
} }
// Send BOS response + cookie // Send BOS response + cookie
@ -131,10 +176,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
authSnac.Data.WriteBinary(usernameTLV) authSnac.Data.WriteBinary(usernameTLV)
authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(SRV_ADDRESS))) authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(SRV_ADDRESS)))
cookie, err := json.Marshal(struct { cookie, err := json.Marshal(AuthorizationCookie{
UIN int
X string
}{
UIN: user.UIN, UIN: user.UIN,
X: fmt.Sprintf("%x", expectedPasswordHash), X: fmt.Sprintf("%x", expectedPasswordHash),
}) })
@ -148,8 +190,9 @@ func (a *AuthorizationRegistrationService) HandleSNAC(db *bun.DB, session *oscar
// Tell them to leave // Tell them to leave
discoFlap := oscar.NewFLAP(4) discoFlap := oscar.NewFLAP(4)
return session.Send(discoFlap) session.Send(discoFlap)
return ctx, session.Disconnect()
} }
return nil return ctx, nil
} }

43
main.go
View file

@ -23,7 +23,7 @@ import (
) )
const ( const (
SRV_HOST = "" SRV_HOST = "10.0.1.2"
SRV_PORT = "5190" SRV_PORT = "5190"
SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT SRV_ADDRESS = SRV_HOST + ":" + SRV_PORT
) )
@ -68,12 +68,40 @@ func main() {
} }
defer listener.Close() defer listener.Close()
handler := oscar.NewHandler(func(session *oscar.Session, flap *oscar.FLAP) { handler := oscar.NewHandler(func(ctx context.Context, flap *oscar.FLAP) context.Context {
session, err := oscar.SessionFromContext(ctx)
if err != nil {
util.PanicIfError(err)
}
if user := models.UserFromContext(ctx); user != nil {
fmt.Printf("%s (%v) ->\n%+v\n", user.Username, session.RemoteAddr(), flap)
} else {
fmt.Printf("%v ->\n%+v\n", session.RemoteAddr(), flap)
}
if flap.Header.Channel == 1 { if flap.Header.Channel == 1 {
// Is this a hello? // Is this a hello?
if bytes.Equal(flap.Data.Bytes(), []byte{0, 0, 0, 1}) { if bytes.Equal(flap.Data.Bytes(), []byte{0, 0, 0, 1}) {
return return ctx
} }
user, err := AuthenticateFLAPCookie(ctx, db, flap)
if err != nil {
log.Printf("Could not authenticate cookie: %s", err)
return ctx
}
ctx = models.NewContextWithUser(ctx, user)
// Send available services
servicesSnac := oscar.NewSNAC(1, 3)
servicesSnac.Data.WriteUint16(0x1)
servicesSnac.Data.WriteUint16(0x4)
servicesFlap := oscar.NewFLAP(2)
servicesFlap.Data.WriteBinary(servicesSnac)
session.Send(servicesFlap)
return ctx
} else if flap.Header.Channel == 2 { } else if flap.Header.Channel == 2 {
snac := &oscar.SNAC{} snac := &oscar.SNAC{}
err := snac.UnmarshalBinary(flap.Data.Bytes()) err := snac.UnmarshalBinary(flap.Data.Bytes())
@ -89,13 +117,20 @@ func main() {
} }
if service, ok := services[snac.Header.Family]; ok { if service, ok := services[snac.Header.Family]; ok {
err = service.HandleSNAC(db, session, snac) newCtx, err := service.HandleSNAC(ctx, db, snac)
util.PanicIfError(err) util.PanicIfError(err)
return newCtx
} }
} else if flap.Header.Channel == 4 {
session.Disconnect()
} }
return ctx
}) })
RegisterService(0x17, &AuthorizationRegistrationService{}) RegisterService(0x17, &AuthorizationRegistrationService{})
RegisterService(0x01, &GenericServiceControls{})
RegisterService(0x04, &ICBM{})
exitChan := make(chan os.Signal, 1) exitChan := make(chan os.Signal, 1)
signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT) signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT)

View file

@ -17,6 +17,16 @@ type User struct {
Cipher string Cipher string
} }
type userKey string
func (s userKey) String() string {
return "user-" + string(s)
}
var (
currentUser = userKey("user")
)
func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, error) { func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, error) {
user := new(User) user := new(User)
if err := db.NewSelect().Model(user).Where("username = ?", username).Scan(ctx, user); err != nil { if err := db.NewSelect().Model(user).Where("username = ?", username).Scan(ctx, user); err != nil {
@ -28,6 +38,29 @@ func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, er
return user, nil return user, nil
} }
func UserByUIN(ctx context.Context, db *bun.DB, uin int) (*User, error) {
user := new(User)
if err := db.NewSelect().Model(user).Where("uin = ?", uin).Scan(ctx, user); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, errors.Wrap(err, "could not fetch user")
}
return user, nil
}
func NewContextWithUser(ctx context.Context, user *User) context.Context {
return context.WithValue(ctx, currentUser, user)
}
func UserFromContext(ctx context.Context) *User {
v := ctx.Value(currentUser)
if v == nil {
return nil
}
return v.(*User)
}
func (u *User) Update(ctx context.Context, db *bun.DB) error { func (u *User) Update(ctx context.Context, db *bun.DB) error {
if _, err := db.NewUpdate().Model(u).WherePK("uin").Exec(ctx); err != nil { if _, err := db.NewUpdate().Model(u).WherePK("uin").Exec(ctx); err != nil {
return errors.Wrap(err, "could not update user") return errors.Wrap(err, "could not update user")

View file

@ -2,27 +2,31 @@ package oscar
import ( import (
"aim-oscar/util" "aim-oscar/util"
"fmt" "context"
"encoding/binary"
"io" "io"
"log" "log"
"net" "net"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
type HandlerFunc func(*Session, *FLAP) type HandlerFunc func(context.Context, *FLAP) context.Context
type Handler struct{ fn HandlerFunc } type Handler struct{ handle HandlerFunc }
func NewHandler(fn HandlerFunc) *Handler { func NewHandler(fn HandlerFunc) *Handler {
return &Handler{ return &Handler{
fn: fn, handle: fn,
} }
} }
func (h *Handler) Handle(conn net.Conn) { func (h *Handler) Handle(conn net.Conn) {
session := NewSession(conn) ctx := NewContextWithSession(context.Background(), conn)
buf := make([]byte, 1024) session, _ := SessionFromContext(ctx)
buf := make([]byte, 2048)
for { for {
if !session.GreetedClient { if !session.GreetedClient {
// send a hello // send a hello
@ -35,7 +39,13 @@ func (h *Handler) Handle(conn net.Conn) {
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
log.Println("Read Error: ", err.Error()) if strings.Contains(err.Error(), "use of closed network connection") {
log.Printf("%v disconnected", conn.RemoteAddr())
session.Disconnect()
return
}
log.Println("OSCAR Read Error: ", err.Error())
return return
} }
@ -46,7 +56,7 @@ func (h *Handler) Handle(conn net.Conn) {
// Try to parse all of the FLAPs in the buffer if we have enough bytes to // Try to parse all of the FLAPs in the buffer if we have enough bytes to
// fill a FLAP header // fill a FLAP header
for len(buf) >= 6 && buf[0] == 0x2a { for len(buf) >= 6 && buf[0] == 0x2a {
dataLength := util.Word(buf[4:6]) dataLength := binary.BigEndian.Uint16(buf[4:6])
flapLength := int(dataLength) + 6 flapLength := int(dataLength) + 6
if len(buf) < flapLength { if len(buf) < flapLength {
log.Printf("not enough data, only %d bytes\n", len(buf)) log.Printf("not enough data, only %d bytes\n", len(buf))
@ -58,8 +68,7 @@ func (h *Handler) Handle(conn net.Conn) {
util.PanicIfError(errors.Wrap(err, "could not unmarshal FLAP")) util.PanicIfError(errors.Wrap(err, "could not unmarshal FLAP"))
} }
buf = buf[flapLength:] buf = buf[flapLength:]
fmt.Printf("%v ->\n%+v\n", conn.RemoteAddr(), flap) ctx = h.handle(ctx, flap)
h.fn(session, flap)
} }
} }
} }

View file

@ -20,14 +20,14 @@ var (
) )
type Session struct { type Session struct {
Conn net.Conn conn net.Conn
SequenceNumber uint16 SequenceNumber uint16
GreetedClient bool GreetedClient bool
} }
func NewSession(conn net.Conn) *Session { func NewSession(conn net.Conn) *Session {
return &Session{ return &Session{
Conn: conn, conn: conn,
SequenceNumber: 0, SequenceNumber: 0,
GreetedClient: false, GreetedClient: false,
} }
@ -38,7 +38,7 @@ func NewContextWithSession(ctx context.Context, conn net.Conn) context.Context {
return context.WithValue(ctx, currentSession, session) return context.WithValue(ctx, currentSession, session)
} }
func CurrentSession(ctx context.Context) (session *Session, err error) { func SessionFromContext(ctx context.Context) (session *Session, err error) {
s := ctx.Value(currentSession) s := ctx.Value(currentSession)
if s == nil { if s == nil {
return nil, errors.New("no session in context") return nil, errors.New("no session in context")
@ -46,6 +46,10 @@ func CurrentSession(ctx context.Context) (session *Session, err error) {
return s.(*Session), nil return s.(*Session), nil
} }
func (s *Session) RemoteAddr() net.Addr {
return s.conn.RemoteAddr()
}
func (s *Session) Send(flap *FLAP) error { func (s *Session) Send(flap *FLAP) error {
s.SequenceNumber += 1 s.SequenceNumber += 1
flap.Header.SequenceNumber = s.SequenceNumber flap.Header.SequenceNumber = s.SequenceNumber
@ -54,7 +58,11 @@ func (s *Session) Send(flap *FLAP) error {
return errors.Wrap(err, "could not marshal message") return errors.Wrap(err, "could not marshal message")
} }
fmt.Printf("-> %v\n%s\n\n", s.Conn.RemoteAddr(), util.PrettyBytes(bytes)) fmt.Printf("-> %v\n%s\n\n", s.conn.RemoteAddr(), util.PrettyBytes(bytes))
_, err = s.Conn.Write(bytes) _, err = s.conn.Write(bytes)
return errors.Wrap(err, "could not write to client connection") return errors.Wrap(err, "could not write to client connection")
} }
func (s *Session) Disconnect() error {
return s.conn.Close()
}

View file

@ -43,8 +43,8 @@ func (t *TLV) UnmarshalBinary(data []byte) error {
if len(data) < 4 { if len(data) < 4 {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
t.Type = util.Word(data[:2]) t.Type = binary.BigEndian.Uint16(data[:2])
t.DataLength = util.Word(data[2:4]) t.DataLength = binary.BigEndian.Uint16(data[2:4])
if len(data) < 4+int(t.DataLength) { if len(data) < 4+int(t.DataLength) {
return io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }

View file

@ -2,10 +2,11 @@ package main
import ( import (
"aim-oscar/oscar" "aim-oscar/oscar"
"context"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
type Service interface { type Service interface {
HandleSNAC(*bun.DB, *oscar.Session, *oscar.SNAC) error HandleSNAC(context.Context, *bun.DB, *oscar.SNAC) (context.Context, error)
} }

View file

@ -53,12 +53,10 @@ func PanicIfError(err error) {
} }
} }
func Word(b []byte) uint16 { func Word(x uint16) []byte {
var _ = b[1] return []byte{byte(x >> 8), byte(x & 0xf)}
return uint16(b[1]) | uint16(b[0])<<8
} }
func DWord(b []byte) uint32 { func Dword(x uint32) []byte {
var _ = b[3] return []byte{byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x & 0xf)}
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
} }