matterbridge/vendor/layeh.com/gumble/gumble/ping.go
2020-10-01 22:50:56 +02:00

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
}
}