protocol.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package bwtest
  2. import (
  3. "context"
  4. "encoding/binary"
  5. "errors"
  6. "hash/fnv"
  7. "math"
  8. "time"
  9. )
  10. const (
  11. MagicBytes uint32 = 0x42575453 // "BWTS"
  12. ProtoVer uint8 = 1
  13. PacketTypeData uint8 = 1
  14. PacketTypeReport uint8 = 2
  15. PacketTypeStop uint8 = 3
  16. HeaderSize = 36
  17. )
  18. var (
  19. ErrInvalidMagic = errors.New("invalid bandwidth test packet magic")
  20. ErrShortPacket = errors.New("packet shorter than minimum header size")
  21. )
  22. // PacketHeader represents the 36-byte binary header on every UDP test datagram
  23. type PacketHeader struct {
  24. Magic uint32
  25. Version uint8
  26. PacketType uint8
  27. StreamID uint8
  28. Flags uint8
  29. SessionHash uint64
  30. Sequence uint64
  31. TimestampNano int64
  32. PayloadLength uint32
  33. }
  34. // HashSessionID generates a fast 64-bit FNV-1a hash of the session ID string
  35. func HashSessionID(sessionID string) uint64 {
  36. h := fnv.New64a()
  37. _, _ = h.Write([]byte(sessionID))
  38. return h.Sum64()
  39. }
  40. // EncodeHeader serializes the packet header into the provided byte slice (must be >= 36 bytes)
  41. func EncodeHeader(b []byte, hdr PacketHeader) {
  42. binary.BigEndian.PutUint32(b[0:4], MagicBytes)
  43. b[4] = ProtoVer
  44. b[5] = hdr.PacketType
  45. b[6] = hdr.StreamID
  46. b[7] = hdr.Flags
  47. binary.BigEndian.PutUint64(b[8:16], hdr.SessionHash)
  48. binary.BigEndian.PutUint64(b[16:24], hdr.Sequence)
  49. binary.BigEndian.PutUint64(b[24:32], uint64(hdr.TimestampNano))
  50. binary.BigEndian.PutUint32(b[32:36], hdr.PayloadLength)
  51. }
  52. // DecodeHeader deserializes the binary packet header from byte slice
  53. func DecodeHeader(b []byte) (PacketHeader, error) {
  54. if len(b) < HeaderSize {
  55. return PacketHeader{}, ErrShortPacket
  56. }
  57. magic := binary.BigEndian.Uint32(b[0:4])
  58. if magic != MagicBytes {
  59. return PacketHeader{}, ErrInvalidMagic
  60. }
  61. return PacketHeader{
  62. Magic: magic,
  63. Version: b[4],
  64. PacketType: b[5],
  65. StreamID: b[6],
  66. Flags: b[7],
  67. SessionHash: binary.BigEndian.Uint64(b[8:16]),
  68. Sequence: binary.BigEndian.Uint64(b[16:24]),
  69. TimestampNano: int64(binary.BigEndian.Uint64(b[24:32])),
  70. PayloadLength: binary.BigEndian.Uint32(b[32:36]),
  71. }, nil
  72. }
  73. // JitterCalculator computes RFC 3550 statistical interarrival jitter
  74. type JitterCalculator struct {
  75. hasPrev bool
  76. prevTransit int64
  77. jitterFloat float64 // in nanoseconds
  78. }
  79. // Update incorporates a newly received packet's sender timestamp and arrival time
  80. func (jc *JitterCalculator) Update(senderTsNano, arrivalTsNano int64) float64 {
  81. transit := arrivalTsNano - senderTsNano
  82. if !jc.hasPrev {
  83. jc.prevTransit = transit
  84. jc.hasPrev = true
  85. return 0
  86. }
  87. diff := math.Abs(float64(transit - jc.prevTransit))
  88. jc.prevTransit = transit
  89. // RFC 3550: J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16
  90. jc.jitterFloat += (diff - jc.jitterFloat) / 16.0
  91. return jc.jitterFloat / 1e6 // Return in milliseconds
  92. }
  93. // RateLimiter implements a high-precision token bucket for pacing outbound test traffic
  94. type RateLimiter struct {
  95. bps int64
  96. tokens float64
  97. maxTokens float64
  98. lastCheckTime time.Time
  99. }
  100. // NewRateLimiter initializes a rate limiter for target bits per second (0 = no limit)
  101. func NewRateLimiter(targetBps int64) *RateLimiter {
  102. if targetBps <= 0 {
  103. return nil
  104. }
  105. bytesPerSec := float64(targetBps) / 8.0
  106. return &RateLimiter{
  107. bps: targetBps,
  108. tokens: bytesPerSec * 0.05, // 50ms burst allowance
  109. maxTokens: bytesPerSec * 0.10, // 100ms max burst
  110. lastCheckTime: time.Now(),
  111. }
  112. }
  113. // Wait preserves the original API; cancellable workers use WaitContext.
  114. func (rl *RateLimiter) Wait(bytes int) { rl.WaitContext(context.Background(), bytes) }
  115. // A frame can exceed the nominal 100ms bucket capacity. Grow that bucket to at
  116. // least one frame; otherwise low-rate TCP tests wait forever for unreachable tokens.
  117. func (rl *RateLimiter) WaitContext(ctx context.Context, bytes int) bool {
  118. if rl == nil || rl.bps <= 0 {
  119. return ctx.Err() == nil
  120. }
  121. needed := float64(bytes)
  122. if rl.maxTokens < needed {
  123. rl.maxTokens = needed
  124. }
  125. rate := float64(rl.bps) / 8
  126. for {
  127. if ctx.Err() != nil {
  128. return false
  129. }
  130. now := time.Now()
  131. rl.tokens += now.Sub(rl.lastCheckTime).Seconds() * rate
  132. rl.lastCheckTime = now
  133. if rl.tokens > rl.maxTokens {
  134. rl.tokens = rl.maxTokens
  135. }
  136. if rl.tokens >= needed {
  137. rl.tokens -= needed
  138. return true
  139. }
  140. wait := time.Duration((needed - rl.tokens) / rate * float64(time.Second))
  141. if wait > 50*time.Millisecond {
  142. wait = 50 * time.Millisecond
  143. }
  144. timer := time.NewTimer(wait)
  145. select {
  146. case <-ctx.Done():
  147. timer.Stop()
  148. return false
  149. case <-timer.C:
  150. }
  151. }
  152. }