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  }