Added raknet as dependency
This commit is contained in:
parent
8a7ccc8fd9
commit
96e65ac141
6
go.mod
6
go.mod
@ -4,4 +4,8 @@ go 1.21
|
||||
|
||||
require github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
|
||||
|
||||
require golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
require (
|
||||
github.com/df-mc/atomic v1.10.0 // indirect
|
||||
github.com/sandertv/go-raknet v1.12.1 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@ -1,4 +1,8 @@
|
||||
github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg=
|
||||
github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc=
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
|
||||
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
|
||||
github.com/sandertv/go-raknet v1.12.1 h1:CXDfeXGaQD8kwlatlaAS1wQsMBLLGlDSH6upZv28Pss=
|
||||
github.com/sandertv/go-raknet v1.12.1/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@ -1,821 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cimeyclust.com/steel/pkg/network/packets"
|
||||
"cimeyclust.com/steel/pkg/utils"
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// currentProtocol is the current RakNet protocol version. This is Minecraft
|
||||
// specific.
|
||||
currentProtocol byte = 11
|
||||
|
||||
maxMTUSize = 1400
|
||||
maxWindowSize = 2048
|
||||
)
|
||||
|
||||
// Conn represents a connection to a specific client. It is not a real
|
||||
// connection, as UDP is connectionless, but rather a connection emulated using
|
||||
// RakNet. Methods may be called on Conn from multiple goroutines
|
||||
// simultaneously.
|
||||
type Conn struct {
|
||||
// rtt is the last measured round-trip time between both ends of the
|
||||
// connection. The rtt is measured in nanoseconds.
|
||||
rtt atomic.Int64
|
||||
|
||||
closing atomic.Int64
|
||||
|
||||
conn net.PacketConn
|
||||
addr net.Addr
|
||||
limits bool
|
||||
|
||||
once sync.Once
|
||||
closed, connected chan struct{}
|
||||
close func()
|
||||
|
||||
mu sync.Mutex
|
||||
buf *bytes.Buffer
|
||||
|
||||
ackBuf, nackBuf *bytes.Buffer
|
||||
|
||||
pk *packets.Packet
|
||||
|
||||
seq, orderIndex, messageIndex utils.Uint24
|
||||
splitID uint32
|
||||
|
||||
// mtuSize is the MTU size of the connection. Packets longer than this size
|
||||
// must be split into fragments for them to arrive at the client without
|
||||
// losing bytes.
|
||||
mtuSize uint16
|
||||
|
||||
// splits is a map of slices indexed by split IDs. The length of each of the
|
||||
// slices is equal to the split count, and packets are positioned in that
|
||||
// slice indexed by the split index.
|
||||
splits map[uint16][][]byte
|
||||
|
||||
// win is an ordered queue used to track which datagrams were received and
|
||||
// which datagrams were missing, so that we can send NACKs to request
|
||||
// missing datagrams.
|
||||
win *datagramWindow
|
||||
|
||||
ackMu sync.Mutex
|
||||
// ackSlice is a slice containing sequence numbers of datagrams that were
|
||||
// received over the last second. When ticked, all of these packets are sent
|
||||
// in an ACK and the slice is cleared.
|
||||
ackSlice []utils.Uint24
|
||||
|
||||
// packetQueue is an ordered queue containing packets indexed by their order
|
||||
// index.
|
||||
packetQueue *packetQueue
|
||||
// packets is a channel containing content of packets that were fully
|
||||
// processed. Calling Conn.Read() consumes a value from this channel.
|
||||
packets chan *bytes.Buffer
|
||||
|
||||
// retransmission is a queue filled with packets that were sent with a given
|
||||
// datagram sequence number.
|
||||
retransmission *resendMap
|
||||
|
||||
// readDeadline is a channel that receives a time.Time after a specific
|
||||
// time. It is used to listen for timeouts in Read after calling
|
||||
// SetReadDeadline.
|
||||
readDeadline <-chan time.Time
|
||||
|
||||
lastActivity atomic.Pointer[time.Time]
|
||||
}
|
||||
|
||||
// newConn constructs a new connection specifically dedicated to the address
|
||||
// passed.
|
||||
func newConn(conn net.PacketConn, addr net.Addr, mtuSize uint16) *Conn {
|
||||
return newConnWithLimits(conn, addr, mtuSize, true)
|
||||
}
|
||||
|
||||
// newConnWithLimits returns a Conn for the net.Addr passed with a specific mtu
|
||||
// size. The limits bool passed specifies if the connection should limit the
|
||||
// bounds of things such as the size of packets. This is generally recommended
|
||||
// for connections coming from a client.
|
||||
func newConnWithLimits(conn net.PacketConn, addr net.Addr, mtuSize uint16, limits bool) *Conn {
|
||||
if mtuSize < 500 || mtuSize > 1500 {
|
||||
mtuSize = maxMTUSize
|
||||
}
|
||||
c := &Conn{
|
||||
addr: addr,
|
||||
conn: conn,
|
||||
limits: limits,
|
||||
mtuSize: mtuSize,
|
||||
pk: new(packets.Packet),
|
||||
closed: make(chan struct{}),
|
||||
connected: make(chan struct{}),
|
||||
packets: make(chan *bytes.Buffer, 512),
|
||||
splits: make(map[uint16][][]byte),
|
||||
win: newDatagramWindow(),
|
||||
packetQueue: newPacketQueue(),
|
||||
retransmission: newRecoveryQueue(),
|
||||
buf: bytes.NewBuffer(make([]byte, 0, mtuSize)),
|
||||
ackBuf: bytes.NewBuffer(make([]byte, 0, 256)),
|
||||
nackBuf: bytes.NewBuffer(make([]byte, 0, 256)),
|
||||
}
|
||||
t := time.Now()
|
||||
c.lastActivity.Store(&t)
|
||||
go c.startTicking()
|
||||
return c
|
||||
}
|
||||
|
||||
// startTicking makes the connection start ticking, sending ACKs and pings to
|
||||
// the other end where necessary and checking if the connection should be timed
|
||||
// out.
|
||||
func (conn *Conn) startTicking() {
|
||||
var (
|
||||
interval = time.Second / 10
|
||||
ticker = time.NewTicker(interval)
|
||||
i int64
|
||||
acksLeft int
|
||||
)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case t := <-ticker.C:
|
||||
i++
|
||||
conn.flushACKs()
|
||||
if i%2 == 0 {
|
||||
// We send a connected ping to calculate the rtt and let the
|
||||
// other side know we haven't timed out.
|
||||
conn.sendPing()
|
||||
}
|
||||
if i%3 == 0 {
|
||||
conn.checkResend(t)
|
||||
}
|
||||
if i%5 == 0 {
|
||||
conn.mu.Lock()
|
||||
if t.Sub(*conn.lastActivity.Load()) > time.Second*5+conn.retransmission.rtt()*2 {
|
||||
// No activity for too long: Start timeout.
|
||||
_ = conn.Close()
|
||||
}
|
||||
conn.mu.Unlock()
|
||||
}
|
||||
if unix := conn.closing.Load(); unix != 0 {
|
||||
before := acksLeft
|
||||
conn.mu.Lock()
|
||||
acksLeft = len(conn.retransmission.unacknowledged)
|
||||
conn.mu.Unlock()
|
||||
|
||||
if before != 0 && acksLeft == 0 {
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
since := time.Since(time.Unix(unix, 0))
|
||||
if (acksLeft == 0 && since > time.Second) || since > time.Second*8 {
|
||||
conn.closeImmediately()
|
||||
}
|
||||
}
|
||||
case <-conn.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flushACKs flushes all pending datagram acknowledgements.
|
||||
func (conn *Conn) flushACKs() {
|
||||
conn.ackMu.Lock()
|
||||
defer conn.ackMu.Unlock()
|
||||
|
||||
if len(conn.ackSlice) > 0 {
|
||||
// Write an ACK packet to the connection containing all datagram
|
||||
// sequence numbers that we received since the last tick.
|
||||
if err := conn.sendACK(conn.ackSlice...); err != nil {
|
||||
return
|
||||
}
|
||||
conn.ackSlice = conn.ackSlice[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// checkResend checks if the connection needs to resend any packets. It sends
|
||||
// an ACK for packets it has received and sends any packets that have been
|
||||
// pending for too long.
|
||||
func (conn *Conn) checkResend(now time.Time) {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
var (
|
||||
resend []utils.Uint24
|
||||
rtt = conn.retransmission.rtt()
|
||||
delay = rtt + rtt/2
|
||||
)
|
||||
conn.rtt.Store(int64(rtt))
|
||||
|
||||
for seq, t := range conn.retransmission.unacknowledged {
|
||||
// These packets have not been acknowledged for too long: We resend them
|
||||
// by ourselves, even though no NACK has been issued yet.
|
||||
if now.Sub(t.timestamp) > delay {
|
||||
resend = append(resend, seq)
|
||||
}
|
||||
}
|
||||
_ = conn.resend(resend)
|
||||
}
|
||||
|
||||
// Write writes a buffer b over the RakNet connection. The amount of bytes
|
||||
// written n is always equal to the length of the bytes written if writing was
|
||||
// successful. If not, an error is returned and n is 0. Write may be called
|
||||
// simultaneously from multiple goroutines, but will write one by one.
|
||||
func (conn *Conn) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-conn.closed:
|
||||
return 0, conn.wrap(net.ErrClosed, "write")
|
||||
default:
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
n, err := conn.write(b)
|
||||
return n, conn.wrap(err, "write")
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a buffer b over the RakNet connection. The amount of bytes
|
||||
// written n is always equal to the length of the bytes written if the write
|
||||
// was successful. If not, an error is returned and n is 0. Write may be called
|
||||
// simultaneously from multiple goroutines, but will write one by one. Unlike
|
||||
// Write, write will not lock.
|
||||
func (conn *Conn) write(b []byte) (n int, err error) {
|
||||
fragments := conn.split(b)
|
||||
orderIndex := conn.orderIndex
|
||||
conn.orderIndex++
|
||||
|
||||
splitID := uint16(conn.splitID)
|
||||
split := len(fragments) > 1
|
||||
if split {
|
||||
conn.splitID++
|
||||
}
|
||||
for splitIndex, content := range fragments {
|
||||
sequenceNumber := conn.seq
|
||||
conn.seq++
|
||||
messageIndex := conn.messageIndex
|
||||
conn.messageIndex++
|
||||
|
||||
conn.buf.WriteByte(packets.BitFlagDatagram | packets.BitFlagNeedsBAndAS)
|
||||
utils.WriteUint24(conn.buf, sequenceNumber)
|
||||
pk := packetPool.Get().(*packets.Packet)
|
||||
if cap(pk.Content) < len(content) {
|
||||
pk.Content = make([]byte, len(content))
|
||||
}
|
||||
// We set the actual slice size to the same size as the content. It
|
||||
// might be bigger than the previous size, in which case it will grow,
|
||||
// which is fine as the underlying array will always be big enough.
|
||||
pk.Content = pk.Content[:len(content)]
|
||||
copy(pk.Content, content)
|
||||
|
||||
pk.OrderIndex = orderIndex
|
||||
pk.MessageIndex = messageIndex
|
||||
|
||||
pk.Split = split
|
||||
if split {
|
||||
// If there were more than one fragment, the pk was split, so we
|
||||
// need to make sure we set the appropriate fields.
|
||||
pk.SplitCount = uint32(len(fragments))
|
||||
pk.SplitIndex = uint32(splitIndex)
|
||||
pk.SplitID = splitID
|
||||
}
|
||||
pk.Write(conn.buf)
|
||||
// We then send the pk to the connection.
|
||||
if _, err := conn.conn.WriteTo(conn.buf.Bytes(), conn.addr); err != nil {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
// We reset the buffer so that we can re-use it for each fragment
|
||||
// created when splitting the packet.
|
||||
conn.buf.Reset()
|
||||
|
||||
// Finally we add the pk to the recovery queue.
|
||||
conn.retransmission.add(sequenceNumber, pk)
|
||||
n += len(content)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read reads from the connection into the byte slice passed. If successful,
|
||||
// the amount of bytes read n is returned, and the error returned will be nil.
|
||||
// Read blocks until a packet is received over the connection, or until the
|
||||
// session is closed or the read times out, in which case an error is returned.
|
||||
func (conn *Conn) Read(b []byte) (n int, err error) {
|
||||
select {
|
||||
case pk := <-conn.packets:
|
||||
if len(b) < pk.Len() {
|
||||
err = conn.wrap(errBufferTooSmall, "read")
|
||||
}
|
||||
return copy(b, pk.Bytes()), err
|
||||
case <-conn.closed:
|
||||
return 0, conn.wrap(net.ErrClosed, "read")
|
||||
case <-conn.readDeadline:
|
||||
return 0, conn.wrap(context.DeadlineExceeded, "read")
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPacket attempts to read the next packet as a byte slice. ReadPacket
|
||||
// blocks until a packet is received over the connection, or until the session
|
||||
// is closed or the read times out, in which case an error is returned.
|
||||
func (conn *Conn) ReadPacket() (b []byte, err error) {
|
||||
select {
|
||||
case packet := <-conn.packets:
|
||||
return packet.Bytes(), err
|
||||
case <-conn.closed:
|
||||
return nil, conn.wrap(net.ErrClosed, "read")
|
||||
case <-conn.readDeadline:
|
||||
return nil, conn.wrap(context.DeadlineExceeded, "read")
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the connection. All blocking Read or Write actions are
|
||||
// cancelled and will return an error, as soon as the closing of the connection
|
||||
// is acknowledged by the client.
|
||||
func (conn *Conn) Close() error {
|
||||
conn.closing.CompareAndSwap(0, time.Now().Unix())
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeImmediately sends a Disconnect notification to the other end of the
|
||||
// connection and closes the underlying UDP connection immediately.
|
||||
func (conn *Conn) closeImmediately() {
|
||||
conn.once.Do(func() {
|
||||
_, _ = conn.Write([]byte{packets.IDDisconnectNotification})
|
||||
close(conn.closed)
|
||||
if conn.close != nil {
|
||||
conn.close()
|
||||
conn.close = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the connection, meaning the address
|
||||
// this connection leads to.
|
||||
func (conn *Conn) RemoteAddr() net.Addr {
|
||||
return conn.addr
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the connection, which is always the
|
||||
// same as the listener's.
|
||||
func (conn *Conn) LocalAddr() net.Addr {
|
||||
return conn.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the read deadline of the connection. An error is
|
||||
// returned only if the time passed is before time.Now(). Calling
|
||||
// SetReadDeadline means the next Read call that exceeds the deadline will fail
|
||||
// and return an error. Setting the read deadline to the default value of
|
||||
// time.Time removes the deadline.
|
||||
func (conn *Conn) SetReadDeadline(t time.Time) error {
|
||||
if t.IsZero() {
|
||||
conn.readDeadline = make(chan time.Time)
|
||||
return nil
|
||||
}
|
||||
if t.Before(time.Now()) {
|
||||
panic(fmt.Errorf("read deadline cannot be before now"))
|
||||
}
|
||||
conn.readDeadline = time.After(time.Until(t))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline has no behaviour. It is merely there to satisfy the
|
||||
// network.Conn interface.
|
||||
func (conn *Conn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeadline sets the deadline of the connection for both Read and Write.
|
||||
// SetDeadline is equivalent to calling both SetReadDeadline and
|
||||
// SetWriteDeadline.
|
||||
func (conn *Conn) SetDeadline(t time.Time) error {
|
||||
return conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// Latency returns a rolling average of rtt between the sending and the
|
||||
// receiving end of the connection. The rtt returned is updated continuously
|
||||
// and is half the average round trip time (RTT).
|
||||
func (conn *Conn) Latency() time.Duration {
|
||||
return time.Duration(conn.rtt.Load() / 2)
|
||||
}
|
||||
|
||||
// sendPing pings the connection, updating the rtt of the Conn if successful.
|
||||
func (conn *Conn) sendPing() {
|
||||
b := bytes.NewBuffer(nil)
|
||||
(&packets.ConnectedPing{ClientTimestamp: timestamp()}).Write(b)
|
||||
_, _ = conn.Write(b.Bytes())
|
||||
}
|
||||
|
||||
// packetPool is a sync.Pool used to pool packets that encapsulate their
|
||||
// content.
|
||||
var packetPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &packets.Packet{Reliability: packets.ReliabilityReliableOrdered}
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
// Datagram header +
|
||||
// Datagram sequence number +
|
||||
// Packet header +
|
||||
// Packet content length +
|
||||
// Packet message index +
|
||||
// Packet order index +
|
||||
// Packet order channel
|
||||
packetAdditionalSize = 1 + 3 + 1 + 2 + 3 + 3 + 1
|
||||
// Packet split count +
|
||||
// Packet split ID +
|
||||
// Packet split index
|
||||
splitAdditionalSize = 4 + 2 + 4
|
||||
)
|
||||
|
||||
// split splits a content buffer in smaller buffers so that they do not exceed
|
||||
// the MTU size that the connection holds.
|
||||
func (conn *Conn) split(b []byte) [][]byte {
|
||||
maxSize := int(conn.mtuSize-packetAdditionalSize) - 28
|
||||
contentLength := len(b)
|
||||
if contentLength > maxSize {
|
||||
// If the content size is bigger than the maximum size here, it means
|
||||
// the packet will get split. This means that the packet will get even
|
||||
// bigger because a split packet uses 4 + 2 + 4 more bytes.
|
||||
maxSize -= splitAdditionalSize
|
||||
}
|
||||
fragmentCount := contentLength / maxSize
|
||||
if contentLength%maxSize != 0 {
|
||||
// If the content length can't be divided by maxSize perfectly, we need
|
||||
// to reserve another fragment for the last bit of the packet.
|
||||
fragmentCount++
|
||||
}
|
||||
fragments := make([][]byte, fragmentCount)
|
||||
|
||||
buf := bytes.NewBuffer(b)
|
||||
for i := 0; i < fragmentCount; i++ {
|
||||
// Take a piece out of the content with the size of maxSize.
|
||||
fragments[i] = buf.Next(maxSize)
|
||||
}
|
||||
return fragments
|
||||
}
|
||||
|
||||
// receive receives a packet from the connection, handling it as appropriate.
|
||||
// If not successful, an error is returned.
|
||||
func (conn *Conn) receive(b *bytes.Buffer) error {
|
||||
headerFlags, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading datagram header flags: %v", err)
|
||||
}
|
||||
if headerFlags&packets.BitFlagDatagram == 0 {
|
||||
// Ignore packets that do not have the datagram bitflag.
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
conn.lastActivity.Store(&t)
|
||||
switch {
|
||||
case headerFlags&packets.BitFlagACK != 0:
|
||||
return conn.handleACK(b)
|
||||
case headerFlags&packets.BitFlagNACK != 0:
|
||||
return conn.handleNACK(b)
|
||||
default:
|
||||
return conn.receiveDatagram(b)
|
||||
}
|
||||
}
|
||||
|
||||
// receiveDatagram handles the receiving of a datagram found in buffer b. If
|
||||
// successful, all packets inside the datagram are handled. if not, an error is
|
||||
// returned.
|
||||
func (conn *Conn) receiveDatagram(b *bytes.Buffer) error {
|
||||
seq, err := utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading datagram sequence number: %v", err)
|
||||
}
|
||||
conn.ackMu.Lock()
|
||||
// Add this sequence number to the received datagrams, so that it is
|
||||
// included in an ACK.
|
||||
conn.ackSlice = append(conn.ackSlice, seq)
|
||||
conn.ackMu.Unlock()
|
||||
|
||||
if !conn.win.new(seq) {
|
||||
// Datagram was already received, this might happen if a packet took a long time to arrive, and we already sent
|
||||
// a NACK for it. This is expected to happen sometimes under normal circumstances, so no reason to return an
|
||||
// error.
|
||||
return nil
|
||||
}
|
||||
conn.win.add(seq)
|
||||
if conn.win.shift() == 0 {
|
||||
// Datagram window couldn't be shifted up, so we're still missing
|
||||
// packets.
|
||||
rtt := time.Duration(conn.rtt.Load())
|
||||
if missing := conn.win.missing(rtt + rtt/2); len(missing) > 0 {
|
||||
if err = conn.sendNACK(missing); err != nil {
|
||||
return fmt.Errorf("error sending NACK to request datagrams: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if conn.win.size() > maxWindowSize && conn.limits {
|
||||
return fmt.Errorf("datagram receive queue window size is too big (%v-%v)", conn.win.lowest, conn.win.highest)
|
||||
}
|
||||
return conn.handleDatagram(b)
|
||||
}
|
||||
|
||||
// handleDatagram handles the contents of a datagram encoded in a bytes.Buffer.
|
||||
func (conn *Conn) handleDatagram(b *bytes.Buffer) error {
|
||||
for b.Len() > 0 {
|
||||
if err := conn.pk.Read(b); err != nil {
|
||||
return fmt.Errorf("error decoding datagram packet: %v", err)
|
||||
}
|
||||
handle := conn.receivePacket
|
||||
if conn.pk.Split {
|
||||
handle = conn.receiveSplitPacket
|
||||
}
|
||||
if err := handle(conn.pk); err != nil {
|
||||
return fmt.Errorf("error handling packet in datagram: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// receivePacket handles the receiving of a packet. It puts the packet in the
|
||||
// queue and takes out all packets that were obtainable after that, and handles
|
||||
// them.
|
||||
func (conn *Conn) receivePacket(packet *packets.Packet) error {
|
||||
if packet.Reliability != packets.ReliabilityReliableOrdered {
|
||||
// If it isn't a reliable ordered packet, handle it immediately.
|
||||
return conn.handlePacket(packet.Content)
|
||||
}
|
||||
if !conn.packetQueue.put(packet.OrderIndex, packet.Content) {
|
||||
// An ordered packet arrived twice.
|
||||
return nil
|
||||
}
|
||||
if conn.packetQueue.WindowSize() > maxWindowSize && conn.limits {
|
||||
return fmt.Errorf("packet queue window size is too big (%v-%v)", conn.packetQueue.lowest, conn.packetQueue.highest)
|
||||
}
|
||||
for _, content := range conn.packetQueue.fetch() {
|
||||
if err := conn.handlePacket(content); err != nil {
|
||||
return fmt.Errorf("error handling packet: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlePacket handles a packet serialised in byte slice b. If not successful,
|
||||
// an error is returned. If the packet was not handled by RakNet, it is sent to
|
||||
// the packet channel.
|
||||
func (conn *Conn) handlePacket(b []byte) error {
|
||||
buffer := bytes.NewBuffer(b)
|
||||
id, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading packet ID: %v", err)
|
||||
}
|
||||
|
||||
switch id {
|
||||
case packets.IDConnectionRequest:
|
||||
return conn.handleConnectionRequest(buffer)
|
||||
case packets.IDConnectionRequestAccepted:
|
||||
return conn.handleConnectionRequestAccepted(buffer)
|
||||
case packets.IDNewIncomingConnection:
|
||||
select {
|
||||
case <-conn.connected:
|
||||
default:
|
||||
close(conn.connected)
|
||||
}
|
||||
case packets.IDConnectedPing:
|
||||
return conn.handleConnectedPing(buffer)
|
||||
case packets.IDConnectedPong:
|
||||
return conn.handleConnectedPong(buffer)
|
||||
case packets.IDDisconnectNotification:
|
||||
conn.closeImmediately()
|
||||
case packets.IDDetectLostConnections:
|
||||
// Let the other end know the connection is still alive.
|
||||
conn.sendPing()
|
||||
default:
|
||||
_ = buffer.UnreadByte()
|
||||
// Insert the packet contents the packet queue could release in the
|
||||
// channel so that Conn.Read() can get a hold of them, but always first
|
||||
// try to escape if the connection was closed.
|
||||
select {
|
||||
case <-conn.closed:
|
||||
case conn.packets <- buffer:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConnectedPing handles a connected ping packet inside of buffer b. An
|
||||
// error is returned if the packet was invalid.
|
||||
func (conn *Conn) handleConnectedPing(b *bytes.Buffer) error {
|
||||
packet := &packets.ConnectedPing{}
|
||||
if err := packet.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading connected ping: %v", err)
|
||||
}
|
||||
b.Reset()
|
||||
|
||||
// Respond with a connected pong that has the ping timestamp found in the
|
||||
// connected ping, and our own timestamp for the pong timestamp.
|
||||
(&packets.ConnectedPong{ClientTimestamp: packet.ClientTimestamp, ServerTimestamp: timestamp()}).Write(b)
|
||||
_, err := conn.Write(b.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// handleConnectedPong handles a connected pong packet inside of buffer b. An
|
||||
// error is returned if the packet was invalid.
|
||||
func (conn *Conn) handleConnectedPong(b *bytes.Buffer) error {
|
||||
packet := &packets.ConnectedPong{}
|
||||
if err := packet.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading connected pong: %v", err)
|
||||
}
|
||||
if packet.ClientTimestamp > timestamp() {
|
||||
return fmt.Errorf("error measuring rtt: ping timestamp is in the future")
|
||||
}
|
||||
// We don't actually use the ConnectedPong to measure rtt. It is too
|
||||
// unreliable and doesn't give a good idea of the connection quality.
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleConnectionRequest handles a connection request packet inside of buffer
|
||||
// b. An error is returned if the packet was invalid.
|
||||
func (conn *Conn) handleConnectionRequest(b *bytes.Buffer) error {
|
||||
packet := &packets.ConnectionRequest{}
|
||||
if err := packet.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading connection request: %v", err)
|
||||
}
|
||||
b.Reset()
|
||||
(&packets.ConnectionRequestAccepted{ClientAddress: *conn.addr.(*net.UDPAddr), RequestTimestamp: packet.RequestTimestamp, AcceptedTimestamp: timestamp()}).Write(b)
|
||||
_, err := conn.Write(b.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// handleConnectionRequestAccepted handles a serialised connection request
|
||||
// accepted packet in b, and returns an error if not successful.
|
||||
func (conn *Conn) handleConnectionRequestAccepted(b *bytes.Buffer) error {
|
||||
packet := &packets.ConnectionRequestAccepted{}
|
||||
_ = packet.Read(b)
|
||||
b.Reset()
|
||||
|
||||
(&packets.NewIncomingConnection{ServerAddress: *conn.addr.(*net.UDPAddr), RequestTimestamp: packet.RequestTimestamp, AcceptedTimestamp: packet.AcceptedTimestamp, SystemAddresses: packet.SystemAddresses}).Write(b)
|
||||
_, err := conn.Write(b.Bytes())
|
||||
|
||||
select {
|
||||
case <-conn.connected:
|
||||
default:
|
||||
close(conn.connected)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// receiveSplitPacket handles a passed split packet. If it is the last split
|
||||
// packet of its sequence, it will continue handling the full packet as it
|
||||
// otherwise would. An error is returned if the packet was not valid.
|
||||
func (conn *Conn) receiveSplitPacket(p *packets.Packet) error {
|
||||
const maxSplitCount = 256
|
||||
if (p.SplitCount > maxSplitCount || len(conn.splits) > maxSplitCount) && conn.limits {
|
||||
return fmt.Errorf("split count %v (%v active) exceeds the maximum %v", p.SplitCount, len(conn.splits), maxSplitCount)
|
||||
}
|
||||
m, ok := conn.splits[p.SplitID]
|
||||
if !ok {
|
||||
m = make([][]byte, p.SplitCount)
|
||||
conn.splits[p.SplitID] = m
|
||||
}
|
||||
if p.SplitIndex > uint32(len(m)-1) {
|
||||
// The split index was either negative or was bigger than the slice
|
||||
// size, meaning the packet is invalid.
|
||||
return fmt.Errorf("error handing split packet: split index %v is out of range (0 - %v)", p.SplitIndex, len(m)-1)
|
||||
}
|
||||
m[p.SplitIndex] = p.Content
|
||||
|
||||
size := 0
|
||||
for _, fragment := range m {
|
||||
if len(fragment) == 0 {
|
||||
// We haven't yet received all split fragments, so we cannot add the packets together yet.
|
||||
return nil
|
||||
}
|
||||
// First we calculate the total size required to hold the content of the
|
||||
// combined content.
|
||||
size += len(fragment)
|
||||
}
|
||||
|
||||
content := make([]byte, 0, size)
|
||||
for _, fragment := range m {
|
||||
content = append(content, fragment...)
|
||||
}
|
||||
|
||||
delete(conn.splits, p.SplitID)
|
||||
|
||||
p.Content = content
|
||||
return conn.receivePacket(p)
|
||||
}
|
||||
|
||||
// sendACK sends an acknowledgement packet containing the packet sequence
|
||||
// numbers passed. If not successful, an error is returned.
|
||||
func (conn *Conn) sendACK(received ...utils.Uint24) error {
|
||||
defer conn.ackBuf.Reset()
|
||||
return conn.sendAcknowledgement(received, packets.BitFlagACK, conn.ackBuf)
|
||||
}
|
||||
|
||||
// sendNACK sends an acknowledgement packet containing the packet sequence
|
||||
// numbers passed. If not successful, an error is returned.
|
||||
func (conn *Conn) sendNACK(received []utils.Uint24) error {
|
||||
defer conn.nackBuf.Reset()
|
||||
return conn.sendAcknowledgement(received, packets.BitFlagNACK, conn.nackBuf)
|
||||
}
|
||||
|
||||
// sendAcknowledgement sends an acknowledgement packet with the packets passed,
|
||||
// potentially sending multiple if too many packets are passed. The bitflag is
|
||||
// added to the header byte.
|
||||
func (conn *Conn) sendAcknowledgement(received []utils.Uint24, bitflag byte, buf *bytes.Buffer) error {
|
||||
ack := &packets.Acknowledgement{Packets: received}
|
||||
|
||||
for len(ack.Packets) != 0 {
|
||||
buf.WriteByte(bitflag | packets.BitFlagDatagram)
|
||||
n, err := ack.Write(buf, conn.mtuSize)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error encoding ACK packet: %v", err))
|
||||
}
|
||||
// We managed to write n packets in the ACK with this MTU size, write
|
||||
// the next of the packets in a new ACK.
|
||||
ack.Packets = ack.Packets[n:]
|
||||
if _, err := conn.conn.WriteTo(buf.Bytes(), conn.addr); err != nil {
|
||||
return fmt.Errorf("error sending ACK packet: %v", err)
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleACK handles an acknowledgement packet from the other end of the
|
||||
// connection. These mean that a datagram was successfully received by the
|
||||
// other end.
|
||||
func (conn *Conn) handleACK(b *bytes.Buffer) error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
ack := &packets.Acknowledgement{}
|
||||
if err := ack.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading ACK: %v", err)
|
||||
}
|
||||
for _, sequenceNumber := range ack.Packets {
|
||||
// Take out all stored packets from the recovery queue.
|
||||
p, ok := conn.retransmission.acknowledge(sequenceNumber)
|
||||
if ok {
|
||||
// Clear the packet and return it to the pool so that it may be
|
||||
// re-used.
|
||||
p.Content = nil
|
||||
packetPool.Put(p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleNACK handles a negative acknowledgment packet from the other end of
|
||||
// the connection. These mean that a datagram was found missing.
|
||||
func (conn *Conn) handleNACK(b *bytes.Buffer) error {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
|
||||
nack := &packets.Acknowledgement{}
|
||||
if err := nack.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading NACK: %v", err)
|
||||
}
|
||||
return conn.resend(nack.Packets)
|
||||
}
|
||||
|
||||
// resend sends all datagrams currently in the recovery queue with the sequence
|
||||
// numbers passed.
|
||||
func (conn *Conn) resend(sequenceNumbers []utils.Uint24) (err error) {
|
||||
for _, sequenceNumber := range sequenceNumbers {
|
||||
pk, ok := conn.retransmission.retransmit(sequenceNumber)
|
||||
if !ok {
|
||||
// We could not resend this datagram. Maybe it was already resent
|
||||
// before at the request of the client. This is generally expected
|
||||
// so we just continue.
|
||||
continue
|
||||
}
|
||||
|
||||
// We first write a new datagram header using a new send sequence number
|
||||
// that we find.
|
||||
if err := conn.buf.WriteByte(packets.BitFlagDatagram | packets.BitFlagNeedsBAndAS); err != nil {
|
||||
return fmt.Errorf("error writing recovered datagram header: %v", err)
|
||||
}
|
||||
newSeqNum := conn.seq
|
||||
conn.seq++
|
||||
utils.WriteUint24(conn.buf, newSeqNum)
|
||||
pk.Write(conn.buf)
|
||||
|
||||
// We then send the pk to the connection.
|
||||
if _, err := conn.conn.WriteTo(conn.buf.Bytes(), conn.addr); err != nil {
|
||||
return fmt.Errorf("error sending pk to addr %v: %v", conn.addr, err)
|
||||
}
|
||||
// We then re-add the pk to the recovery queue in case the new one gets
|
||||
// lost too, in which case we need to resend it again.
|
||||
conn.retransmission.add(newSeqNum, pk)
|
||||
conn.buf.Reset()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// requestConnection requests the connection from the server, provided this
|
||||
// connection operates as a client. An error occurs if the request was not
|
||||
// successful.
|
||||
func (conn *Conn) requestConnection(id int64) error {
|
||||
b := bytes.NewBuffer(nil)
|
||||
(&packets.ConnectionRequest{ClientGUID: id, RequestTimestamp: timestamp()}).Write(b)
|
||||
_, err := conn.Write(b.Bytes())
|
||||
return err
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"cimeyclust.com/steel/pkg/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
// datagramWindow is a queue for incoming datagrams.
|
||||
type datagramWindow struct {
|
||||
lowest, highest utils.Uint24
|
||||
queue map[utils.Uint24]time.Time
|
||||
}
|
||||
|
||||
// newDatagramWindow returns a new initialised datagram window.
|
||||
func newDatagramWindow() *datagramWindow {
|
||||
return &datagramWindow{queue: make(map[utils.Uint24]time.Time)}
|
||||
}
|
||||
|
||||
// new checks if the index passed is new to the datagramWindow.
|
||||
func (win *datagramWindow) new(index utils.Uint24) bool {
|
||||
if index < win.lowest {
|
||||
return true
|
||||
}
|
||||
_, ok := win.queue[index]
|
||||
return !ok
|
||||
}
|
||||
|
||||
// add puts an index in the window.
|
||||
func (win *datagramWindow) add(index utils.Uint24) {
|
||||
if index >= win.highest {
|
||||
win.highest = index + 1
|
||||
}
|
||||
win.queue[index] = time.Now()
|
||||
}
|
||||
|
||||
// shift attempts to delete as many indices from the queue as possible,
|
||||
// increasing the lowest index if and when possible.
|
||||
func (win *datagramWindow) shift() (n int) {
|
||||
var index utils.Uint24
|
||||
for index = win.lowest; index < win.highest; index++ {
|
||||
if _, ok := win.queue[index]; !ok {
|
||||
break
|
||||
}
|
||||
delete(win.queue, index)
|
||||
n++
|
||||
}
|
||||
win.lowest = index
|
||||
return n
|
||||
}
|
||||
|
||||
// missing returns a slice of all indices in the datagram queue that weren't
|
||||
// set using add while within the window of lowest and highest index. The queue
|
||||
// is shifted after this call.
|
||||
func (win *datagramWindow) missing(since time.Duration) (indices []utils.Uint24) {
|
||||
var (
|
||||
missing = false
|
||||
)
|
||||
for index := int(win.highest) - 1; index >= int(win.lowest); index-- {
|
||||
i := utils.Uint24(index)
|
||||
t, ok := win.queue[i]
|
||||
if ok {
|
||||
if time.Since(t) >= since {
|
||||
// All packets before this one took too long to arrive, so we
|
||||
// mark them as missing.
|
||||
missing = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
if missing {
|
||||
indices = append(indices, i)
|
||||
win.queue[i] = time.Time{}
|
||||
}
|
||||
}
|
||||
win.shift()
|
||||
return indices
|
||||
}
|
||||
|
||||
// size returns the size of the datagramWindow.
|
||||
func (win *datagramWindow) size() utils.Uint24 {
|
||||
return win.highest - win.lowest
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
errBufferTooSmall = errors.New("a message sent was larger than the buffer used to receive the message into")
|
||||
errListenerClosed = errors.New("use of closed listener")
|
||||
)
|
||||
|
||||
// ErrConnectionClosed checks if the error passed was an error caused by
|
||||
// reading from a Conn of which the connection was closed.
|
||||
func ErrConnectionClosed(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), net.ErrClosed.Error())
|
||||
}
|
||||
|
||||
// wrap wraps the error passed into a net.OpError with the op as operation and
|
||||
// returns it, or nil if the error passed is nil.
|
||||
func (conn *Conn) wrap(err error, op string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &net.OpError{
|
||||
Op: op,
|
||||
Net: "raknet",
|
||||
Source: conn.LocalAddr(),
|
||||
Addr: conn.RemoteAddr(),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
@ -1,313 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cimeyclust.com/steel/pkg/cmd"
|
||||
"cimeyclust.com/steel/pkg/network/packets"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UpstreamPacketListener allows for a custom PacketListener implementation.
|
||||
type UpstreamPacketListener interface {
|
||||
ListenPacket(network, address string) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
// ListenConfig may be used to pass additional configuration to a Listener.
|
||||
type ListenConfig struct {
|
||||
// ErrorLog is a logger that errors from packet decoding are logged to. It
|
||||
// may be set to a logger that simply discards the messages. The default
|
||||
// value is slog.Default().
|
||||
ErrorLog *slog.Logger
|
||||
|
||||
// UpstreamPacketListener adds an abstraction for net.ListenPacket.
|
||||
UpstreamPacketListener UpstreamPacketListener
|
||||
}
|
||||
|
||||
// Listener implements a RakNet connection listener. It follows the same
|
||||
// methods as those implemented by the TCPListener in the net package. Listener
|
||||
// implements the network.Listener interface.
|
||||
type Listener struct {
|
||||
once sync.Once
|
||||
closed chan struct{}
|
||||
|
||||
conn net.PacketConn
|
||||
// incoming is a channel of incoming connections. Connections that end up in
|
||||
// here will also end up in the connections map.
|
||||
incoming chan *Conn
|
||||
|
||||
// connections is a map of currently active connections, indexed by their
|
||||
// address.
|
||||
connections sync.Map
|
||||
|
||||
// id is a random server ID generated upon starting listening. It is used
|
||||
// several times throughout the connection sequence of RakNet.
|
||||
id int64
|
||||
|
||||
// pongData is a byte slice of data that is sent in an unconnected pong
|
||||
// packet each time the client sends and unconnected ping to the server.
|
||||
pongData atomic.Pointer[[]byte]
|
||||
}
|
||||
|
||||
// listenerID holds the next ID to use for a Listener.
|
||||
var listenerID atomic.Int64
|
||||
|
||||
func init() {
|
||||
listenerID.Store(rand.New(rand.NewSource(time.Now().Unix())).Int63())
|
||||
}
|
||||
|
||||
// Listen listens on the address passed and returns a listener that may be used
|
||||
// to accept connections. If not successful, an error is returned. The address
|
||||
// follows the same rules as those defined in the net.TCPListen() function.
|
||||
// Specific features of the listener may be modified once it is returned, such
|
||||
// as the used log and/or the accepted protocol.
|
||||
func (l ListenConfig) Listen(address string) (*Listener, error) {
|
||||
var conn net.PacketConn
|
||||
var err error
|
||||
|
||||
if l.UpstreamPacketListener == nil {
|
||||
conn, err = net.ListenPacket("udp", address)
|
||||
} else {
|
||||
conn, err = l.UpstreamPacketListener.ListenPacket("udp", address)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, &net.OpError{Op: "listen", Net: "raknet", Source: nil, Addr: nil, Err: err}
|
||||
}
|
||||
listener := &Listener{
|
||||
conn: conn,
|
||||
incoming: make(chan *Conn),
|
||||
closed: make(chan struct{}),
|
||||
id: listenerID.Add(1),
|
||||
}
|
||||
|
||||
go listener.listen()
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
// Listen listens on the address passed and returns a listener that may be used
|
||||
// to accept connections. If not successful, an error is returned. The address
|
||||
// follows the same rules as those defined in the net.TCPListen() function.
|
||||
// Specific features of the listener may be modified once it is returned, such
|
||||
// as the used log and/or the accepted protocol.
|
||||
func Listen(address string) (*Listener, error) {
|
||||
var lc ListenConfig
|
||||
return lc.Listen(address)
|
||||
}
|
||||
|
||||
// Accept blocks until a connection can be accepted by the listener. If
|
||||
// successful, Accept returns a connection that is ready to send and receive
|
||||
// data. If not successful, a nil listener is returned and an error describing
|
||||
// the problem.
|
||||
func (listener *Listener) Accept() (net.Conn, error) {
|
||||
conn, ok := <-listener.incoming
|
||||
if !ok {
|
||||
return nil, &net.OpError{Op: "accept", Net: "raknet", Source: nil, Addr: nil, Err: errListenerClosed}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Addr returns the address the Listener is bound to and listening for
|
||||
// connections on.
|
||||
func (listener *Listener) Addr() net.Addr {
|
||||
return listener.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// Close closes the listener so that it may be cleaned up. It makes sure the
|
||||
// goroutine handling incoming packets is able to be freed.
|
||||
func (listener *Listener) Close() error {
|
||||
var err error
|
||||
listener.once.Do(func() {
|
||||
close(listener.closed)
|
||||
err = listener.conn.Close()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// PongData sets the pong data that is used to respond with when a client sends
|
||||
// a ping. It usually holds game specific data that is used to display in a
|
||||
// server list. If a data slice is set with a size bigger than math.MaxInt16,
|
||||
// the function panics.
|
||||
func (listener *Listener) PongData(data []byte) {
|
||||
if len(data) > math.MaxInt16 {
|
||||
panic(fmt.Sprintf("error setting pong data: pong data must not be longer than %v", math.MaxInt16))
|
||||
}
|
||||
listener.pongData.Store(&data)
|
||||
}
|
||||
|
||||
// ID returns the unique ID of the listener. This ID is usually used by a
|
||||
// client to identify a specific server during a single session.
|
||||
func (listener *Listener) ID() int64 {
|
||||
return listener.id
|
||||
}
|
||||
|
||||
// listen continuously reads from the listener's UDP connection, until closed
|
||||
// has a value in it.
|
||||
func (listener *Listener) listen() {
|
||||
// Create a buffer with the maximum size a UDP packet sent over RakNet is
|
||||
// allowed to have. We can re-use this buffer for each packet.
|
||||
b := make([]byte, 1500)
|
||||
buf := bytes.NewBuffer(b[:0])
|
||||
for {
|
||||
n, addr, err := listener.conn.ReadFrom(b)
|
||||
if err != nil {
|
||||
close(listener.incoming)
|
||||
return
|
||||
}
|
||||
_, _ = buf.Write(b[:n])
|
||||
|
||||
// Technically we should not re-use the same byte slice after its
|
||||
// ownership has been taken by the buffer, but we can do this anyway
|
||||
// because we copy the data later.
|
||||
if err := listener.handle(buf, addr); err != nil {
|
||||
cmd.Logger.Error(fmt.Sprintf("listener: handle packet: %v from %s", err, addr.String()))
|
||||
}
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// handle handles an incoming packet in buffer b from the address passed. If
|
||||
// not successful, an error is returned describing the issue.
|
||||
func (listener *Listener) handle(b *bytes.Buffer, addr net.Addr) error {
|
||||
value, found := listener.connections.Load(addr.String())
|
||||
if !found {
|
||||
// If there was no session yet, it means the packet is an offline
|
||||
// message. It is not contained in a datagram.
|
||||
packetID, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading packet ID byte: %v", err)
|
||||
}
|
||||
switch packetID {
|
||||
case packets.IDUnconnectedPing, packets.IDUnconnectedPingOpenConnections:
|
||||
return listener.handleUnconnectedPing(b, addr)
|
||||
case packets.IDOpenConnectionRequest1:
|
||||
return listener.handleOpenConnectionRequest1(b, addr)
|
||||
case packets.IDOpenConnectionRequest2:
|
||||
return listener.handleOpenConnectionRequest2(b, addr)
|
||||
default:
|
||||
// In some cases, the client will keep trying to send datagrams
|
||||
// while it has already timed out. In this case, we should not print
|
||||
// an error.
|
||||
if packetID&packets.BitFlagDatagram == 0 {
|
||||
return fmt.Errorf("unknown packet received (%x): %x", packetID, b.Bytes())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
conn := value.(*Conn)
|
||||
select {
|
||||
case <-conn.closed:
|
||||
// Connection was closed already.
|
||||
return nil
|
||||
default:
|
||||
err := conn.receive(b)
|
||||
if err != nil {
|
||||
conn.closeImmediately()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// handleOpenConnectionRequest2 handles an open connection request 2 packet
|
||||
// stored in buffer b, coming from an address addr.
|
||||
func (listener *Listener) handleOpenConnectionRequest2(b *bytes.Buffer, addr net.Addr) error {
|
||||
packet := &packets.OpenConnectionRequest2{}
|
||||
if err := packet.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading open connection request 2: %v", err)
|
||||
}
|
||||
b.Reset()
|
||||
|
||||
mtuSize := packet.ClientPreferredMTUSize
|
||||
if mtuSize > maxMTUSize {
|
||||
mtuSize = maxMTUSize
|
||||
}
|
||||
|
||||
(&packets.OpenConnectionReply2{ServerGUID: listener.id, ClientAddress: *addr.(*net.UDPAddr), MTUSize: mtuSize}).Write(b)
|
||||
if _, err := listener.conn.WriteTo(b.Bytes(), addr); err != nil {
|
||||
return fmt.Errorf("error sending open connection reply 2: %v", err)
|
||||
}
|
||||
|
||||
conn := newConn(listener.conn, addr, packet.ClientPreferredMTUSize)
|
||||
conn.close = func() {
|
||||
// Make sure to remove the connection from the Listener once the Conn is
|
||||
// closed.
|
||||
listener.connections.Delete(addr.String())
|
||||
}
|
||||
listener.connections.Store(addr.String(), conn)
|
||||
|
||||
go func() {
|
||||
t := time.NewTimer(time.Second * 10)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-conn.connected:
|
||||
// Add the connection to the incoming channel so that a caller of
|
||||
// Accept() can receive it.
|
||||
listener.incoming <- conn
|
||||
case <-listener.closed:
|
||||
_ = conn.Close()
|
||||
case <-t.C:
|
||||
// It took too long to complete this connection. We closed it and go
|
||||
// back to accepting.
|
||||
_ = conn.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleOpenConnectionRequest1 handles an open connection request 1 packet
|
||||
// stored in buffer b, coming from an address addr.
|
||||
func (listener *Listener) handleOpenConnectionRequest1(b *bytes.Buffer, addr net.Addr) error {
|
||||
packet := &packets.OpenConnectionRequest1{}
|
||||
if err := packet.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading open connection request 1: %v", err)
|
||||
}
|
||||
b.Reset()
|
||||
mtuSize := packet.MaximumSizeNotDropped
|
||||
if mtuSize > maxMTUSize {
|
||||
mtuSize = maxMTUSize
|
||||
}
|
||||
|
||||
if packet.Protocol != currentProtocol {
|
||||
(&packets.IncompatibleProtocolVersion{ServerGUID: listener.id, ServerProtocol: currentProtocol}).Write(b)
|
||||
_, _ = listener.conn.WriteTo(b.Bytes(), addr)
|
||||
return fmt.Errorf("error handling open connection request 1: incompatible protocol version %v (listener protocol = %v)", packet.Protocol, currentProtocol)
|
||||
}
|
||||
|
||||
(&packets.OpenConnectionReply1{ServerGUID: listener.id, Secure: false, ServerPreferredMTUSize: mtuSize}).Write(b)
|
||||
_, err := listener.conn.WriteTo(b.Bytes(), addr)
|
||||
return err
|
||||
}
|
||||
|
||||
// handleUnconnectedPing handles an unconnected ping packet stored in buffer b,
|
||||
// coming from an address addr.
|
||||
func (listener *Listener) handleUnconnectedPing(b *bytes.Buffer, addr net.Addr) error {
|
||||
pk := &packets.UnconnectedPing{}
|
||||
if err := pk.Read(b); err != nil {
|
||||
return fmt.Errorf("error reading unconnected ping: %v", err)
|
||||
}
|
||||
cmd.Logger.Info(fmt.Sprintf("Buffer length: %v", b.Len()))
|
||||
b.Reset()
|
||||
|
||||
cmd.Logger.Info(fmt.Sprintf("Received unconnected ping from %s", addr.String()))
|
||||
// Log pongData if it is set.
|
||||
if data := listener.pongData.Load(); data != nil {
|
||||
cmd.Logger.Info(fmt.Sprintf("Pong data: %v", data))
|
||||
} else {
|
||||
cmd.Logger.Info("No pong data set")
|
||||
}
|
||||
cmd.Logger.Info(fmt.Sprintf("Ping data: "))
|
||||
(&packets.UnconnectedPong{ServerGUID: listener.id, SendTimestamp: pk.SendTimestamp, Data: *listener.pongData.Load()}).Write(b)
|
||||
_, err := listener.conn.WriteTo(b.Bytes(), addr)
|
||||
return err
|
||||
}
|
||||
|
||||
// timestamp returns a timestamp in milliseconds.
|
||||
func timestamp() int64 {
|
||||
return time.Now().UnixNano() / int64(time.Second)
|
||||
}
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"cimeyclust.com/steel/pkg/cmd"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/sandertv/go-raknet"
|
||||
"net"
|
||||
)
|
||||
|
||||
@ -13,7 +14,7 @@ func Run(baseCtx context.Context, addr string) {
|
||||
defer cancel()
|
||||
|
||||
// Start listening on the specified address
|
||||
listener, err := Listen(addr)
|
||||
listener, err := raknet.Listen(addr)
|
||||
if err != nil {
|
||||
cmd.Logger.Error(fmt.Sprintf("Error starting UDP server: %v", err))
|
||||
return
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
package network
|
||||
|
||||
import "cimeyclust.com/steel/pkg/utils"
|
||||
|
||||
// packetQueue is an ordered queue for reliable ordered packets.
|
||||
type packetQueue struct {
|
||||
lowest utils.Uint24
|
||||
highest utils.Uint24
|
||||
queue map[utils.Uint24][]byte
|
||||
}
|
||||
|
||||
// newPacketQueue returns a new initialised ordered queue.
|
||||
func newPacketQueue() *packetQueue {
|
||||
return &packetQueue{queue: make(map[utils.Uint24][]byte)}
|
||||
}
|
||||
|
||||
// put puts a value at the index passed. If the index was already occupied
|
||||
// once, false is returned.
|
||||
func (queue *packetQueue) put(index utils.Uint24, packet []byte) bool {
|
||||
if index < queue.lowest {
|
||||
return false
|
||||
}
|
||||
if _, ok := queue.queue[index]; ok {
|
||||
return false
|
||||
}
|
||||
if index >= queue.highest {
|
||||
queue.highest = index + 1
|
||||
}
|
||||
queue.queue[index] = packet
|
||||
return true
|
||||
}
|
||||
|
||||
// fetch attempts to take out as many values from the ordered queue as
|
||||
// possible. Upon encountering an index that has no value yet, the function
|
||||
// returns all values that it did find and takes them out.
|
||||
func (queue *packetQueue) fetch() (packets [][]byte) {
|
||||
index := queue.lowest
|
||||
for index < queue.highest {
|
||||
packet, ok := queue.queue[index]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
delete(queue.queue, index)
|
||||
packets = append(packets, packet)
|
||||
index++
|
||||
}
|
||||
queue.lowest = index
|
||||
return
|
||||
}
|
||||
|
||||
// WindowSize returns the size of the window held by the packet queue.
|
||||
func (queue *packetQueue) WindowSize() utils.Uint24 {
|
||||
return queue.highest - queue.lowest
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type ConnectedPing struct {
|
||||
ClientTimestamp int64
|
||||
}
|
||||
|
||||
func (pk *ConnectedPing) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDConnectedPing)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientTimestamp)
|
||||
}
|
||||
|
||||
func (pk *ConnectedPing) Read(buf *bytes.Buffer) error {
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ClientTimestamp)
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type ConnectedPong struct {
|
||||
ClientTimestamp int64
|
||||
ServerTimestamp int64
|
||||
}
|
||||
|
||||
func (pk *ConnectedPong) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDConnectedPong)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerTimestamp)
|
||||
}
|
||||
|
||||
func (pk *ConnectedPong) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ClientTimestamp)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ServerTimestamp)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type ConnectionRequest struct {
|
||||
ClientGUID int64
|
||||
RequestTimestamp int64
|
||||
Secure bool
|
||||
}
|
||||
|
||||
func (pk *ConnectionRequest) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDConnectionRequest)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientGUID)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.RequestTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.Secure)
|
||||
}
|
||||
|
||||
func (pk *ConnectionRequest) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ClientGUID)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.RequestTimestamp)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.Secure)
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ConnectionRequestAccepted struct {
|
||||
ClientAddress net.UDPAddr
|
||||
SystemAddresses [20]net.UDPAddr
|
||||
RequestTimestamp int64
|
||||
AcceptedTimestamp int64
|
||||
}
|
||||
|
||||
func (pk *ConnectionRequestAccepted) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDConnectionRequestAccepted)
|
||||
writeAddr(buf, pk.ClientAddress)
|
||||
_ = binary.Write(buf, binary.BigEndian, int16(0))
|
||||
for _, addr := range pk.SystemAddresses {
|
||||
writeAddr(buf, addr)
|
||||
}
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.RequestTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.AcceptedTimestamp)
|
||||
}
|
||||
|
||||
func (pk *ConnectionRequestAccepted) Read(buf *bytes.Buffer) error {
|
||||
_ = readAddr(buf, &pk.ClientAddress)
|
||||
buf.Next(2)
|
||||
for i := 0; i < 20; i++ {
|
||||
_ = readAddr(buf, &pk.SystemAddresses[i])
|
||||
if buf.Len() == 16 {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.RequestTimestamp)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.AcceptedTimestamp)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type IncompatibleProtocolVersion struct {
|
||||
Magic [16]byte
|
||||
ServerProtocol byte
|
||||
ServerGUID int64
|
||||
}
|
||||
|
||||
func (pk *IncompatibleProtocolVersion) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDIncompatibleProtocolVersion)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerProtocol)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerGUID)
|
||||
}
|
||||
|
||||
func (pk *IncompatibleProtocolVersion) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ServerProtocol)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ServerGUID)
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type NewIncomingConnection struct {
|
||||
ServerAddress net.UDPAddr
|
||||
SystemAddresses [20]net.UDPAddr
|
||||
RequestTimestamp int64
|
||||
AcceptedTimestamp int64
|
||||
}
|
||||
|
||||
func (pk *NewIncomingConnection) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDNewIncomingConnection)
|
||||
writeAddr(buf, pk.ServerAddress)
|
||||
for _, addr := range pk.SystemAddresses {
|
||||
writeAddr(buf, addr)
|
||||
}
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.RequestTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.AcceptedTimestamp)
|
||||
}
|
||||
|
||||
func (pk *NewIncomingConnection) Read(buf *bytes.Buffer) error {
|
||||
_ = readAddr(buf, &pk.ServerAddress)
|
||||
for i := 0; i < 20; i++ {
|
||||
_ = readAddr(buf, &pk.SystemAddresses[i])
|
||||
if buf.Len() == 16 {
|
||||
break
|
||||
}
|
||||
}
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.RequestTimestamp)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.AcceptedTimestamp)
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type OpenConnectionReply1 struct {
|
||||
Magic [16]byte
|
||||
ServerGUID int64
|
||||
Secure bool
|
||||
ServerPreferredMTUSize uint16
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionReply1) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDOpenConnectionReply1)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerGUID)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.Secure)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerPreferredMTUSize)
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionReply1) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ServerGUID)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Secure)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ServerPreferredMTUSize)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type OpenConnectionReply2 struct {
|
||||
Magic [16]byte
|
||||
ServerGUID int64
|
||||
ClientAddress net.UDPAddr
|
||||
MTUSize uint16
|
||||
Secure bool
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionReply2) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDOpenConnectionReply2)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerGUID)
|
||||
writeAddr(buf, pk.ClientAddress)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.MTUSize)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.Secure)
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionReply2) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ServerGUID)
|
||||
_ = readAddr(buf, &pk.ClientAddress)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.MTUSize)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.Secure)
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type OpenConnectionRequest1 struct {
|
||||
Magic [16]byte
|
||||
Protocol byte
|
||||
MaximumSizeNotDropped uint16
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionRequest1) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDOpenConnectionRequest1)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.Protocol)
|
||||
_, _ = buf.Write(make([]byte, pk.MaximumSizeNotDropped-uint16(buf.Len()+28)))
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionRequest1) Read(buf *bytes.Buffer) error {
|
||||
pk.MaximumSizeNotDropped = uint16(buf.Len()+1) + 28
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.Protocol)
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type OpenConnectionRequest2 struct {
|
||||
Magic [16]byte
|
||||
ServerAddress net.UDPAddr
|
||||
ClientPreferredMTUSize uint16
|
||||
ClientGUID int64
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionRequest2) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDOpenConnectionRequest2)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
writeAddr(buf, pk.ServerAddress)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientPreferredMTUSize)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientGUID)
|
||||
}
|
||||
|
||||
func (pk *OpenConnectionRequest2) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
_ = readAddr(buf, &pk.ServerAddress)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ClientPreferredMTUSize)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ClientGUID)
|
||||
}
|
||||
@ -1,419 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cimeyclust.com/steel/pkg/utils"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
IDConnectedPing byte = 0x00
|
||||
IDUnconnectedPing byte = 0x01
|
||||
IDUnconnectedPingOpenConnections byte = 0x02
|
||||
IDConnectedPong byte = 0x03
|
||||
IDDetectLostConnections byte = 0x04
|
||||
IDOpenConnectionRequest1 byte = 0x05
|
||||
IDOpenConnectionReply1 byte = 0x06
|
||||
IDOpenConnectionRequest2 byte = 0x07
|
||||
IDOpenConnectionReply2 byte = 0x08
|
||||
IDConnectionRequest byte = 0x09
|
||||
IDConnectionRequestAccepted byte = 0x10
|
||||
IDNewIncomingConnection byte = 0x13
|
||||
IDDisconnectNotification byte = 0x15
|
||||
|
||||
IDIncompatibleProtocolVersion byte = 0x19
|
||||
|
||||
IDUnconnectedPong byte = 0x1c
|
||||
)
|
||||
|
||||
// unconnectedMessageSequence is a sequence of bytes which is found in every unconnected message sent in
|
||||
// RakNet.
|
||||
var unconnectedMessageSequence = [16]byte{0x00, 0xff, 0xff, 0x00, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78}
|
||||
|
||||
// writeAddr writes a UDP address to the buffer passed.
|
||||
func writeAddr(buffer *bytes.Buffer, addr net.UDPAddr) {
|
||||
var ver byte = 6
|
||||
if addr.IP.To4() != nil {
|
||||
ver = 4
|
||||
}
|
||||
if addr.IP == nil {
|
||||
addr.IP = make([]byte, 16)
|
||||
}
|
||||
_ = buffer.WriteByte(ver)
|
||||
if ver == 4 {
|
||||
ipBytes := addr.IP.To4()
|
||||
|
||||
_ = buffer.WriteByte(^ipBytes[0])
|
||||
_ = buffer.WriteByte(^ipBytes[1])
|
||||
_ = buffer.WriteByte(^ipBytes[2])
|
||||
_ = buffer.WriteByte(^ipBytes[3])
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint16(addr.Port))
|
||||
} else {
|
||||
_ = binary.Write(buffer, binary.LittleEndian, int16(23)) // syscall.AF_INET6 on Windows.
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint16(addr.Port))
|
||||
// The IPv6 address is enclosed in two 0 integers.
|
||||
_ = binary.Write(buffer, binary.BigEndian, int32(0))
|
||||
_, _ = buffer.Write(addr.IP.To16())
|
||||
_ = binary.Write(buffer, binary.BigEndian, int32(0))
|
||||
}
|
||||
}
|
||||
|
||||
// readAddr decodes a RakNet address from the buffer passed. If not successful, an error is returned.
|
||||
func readAddr(buffer *bytes.Buffer, addr *net.UDPAddr) error {
|
||||
ver, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ver == 4 {
|
||||
ipBytes := make([]byte, 4)
|
||||
if _, err := buffer.Read(ipBytes); err != nil {
|
||||
return fmt.Errorf("error reading raknet address ipv4 bytes: %v", err)
|
||||
}
|
||||
// Construct an IPv4 out of the 4 bytes we just read.
|
||||
addr.IP = net.IPv4((-ipBytes[0]-1)&0xff, (-ipBytes[1]-1)&0xff, (-ipBytes[2]-1)&0xff, (-ipBytes[3]-1)&0xff)
|
||||
var port uint16
|
||||
if err := binary.Read(buffer, binary.BigEndian, &port); err != nil {
|
||||
return fmt.Errorf("error reading raknet address port: %v", err)
|
||||
}
|
||||
addr.Port = int(port)
|
||||
} else {
|
||||
buffer.Next(2)
|
||||
var port uint16
|
||||
if err := binary.Read(buffer, binary.LittleEndian, &port); err != nil {
|
||||
return fmt.Errorf("error reading raknet address port: %v", err)
|
||||
}
|
||||
addr.Port = int(port)
|
||||
buffer.Next(4)
|
||||
addr.IP = make([]byte, 16)
|
||||
if _, err := buffer.Read(addr.IP); err != nil {
|
||||
return fmt.Errorf("error reading raknet address ipv6 bytes: %v", err)
|
||||
}
|
||||
buffer.Next(4)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// BitFlagDatagram is set for every valid datagram. It is used to identify
|
||||
// packets that are datagrams.
|
||||
BitFlagDatagram = 0x80
|
||||
// BitFlagACK is set for every ACK Packet.
|
||||
BitFlagACK = 0x40
|
||||
// BitFlagNACK is set for every NACK Packet.
|
||||
BitFlagNACK = 0x20
|
||||
// BitFlagNeedsBAndAS is set for every datagram with Packet data, but is not
|
||||
// actually used.
|
||||
BitFlagNeedsBAndAS = 0x04
|
||||
)
|
||||
|
||||
// noinspection GoUnusedConst
|
||||
const (
|
||||
// ReliabilityUnreliable means that the Packet sent could arrive out of
|
||||
// order, be duplicated, or just not arrive at all. It is usually used for
|
||||
// high frequency packets of which the order does not matter.
|
||||
// lint:ignore U1000 While this constant is unused, it is here for the sake
|
||||
// of having all reliabilities documented.
|
||||
ReliabilityUnreliable byte = iota
|
||||
// ReliabilityUnreliableSequenced means that the Packet sent could be
|
||||
// duplicated or not arrive at all, but ensures that it is always handled in
|
||||
// the right order.
|
||||
ReliabilityUnreliableSequenced
|
||||
// ReliabilityReliable means that the Packet sent could not arrive, or
|
||||
// arrive out of order, but ensures that the Packet is not duplicated.
|
||||
ReliabilityReliable
|
||||
// ReliabilityReliableOrdered means that every Packet sent arrives, arrives
|
||||
// in the right order and is not duplicated.
|
||||
ReliabilityReliableOrdered
|
||||
// ReliabilityReliableSequenced means that the Packet sent could not arrive,
|
||||
// but ensures that the Packet will be in the right order and not be
|
||||
// duplicated.
|
||||
ReliabilityReliableSequenced
|
||||
|
||||
// SplitFlag is set in the header if the Packet was split. If so, the
|
||||
// encapsulation contains additional data about the fragment.
|
||||
SplitFlag = 0x10
|
||||
)
|
||||
|
||||
// Packet is an encapsulation around every Packet sent after the connection is
|
||||
// established. It is
|
||||
type Packet struct {
|
||||
Reliability byte
|
||||
|
||||
Content []byte
|
||||
MessageIndex utils.Uint24
|
||||
sequenceIndex utils.Uint24
|
||||
OrderIndex utils.Uint24
|
||||
|
||||
Split bool
|
||||
SplitCount uint32
|
||||
SplitIndex uint32
|
||||
SplitID uint16
|
||||
}
|
||||
|
||||
// Write writes the Packet and its Content to the buffer passed.
|
||||
func (packet *Packet) Write(b *bytes.Buffer) {
|
||||
header := packet.Reliability << 5
|
||||
if packet.Split {
|
||||
header |= SplitFlag
|
||||
}
|
||||
b.WriteByte(header)
|
||||
_ = binary.Write(b, binary.BigEndian, uint16(len(packet.Content))<<3)
|
||||
if packet.reliable() {
|
||||
utils.WriteUint24(b, packet.MessageIndex)
|
||||
}
|
||||
if packet.sequenced() {
|
||||
utils.WriteUint24(b, packet.sequenceIndex)
|
||||
}
|
||||
if packet.sequencedOrOrdered() {
|
||||
utils.WriteUint24(b, packet.OrderIndex)
|
||||
// Order channel, we don't care about this.
|
||||
b.WriteByte(0)
|
||||
}
|
||||
if packet.Split {
|
||||
_ = binary.Write(b, binary.BigEndian, packet.SplitCount)
|
||||
_ = binary.Write(b, binary.BigEndian, packet.SplitID)
|
||||
_ = binary.Write(b, binary.BigEndian, packet.SplitIndex)
|
||||
}
|
||||
b.Write(packet.Content)
|
||||
}
|
||||
|
||||
// Read reads a Packet and its Content from the buffer passed.
|
||||
func (packet *Packet) Read(b *bytes.Buffer) error {
|
||||
header, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading Packet header: %v", err)
|
||||
}
|
||||
packet.Split = (header & SplitFlag) != 0
|
||||
packet.Reliability = (header & 224) >> 5
|
||||
var packetLength uint16
|
||||
if err := binary.Read(b, binary.BigEndian, &packetLength); err != nil {
|
||||
return fmt.Errorf("error reading Packet length: %v", err)
|
||||
}
|
||||
packetLength >>= 3
|
||||
if packetLength == 0 {
|
||||
return fmt.Errorf("invalid Packet length: cannot be 0")
|
||||
}
|
||||
|
||||
if packet.reliable() {
|
||||
packet.MessageIndex, err = utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading Packet message index: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if packet.sequenced() {
|
||||
packet.sequenceIndex, err = utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading Packet sequence index: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if packet.sequencedOrOrdered() {
|
||||
packet.OrderIndex, err = utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading Packet order index: %v", err)
|
||||
}
|
||||
// Order channel (byte), we don't care about this.
|
||||
b.Next(1)
|
||||
}
|
||||
|
||||
if packet.Split {
|
||||
if err := binary.Read(b, binary.BigEndian, &packet.SplitCount); err != nil {
|
||||
return fmt.Errorf("error reading Packet split count: %v", err)
|
||||
}
|
||||
if err := binary.Read(b, binary.BigEndian, &packet.SplitID); err != nil {
|
||||
return fmt.Errorf("error reading Packet split ID: %v", err)
|
||||
}
|
||||
if err := binary.Read(b, binary.BigEndian, &packet.SplitIndex); err != nil {
|
||||
return fmt.Errorf("error reading Packet split index: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
packet.Content = make([]byte, packetLength)
|
||||
if n, err := b.Read(packet.Content); err != nil || n != int(packetLength) {
|
||||
return fmt.Errorf("not enough data in Packet: %v bytes read but need %v", n, packetLength)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (packet *Packet) reliable() bool {
|
||||
switch packet.Reliability {
|
||||
case ReliabilityReliable,
|
||||
ReliabilityReliableOrdered,
|
||||
ReliabilityReliableSequenced:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (packet *Packet) sequencedOrOrdered() bool {
|
||||
switch packet.Reliability {
|
||||
case ReliabilityUnreliableSequenced,
|
||||
ReliabilityReliableOrdered,
|
||||
ReliabilityReliableSequenced:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (packet *Packet) sequenced() bool {
|
||||
switch packet.Reliability {
|
||||
case ReliabilityUnreliableSequenced,
|
||||
ReliabilityReliableSequenced:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
// packetRange indicates a range of packets, followed by the first and the
|
||||
// last Packet in the range.
|
||||
packetRange = iota
|
||||
// packetSingle indicates a single Packet, followed by its sequence number.
|
||||
packetSingle
|
||||
)
|
||||
|
||||
// Acknowledgement is an Acknowledgement Packet that may either be an ACK or a
|
||||
// NACK, depending on the purpose that it is sent with.
|
||||
type Acknowledgement struct {
|
||||
Packets []utils.Uint24
|
||||
}
|
||||
|
||||
// Write encodes an Acknowledgement Packet and returns an error if not
|
||||
// successful.
|
||||
func (ack *Acknowledgement) Write(b *bytes.Buffer, mtu uint16) (n int, err error) {
|
||||
packets := ack.Packets
|
||||
if len(packets) == 0 {
|
||||
return 0, binary.Write(b, binary.BigEndian, int16(0))
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
// Sort packets before encoding to ensure packets are encoded correctly.
|
||||
sort.Slice(packets, func(i, j int) bool {
|
||||
return packets[i] < packets[j]
|
||||
})
|
||||
|
||||
var firstPacketInRange utils.Uint24
|
||||
var lastPacketInRange utils.Uint24
|
||||
var recordCount int16
|
||||
|
||||
for index, packet := range packets {
|
||||
if buffer.Len() >= int(mtu-10) {
|
||||
// We must make sure the final Packet length doesn't exceed the MTU
|
||||
// size.
|
||||
break
|
||||
}
|
||||
n++
|
||||
if index == 0 {
|
||||
// The first Packet, set the first and last Packet to it.
|
||||
firstPacketInRange = packet
|
||||
lastPacketInRange = packet
|
||||
continue
|
||||
}
|
||||
if packet == lastPacketInRange+1 {
|
||||
// Packet is still part of the current range, as it's sequenced
|
||||
// properly with the last Packet. Set the last Packet in range to
|
||||
// the Packet and continue to the next Packet.
|
||||
lastPacketInRange = packet
|
||||
continue
|
||||
} else {
|
||||
// We got to the end of a range/single Packet. We need to write
|
||||
// those down now.
|
||||
if firstPacketInRange == lastPacketInRange {
|
||||
// First Packet equals last Packet, so we have a single Packet
|
||||
// record. Write down the Packet, and set the first and last
|
||||
// Packet to the current Packet.
|
||||
if err := buffer.WriteByte(packetSingle); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
utils.WriteUint24(buffer, firstPacketInRange)
|
||||
|
||||
firstPacketInRange = packet
|
||||
lastPacketInRange = packet
|
||||
} else {
|
||||
// There's a gap between the first and last Packet, so we have a
|
||||
// range of packets. Write the first and last Packet of the
|
||||
// range and set both to the current Packet.
|
||||
if err := buffer.WriteByte(packetRange); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
utils.WriteUint24(buffer, firstPacketInRange)
|
||||
utils.WriteUint24(buffer, lastPacketInRange)
|
||||
|
||||
firstPacketInRange = packet
|
||||
lastPacketInRange = packet
|
||||
}
|
||||
// Keep track of the amount of records as we need to write that
|
||||
// first.
|
||||
recordCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the last single Packet/range is written, as we always need to
|
||||
// know one Packet ahead to know how we should write the current.
|
||||
if firstPacketInRange == lastPacketInRange {
|
||||
if err := buffer.WriteByte(packetSingle); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
utils.WriteUint24(buffer, firstPacketInRange)
|
||||
} else {
|
||||
if err := buffer.WriteByte(packetRange); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
utils.WriteUint24(buffer, firstPacketInRange)
|
||||
utils.WriteUint24(buffer, lastPacketInRange)
|
||||
}
|
||||
recordCount++
|
||||
if err := binary.Write(b, binary.BigEndian, recordCount); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := b.Write(buffer.Bytes()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Read decodes an Acknowledgement Packet and returns an error if not
|
||||
// successful.
|
||||
func (ack *Acknowledgement) Read(b *bytes.Buffer) error {
|
||||
const maxAcknowledgementPackets = 8192
|
||||
var recordCount int16
|
||||
if err := binary.Read(b, binary.BigEndian, &recordCount); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := int16(0); i < recordCount; i++ {
|
||||
recordType, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch recordType {
|
||||
case packetRange:
|
||||
start, err := utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
end, err := utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for pack := start; pack <= end; pack++ {
|
||||
ack.Packets = append(ack.Packets, pack)
|
||||
if len(ack.Packets) > maxAcknowledgementPackets {
|
||||
return fmt.Errorf("maximum amount of packets in acknowledgement exceeded")
|
||||
}
|
||||
}
|
||||
case packetSingle:
|
||||
packet, err := utils.ReadUint24(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ack.Packets = append(ack.Packets, packet)
|
||||
if len(ack.Packets) > maxAcknowledgementPackets {
|
||||
return fmt.Errorf("maximum amount of packets in acknowledgement exceeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type UnconnectedPing struct {
|
||||
Magic [16]byte
|
||||
SendTimestamp int64
|
||||
ClientGUID int64
|
||||
}
|
||||
|
||||
func (pk *UnconnectedPing) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDUnconnectedPing)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.SendTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ClientGUID)
|
||||
}
|
||||
|
||||
func (pk *UnconnectedPing) Read(buf *bytes.Buffer) error {
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.SendTimestamp)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
return binary.Read(buf, binary.BigEndian, &pk.ClientGUID)
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package packets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
type UnconnectedPong struct {
|
||||
Magic [16]byte
|
||||
SendTimestamp int64
|
||||
ServerGUID int64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (pk *UnconnectedPong) Write(buf *bytes.Buffer) {
|
||||
_ = binary.Write(buf, binary.BigEndian, IDUnconnectedPong)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.SendTimestamp)
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.ServerGUID)
|
||||
_ = binary.Write(buf, binary.BigEndian, unconnectedMessageSequence)
|
||||
_ = binary.Write(buf, binary.BigEndian, int16(len(pk.Data)))
|
||||
_ = binary.Write(buf, binary.BigEndian, pk.Data)
|
||||
}
|
||||
|
||||
func (pk *UnconnectedPong) Read(buf *bytes.Buffer) error {
|
||||
var l int16
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.SendTimestamp)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.ServerGUID)
|
||||
_ = binary.Read(buf, binary.BigEndian, &pk.Magic)
|
||||
_ = binary.Read(buf, binary.BigEndian, &l)
|
||||
pk.Data = make([]byte, l)
|
||||
_, err := buf.Read(pk.Data)
|
||||
return err
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"cimeyclust.com/steel/pkg/network/packets"
|
||||
"cimeyclust.com/steel/pkg/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
// resendMap is a map of packets, used to recover datagrams if the other end of
|
||||
// the connection ended up not having them.
|
||||
type resendMap struct {
|
||||
unacknowledged map[utils.Uint24]resendRecord
|
||||
delays map[time.Time]time.Duration
|
||||
}
|
||||
|
||||
// resendRecord represents a single packet with a timestamp from when it was
|
||||
// initially sent. It may be either acknowledged or NACKed by the other end.
|
||||
type resendRecord struct {
|
||||
pk *packets.Packet
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// newRecoveryQueue returns a new initialised recovery queue.
|
||||
func newRecoveryQueue() *resendMap {
|
||||
return &resendMap{
|
||||
delays: make(map[time.Time]time.Duration),
|
||||
unacknowledged: make(map[utils.Uint24]resendRecord),
|
||||
}
|
||||
}
|
||||
|
||||
// add puts a packet at the index passed and records the current time.
|
||||
func (m *resendMap) add(index utils.Uint24, pk *packets.Packet) {
|
||||
m.unacknowledged[index] = resendRecord{pk: pk, timestamp: time.Now()}
|
||||
}
|
||||
|
||||
// acknowledge marks a packet with the index passed as acknowledged. The packet
|
||||
// is removed from the resendMap and returned if found.
|
||||
func (m *resendMap) acknowledge(index utils.Uint24) (*packets.Packet, bool) {
|
||||
return m.remove(index, 1)
|
||||
}
|
||||
|
||||
// retransmit looks up a packet with an index from the resendMap so that it may
|
||||
// be resent.
|
||||
func (m *resendMap) retransmit(index utils.Uint24) (*packets.Packet, bool) {
|
||||
return m.remove(index, 2)
|
||||
}
|
||||
|
||||
// remove deletes an index from the resendMap and adds the time since the
|
||||
// packet was originally sent multiplied by mul to the delays slice.
|
||||
func (m *resendMap) remove(index utils.Uint24, mul int) (*packets.Packet, bool) {
|
||||
record, ok := m.unacknowledged[index]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
delete(m.unacknowledged, index)
|
||||
|
||||
now := time.Now()
|
||||
m.delays[now] = now.Sub(record.timestamp) * time.Duration(mul)
|
||||
return record.pk, true
|
||||
}
|
||||
|
||||
// rtt returns the average round trip time between the putting of the value
|
||||
// into the recovery queue and the taking out of it again. It is measured over
|
||||
// the last delayRecordCount values add in.
|
||||
func (m *resendMap) rtt() time.Duration {
|
||||
const averageDuration = time.Second * 5
|
||||
var (
|
||||
total, records time.Duration
|
||||
now = time.Now()
|
||||
)
|
||||
for t, rtt := range m.delays {
|
||||
if now.Sub(t) > averageDuration {
|
||||
delete(m.delays, t)
|
||||
continue
|
||||
}
|
||||
total += rtt
|
||||
records++
|
||||
}
|
||||
if records == 0 {
|
||||
// No records yet, generally should not happen. Just return a reasonable amount of time.
|
||||
return time.Millisecond * 50
|
||||
}
|
||||
return total / records
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Uint24 represents an integer existing out of 3 bytes. It is actually a
|
||||
// uint32, but is an alias for the sake of clarity.
|
||||
type Uint24 uint32
|
||||
|
||||
// ReadUint24 reads 3 bytes from the buffer passed and combines it into a
|
||||
// uint24. If there were no 3 bytes to read, an error is returned.
|
||||
func ReadUint24(b *bytes.Buffer) (Uint24, error) {
|
||||
ba, _ := b.ReadByte()
|
||||
bb, _ := b.ReadByte()
|
||||
bc, err := b.ReadByte()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error reading uint24: %v", err)
|
||||
}
|
||||
return Uint24(ba) | (Uint24(bb) << 8) | (Uint24(bc) << 16), nil
|
||||
}
|
||||
|
||||
// WriteUint24 writes a uint24 to the buffer passed as 3 bytes. If not
|
||||
// successful, an error is returned.
|
||||
func WriteUint24(b *bytes.Buffer, value Uint24) {
|
||||
b.WriteByte(byte(value))
|
||||
b.WriteByte(byte(value >> 8))
|
||||
b.WriteByte(byte(value >> 16))
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user