github.com/randomizedcoder/goTrackRTP@v0.0.2/trackRTP.go (about) 1 package goTrackRTP 2 3 // goTracker is for tracking RTP sequence number arrivals 4 5 // The general strategy is to store the sequence numbers is a B-tree 6 // The number of items (.Len()) is the number recieved in the window 7 8 // https://github.com/randomizedcoder/goTracker/ 9 10 // https://pkg.go.dev/container/ring 11 // https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/container/ring/ring.go 12 // https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/container/ring/example_test.go 13 14 import ( 15 "errors" 16 "log" 17 18 "github.com/google/btree" 19 ) 20 21 const ( 22 BtreeDegreeCst = 3 23 24 ClearFreeListCst = true 25 26 maxUint16 = ^uint16(0) 27 ) 28 29 var ( 30 ErrUnimplmented = errors.New("ErrUnimplmented") 31 ErrPosition = errors.New("ErrPosition") 32 ) 33 34 type Tracker struct { 35 b *btree.BTreeG[uint16] 36 37 aw uint16 // aheadWindow 38 bw uint16 // behindWindow 39 ab uint16 // aheadBuffer 40 bb uint16 // behindBuffer 41 42 awPlusAb uint16 // aw + ab 43 bwPlusBb uint16 // bw + bb 44 Window uint16 // aw + bw 45 46 debugLevel int 47 } 48 49 type Taxonomy struct { 50 Position int 51 Categroy int 52 SubCategory int 53 Len int 54 Jump uint16 55 } 56 57 // Position 58 const ( 59 PositionUnknown int = iota 60 PositionInit 61 PositionAhead 62 PositionBehind 63 PositionDuplicate 64 ) 65 66 // Category 67 const ( 68 CategoryUnknown int = iota 69 CategoryRestart 70 CategoryBuffer 71 CategoryWindow 72 ) 73 74 // SubCategory 75 const ( 76 SubCategoryUnknown int = iota 77 SubCategoryNext 78 SubCategoryDuplicate 79 SubCategoryAlready 80 SubCategoryJump 81 ) 82 83 type TrackIntToStringMap struct { 84 PosMap map[int]string 85 CatMap map[int]string 86 SubCatMap map[int]string 87 } 88 89 func NewMaps() *TrackIntToStringMap { 90 91 pm := make(map[int]string) 92 pm[PositionUnknown] = "Unknown" 93 pm[PositionInit] = "Init" 94 pm[PositionBehind] = "Behind" 95 pm[PositionDuplicate] = "Duplicate" 96 97 cm := make(map[int]string) 98 cm[CategoryUnknown] = "Unknown" 99 cm[CategoryRestart] = "Restart" 100 cm[CategoryBuffer] = "Buffer" 101 cm[CategoryWindow] = "Window" 102 103 sm := make(map[int]string) 104 sm[SubCategoryUnknown] = "Unknown" 105 sm[SubCategoryNext] = "Next" 106 sm[SubCategoryDuplicate] = "Duplicate" 107 sm[SubCategoryAlready] = "Already" 108 sm[SubCategoryJump] = "Jump" 109 110 return &TrackIntToStringMap{ 111 PosMap: pm, 112 CatMap: cm, 113 SubCatMap: sm, 114 } 115 } 116 117 // New creates a Tracker 118 // aw = ahead window 119 // bw = behind window 120 // ab = ahead buffer 121 // bb = behind buffer 122 func New(aw uint16, bw uint16, ab uint16, bb uint16, debugLevel int) (*Tracker, error) { 123 124 return NewDegree(aw, bw, ab, bb, BtreeDegreeCst, debugLevel) 125 126 } 127 128 // New creates a Tracker allowing the BTree "degree" to be specified 129 // See also "degree" or branching factor: https://en.wikipedia.org/wiki/Branching_factor 130 func NewDegree(aw uint16, bw uint16, ab uint16, bb uint16, degree int, debugLevel int) (*Tracker, error) { 131 132 err := validateNew(aw, bw, ab, bb, degree) 133 if err != nil { 134 return nil, err 135 } 136 137 return &Tracker{ 138 b: btree.NewG[uint16](degree, isLess), 139 //b: btree.NewOrderedG[uint16](degree), 140 aw: aw, 141 bw: bw, 142 ab: ab, 143 bb: bb, 144 awPlusAb: aw + ab, 145 bwPlusBb: bw + bb, 146 Window: aw + bw, 147 148 debugLevel: debugLevel, 149 }, nil 150 } 151 152 // PacketArrival is the primary packet handling entry point 153 func (t *Tracker) PacketArrival(seq uint16) (*Taxonomy, error) { 154 155 if t.debugLevel > 10 { 156 log.Printf("PacketArrival, seq:%d", seq) 157 } 158 159 m, ok := t.b.Max() 160 if !ok { 161 return t.init(seq) 162 } 163 164 if t.debugLevel > 10 { 165 log.Printf("PacketArrival, seq:%d, m:%d", seq, m) 166 } 167 168 if seq == m { 169 return t.positionDuplicate(seq, m) 170 } 171 172 if isLessBranchless(seq, m) { 173 return t.positionBehind(seq, m) 174 } else { 175 return t.positionAhead(seq, m) 176 } 177 } 178 179 // init is initilizing the data structure on the first packet received 180 func (t *Tracker) init(seq uint16) (*Taxonomy, error) { 181 182 if t.debugLevel > 10 { 183 m, _ := t.b.Max() 184 log.Printf("init, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 185 } 186 187 tax := &Taxonomy{} 188 tax.Position = PositionInit 189 190 // https://pkg.go.dev/github.com/google/btree#BTree.ReplaceOrInsert 191 _, already := t.b.ReplaceOrInsert(seq) 192 if already { 193 tax.SubCategory = SubCategoryAlready 194 } 195 196 if t.debugLevel > 10 { 197 m, _ := t.b.Max() 198 log.Printf("init, after insert seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 199 } 200 201 tax.Len = t.b.Len() 202 203 return tax, nil 204 } 205 206 // positionDuplicate is seq == m 207 func (t *Tracker) positionDuplicate(seq, m uint16) (*Taxonomy, error) { 208 209 if t.debugLevel > 10 { 210 log.Printf("positionDuplicate, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 211 } 212 213 tax := &Taxonomy{} 214 tax.Position = PositionDuplicate 215 216 tax.Len = t.b.Len() 217 218 return tax, nil 219 } 220 221 // positionAhead handles seq > Max()2 222 func (t *Tracker) positionAhead(seq, m uint16) (*Taxonomy, error) { 223 224 if t.debugLevel > 10 { 225 log.Printf("positionAhead, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 226 } 227 228 tax := &Taxonomy{} 229 tax.Position = PositionAhead 230 231 diff := uint16Diff(seq, m) 232 233 // m < aheadWindow [aw] < categoryBuffer (no op) [aheadBuffer ] < categoryRestart 234 if diff > t.awPlusAb { 235 return t.categoryRestart(seq, tax) 236 } else if diff > t.aw { 237 return t.categoryBuffer(seq, tax) 238 } 239 return t.aheadWindow(seq, m, diff, tax) 240 } 241 242 // positionBehind handles seq < Max() 243 func (t *Tracker) positionBehind(seq, m uint16) (*Taxonomy, error) { 244 245 if t.debugLevel > 10 { 246 log.Printf("positionBehind, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 247 } 248 249 tax := &Taxonomy{} 250 tax.Position = PositionBehind 251 252 diff := uint16Diff(seq, m) 253 254 // m < behindWindow [bw] < categoryBuffer (no op) [behindBuffer ] < categoryRestart 255 if diff > t.bwPlusBb { 256 257 if t.debugLevel > 10 { 258 log.Printf("positionBehind, seq:%d, diff:%d > t.bwPlusBb:%d)", seq, diff, t.bwPlusBb) 259 } 260 261 return t.categoryRestart(seq, tax) 262 263 } else if diff > t.bw { 264 265 if t.debugLevel > 10 { 266 log.Printf("positionBehind, seq:%d, diff:%d > t.bw:%d", seq, diff, t.bw) 267 } 268 269 return t.categoryBuffer(seq, tax) 270 } 271 272 if t.debugLevel > 10 { 273 log.Println("positionBehind, in window") 274 } 275 276 return t.behindWindow(seq, m, diff, tax) 277 } 278 279 // categoryRestart clears the btree and inserts the new seq 280 // See also: https://pkg.go.dev/github.com/google/btree#BTreeG.Clear 281 func (t *Tracker) categoryRestart(seq uint16, tax *Taxonomy) (*Taxonomy, error) { 282 283 if t.debugLevel > 10 { 284 m, _ := t.b.Max() 285 log.Printf("categoryRestart, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 286 } 287 288 tax.Categroy = CategoryRestart 289 290 t.b.Clear(ClearFreeListCst) 291 292 _, already := t.b.ReplaceOrInsert(seq) 293 if already { 294 tax.SubCategory = SubCategoryAlready 295 } 296 297 tax.Len = t.b.Len() 298 299 return tax, nil 300 } 301 302 // categoryBuffer is essentially a no-op 303 // This is here to make sure we don't reset the window because of some random crazy late/early packet 304 // With well configured windows this shouldn't happen very often, and if it does maybe your network 305 // has different latency characteristics than you think? 306 func (t *Tracker) categoryBuffer(seq uint16, tax *Taxonomy) (*Taxonomy, error) { 307 308 if t.debugLevel > 10 { 309 m, _ := t.b.Max() 310 log.Printf("categoryBuffer, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 311 } 312 313 tax.Categroy = CategoryBuffer 314 315 tax.Len = t.b.Len() 316 317 return tax, nil 318 } 319 320 // aheadWindow is (hopefully) the most common case 321 // we need to move the acceptable window forward by clearing items that fall off the back 322 // See also: https://pkg.go.dev/github.com/google/btree#BTreeG.DescendLessOrEqual 323 func (t *Tracker) aheadWindow(seq, m uint16, diff uint16, tax *Taxonomy) (*Taxonomy, error) { 324 325 if t.debugLevel > 10 { 326 log.Printf("aheadWindow, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 327 } 328 329 tax.Categroy = CategoryWindow 330 tax.Jump = diff 331 332 _, duplicate := t.b.ReplaceOrInsert(seq) 333 m, _ = t.b.Max() 334 if duplicate { 335 tax.SubCategory = SubCategoryDuplicate 336 if t.debugLevel > 10 { 337 log.Printf("aheadWindow, DUPLICATE, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 338 } 339 } else if diff == 1 { 340 tax.SubCategory = SubCategoryNext 341 if t.debugLevel > 10 { 342 log.Printf("aheadWindow, diff==1. This is the best outcome! woot woot!") 343 } 344 } else { 345 tax.SubCategory = SubCategoryJump 346 if t.debugLevel > 10 { 347 log.Printf("aheadWindow, jump:%d", diff) 348 } 349 } 350 351 if t.debugLevel > 10 { 352 m, _ := t.b.Max() 353 min, _ := t.b.Min() 354 log.Printf("aheadWindow inserted, seq:%d, t.b.Max():%d, t.b.Min():%d, t.b.Len():%d, diff:%d", seq, m, min, t.b.Len(), diff) 355 } 356 357 t.deleteItemsFallingOffTheBack(seq) 358 359 tax.Len = t.b.Len() 360 361 return tax, nil 362 } 363 364 // deleteItemsFallingOffTheBack is called by aheadWindow, and deletes 365 // items falling off the back of the behindWindow 366 func (t *Tracker) deleteItemsFallingOffTheBack(seq uint16) { 367 368 min, ok := t.b.Min() 369 if !ok { 370 log.Panicf("aheadWindow Min() not ok:%v, min:%d", ok, min) 371 } 372 373 backOfWindow := seq - t.aw - t.bw + 1 374 if isLess(min, backOfWindow) { 375 376 // Iterate to clear the items which are falling off the back 377 // ( An alternative strategy would be to loop doing deleteMin, 378 // but that would be more calls to the btree. ) 379 380 var deleted []uint16 381 //t.b.DescendLessOrEqual(backOfWindow, func(item uint16) bool { 382 t.b.Ascend(func(item uint16) bool { 383 if t.debugLevel > 10 { 384 log.Printf("aheadWindow, Ascend backOfWindow:%d, min:%d, item:%d", backOfWindow, min, item) 385 } 386 if isLess(item, backOfWindow) { 387 _, ok := t.b.Delete(item) 388 if !ok { 389 log.Panicf("aheadWindow DescendLessOrEqual Delete not ok:%v", item) 390 } 391 deleted = append(deleted, item) 392 if t.debugLevel > 10 { 393 log.Printf("aheadWindow, Ascend backOfWindow:%d, min:%d, deleted item:%d", backOfWindow, min, item) 394 } 395 return true 396 } 397 if t.debugLevel > 10 { 398 if !isLess(item, backOfWindow) { 399 log.Printf("aheadWindow, !isLess(item:%d, backOfWindow:%d)", item, backOfWindow) 400 } 401 } 402 return false 403 }) 404 405 if t.debugLevel > 10 { 406 m, _ := t.b.Max() 407 log.Printf("aheadWindow deleted, seq:%d, t.b.Max():%d, t.b.Len():%d, len(deleted):%d, deleted:%v", 408 seq, m, t.b.Len(), len(deleted), deleted) 409 } 410 } 411 } 412 413 // behindWindow handles when the sequence number is within our current 414 // lookback window 415 func (t *Tracker) behindWindow(seq, m uint16, diff uint16, tax *Taxonomy) (*Taxonomy, error) { 416 417 if t.debugLevel > 10 { 418 log.Printf("behindWindow, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 419 } 420 421 tax.Categroy = CategoryWindow 422 423 _, duplicate := t.b.ReplaceOrInsert(seq) 424 m, _ = t.b.Max() 425 if duplicate { 426 427 tax.SubCategory = SubCategoryDuplicate 428 429 if t.debugLevel > 10 { 430 log.Printf("behindWindow, DUPLICATE, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 431 } 432 433 } 434 // We don't track "jump" behind 435 // else { 436 // tax.SubCategory = SubCategoryJump 437 // if t.debugLevel > 10 { 438 // log.Printf("behindWindow, jump:%d", diff) 439 // } 440 // } 441 442 tax.Jump = diff 443 444 if t.debugLevel > 10 { 445 m, _ := t.b.Max() 446 log.Printf("behindWindow inserted, seq:%d, t.b.Max():%d, t.b.Len():%d", seq, m, t.b.Len()) 447 } 448 449 tax.Len = t.b.Len() 450 451 return tax, nil 452 } 453 454 // Len() returns the current number of items in the btree 455 // Try not to use this function frequently 456 func (t *Tracker) Len() int { 457 458 return t.b.Len() 459 460 } 461 462 // Max() returns the current max item in the btree 463 // Try not to use this function frequently 464 func (t *Tracker) Max() uint16 { 465 466 m, _ := t.b.Max() 467 return m 468 469 } 470 471 // Min() returns the current min item in the btree 472 // Try not to use this function frequently 473 func (t *Tracker) Min() uint16 { 474 475 m, _ := t.b.Min() 476 return m 477 478 } 479 480 // itemsDescending() iterates descending, returning the list of items 481 // Try not to use this function frequently ( expensive ) 482 func (t *Tracker) itemsDescending() (items []uint16) { 483 t.b.Descend(func(item uint16) bool { 484 items = append(items, item) 485 return true 486 }) 487 return items 488 }