| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- package wire
- import (
- "bytes"
- "fmt"
- "io"
- "sync"
- )
- type SNACError struct {
- Code uint16
- TLVRestBlock
- }
- const (
- FLAPFrameSignon uint8 = 0x01
- FLAPFrameData uint8 = 0x02
- FLAPFrameError uint8 = 0x03
- FLAPFrameSignoff uint8 = 0x04
- FLAPFrameKeepAlive uint8 = 0x05
- )
- const (
- // FLAPMaxDataSize is the maximum size of a FLAP packet's data (excluding header).
- FLAPMaxDataSize uint32 = 0xFFF9
- )
- type FLAPFrame struct {
- StartMarker uint8
- FrameType uint8
- Sequence uint16
- Payload []byte `oscar:"len_prefix=uint16"`
- }
- // FLAPFrameDisconnect is the last FLAP frame sent to a client before
- // disconnection. It differs from FLAPFrame in that there is no payload length
- // prefix at the end, which causes pre-multi-conn Windows AIM clients to
- // improperly handle server disconnections, as when the regular FLAPFrame type
- // is used.
- type FLAPFrameDisconnect struct {
- StartMarker uint8
- FrameType uint8
- Sequence uint16
- }
- type SNACFrame struct {
- FoodGroup uint16
- SubGroup uint16
- Flags uint16
- RequestID uint32
- }
- // SNACFlagsMoreToCome is set on SNAC frames that are part of a multi-packet
- // response (bit 0). Use it on every fragment except the last.
- const SNACFlagsMoreToCome uint16 = 0x0001
- // ReqIDFromServer is the SNAC frame Request ID value that indicates the SNAC
- // is initiated by the server. Some clients, such as the Java AIM 1.1.19,
- // completely fail to process some server SNACs if the high bit is not set on
- // request ID.
- const ReqIDFromServer = 1 << 31
- type FLAPSignonFrame struct {
- FLAPVersion uint32
- TLVRestBlock
- }
- 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,
- mutex: sync.Mutex{},
- }
- }
- // 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. It is not safe to use with multiple goroutines
- // without synchronization.
- type FlapClient struct {
- sequence uint32
- r io.Reader
- w io.Writer
- mutex sync.Mutex
- }
- // Fixes a race condition caused by testify. Yup...
- // https://github.com/stretchr/testify/issues/625
- func (f *FlapClient) String() string {
- return ""
- }
- // 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 := MarshalBE(signonFrame, buf); err != nil {
- return err
- }
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrame{
- StartMarker: 42,
- FrameType: FLAPFrameSignon,
- Sequence: uint16(f.sequence),
- Payload: buf.Bytes(),
- }
- if err := MarshalBE(flap, f.w); 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 := UnmarshalBE(&flap, f.r); err != nil {
- return FLAPSignonFrame{}, err
- }
- signonFrame := FLAPSignonFrame{}
- if err := UnmarshalBE(&signonFrame, bytes.NewBuffer(flap.Payload)); err != nil {
- return FLAPSignonFrame{}, err
- }
- return signonFrame, nil
- }
- // ReceiveFLAP receives a FLAP frame and body. It only returns a body if the
- // FLAP frame is a data frame.
- func (f *FlapClient) ReceiveFLAP() (FLAPFrame, error) {
- flap := FLAPFrame{}
- err := UnmarshalBE(&flap, f.r)
- if err != nil {
- err = fmt.Errorf("unable to unmarshal FLAP frame: %w", err)
- }
- return flap, err
- }
- // OldSignoff sends a signoff FLAP frame for legacy clients that do not
- // support multi-connection (Windows AIM 1.x–4.1).
- //
- // When these clients receive this frame, they display a "connection lost"
- // message and close the session. Unlike normal FLAP frames, this variant
- // omits the payload size field. If the size field were present, the client
- // would hang without displaying any message upon server disconnection.
- func (f *FlapClient) OldSignoff() error {
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrameDisconnect{
- StartMarker: 42,
- FrameType: FLAPFrameSignoff,
- Sequence: uint16(f.sequence),
- }
- return MarshalBE(flap, f.w)
- }
- // NewSignoff sends a signoff FLAP frame for multi-connection clients.
- //
- // The frame includes a TLV block with additional metadata such as error codes.
- // Client behavior depends on the version:
- // - AIM 4.3–5.x: the client minimizes and enters a "signed off" state.
- // - AIM 6.x–7.x: the client closes and displays a disconnection error.
- func (f *FlapClient) NewSignoff(tlvs TLVRestBlock) error {
- tlvBuf := &bytes.Buffer{}
- if err := MarshalBE(tlvs, tlvBuf); err != nil {
- return err
- }
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrame{
- StartMarker: 42,
- FrameType: FLAPFrameSignoff,
- Sequence: uint16(f.sequence),
- Payload: tlvBuf.Bytes(),
- }
- if err := MarshalBE(flap, f.w); err != nil {
- return err
- }
- 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 := MarshalBE(frame, snacBuf); err != nil {
- return err
- }
- if err := MarshalBE(body, snacBuf); err != nil {
- return err
- }
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrame{
- StartMarker: 42,
- FrameType: FLAPFrameData,
- Sequence: uint16(f.sequence),
- Payload: snacBuf.Bytes(),
- }
- if err := MarshalBE(flap, f.w); err != nil {
- return err
- }
- f.sequence++
- return nil
- }
- func (f *FlapClient) SendDataFrame(payload []byte) error {
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrame{
- StartMarker: 42,
- FrameType: FLAPFrameData,
- Sequence: uint16(f.sequence),
- Payload: payload,
- }
- if err := MarshalBE(flap, f.w); err != nil {
- return err
- }
- f.sequence++
- return nil
- }
- func (f *FlapClient) SendKeepAliveFrame() error {
- f.mutex.Lock()
- defer f.mutex.Unlock()
- flap := FLAPFrame{
- StartMarker: 42,
- FrameType: FLAPFrameKeepAlive,
- Sequence: uint16(f.sequence),
- }
- if err := MarshalBE(flap, f.w); err != nil {
- return err
- }
- 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 := UnmarshalBE(&flap, f.r); err != nil {
- return err
- }
- buf := bytes.NewBuffer(flap.Payload)
- if err := UnmarshalBE(frame, buf); err != nil {
- return err
- }
- return UnmarshalBE(body, buf)
- }
|