109 lines
2.5 KiB
Go
109 lines
2.5 KiB
Go
package gumble
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// PingResponse contains information about a server that responded to a UDP
|
|
// ping packet.
|
|
type PingResponse struct {
|
|
// The address of the pinged server.
|
|
Address *net.UDPAddr
|
|
// The round-trip time from the client to the server.
|
|
Ping time.Duration
|
|
// The server's version. Only the Version field and SemanticVersion method of
|
|
// the value will be valid.
|
|
Version Version
|
|
// The number users currently connected to the server.
|
|
ConnectedUsers int
|
|
// The maximum number of users that can connect to the server.
|
|
MaximumUsers int
|
|
// The maximum audio bitrate per user for the server.
|
|
MaximumBitrate int
|
|
}
|
|
|
|
// Ping sends a UDP ping packet to the given server. If interval is positive,
|
|
// the packet is retransmitted at every interval.
|
|
//
|
|
// Returns a PingResponse and nil on success. The function will return nil and
|
|
// an error if a valid response is not received after the given timeout.
|
|
func Ping(address string, interval, timeout time.Duration) (*PingResponse, error) {
|
|
if timeout < 0 {
|
|
return nil, errors.New("gumble: timeout must be positive")
|
|
}
|
|
deadline := time.Now().Add(timeout)
|
|
conn, err := net.DialTimeout("udp", address, timeout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer conn.Close()
|
|
conn.SetReadDeadline(deadline)
|
|
|
|
var (
|
|
idsLock sync.Mutex
|
|
ids = make(map[string]time.Time)
|
|
)
|
|
|
|
buildSendPacket := func() {
|
|
var packet [12]byte
|
|
if _, err := rand.Read(packet[4:]); err != nil {
|
|
return
|
|
}
|
|
id := string(packet[4:])
|
|
idsLock.Lock()
|
|
ids[id] = time.Now()
|
|
idsLock.Unlock()
|
|
conn.Write(packet[:])
|
|
}
|
|
|
|
if interval > 0 {
|
|
end := make(chan struct{})
|
|
defer close(end)
|
|
go func() {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
buildSendPacket()
|
|
case <-end:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
buildSendPacket()
|
|
|
|
for {
|
|
var incoming [24]byte
|
|
if _, err := io.ReadFull(conn, incoming[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
id := string(incoming[4:12])
|
|
idsLock.Lock()
|
|
sendTime, ok := ids[id]
|
|
idsLock.Unlock()
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
return &PingResponse{
|
|
Address: conn.RemoteAddr().(*net.UDPAddr),
|
|
Ping: time.Since(sendTime),
|
|
Version: Version{
|
|
Version: binary.BigEndian.Uint32(incoming[0:]),
|
|
},
|
|
ConnectedUsers: int(binary.BigEndian.Uint32(incoming[12:])),
|
|
MaximumUsers: int(binary.BigEndian.Uint32(incoming[16:])),
|
|
MaximumBitrate: int(binary.BigEndian.Uint32(incoming[20:])),
|
|
}, nil
|
|
}
|
|
}
|