Use address.Range instead of (start,end) pairs

This commit is contained in:
Bryan Boreham committed 2015-06-10 17:24:36 +01:00
1 parent 1e71d2dacb
commit e21f66db0a
9 files changed
+107 -100

No files matched your search

+9
View File
@@ -15,6 +15,12 @@ type Range struct {
Start, End Address // [Start, End); Start <= End
}
func MakeRange(start Address, size Offset) Range { return Range{Start: start, End: Add(start, size)} }
func (r Range) Size() Offset { return Subtract(r.End, r.Start) }
func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Start, r.End) }
func (r1 Range) Overlaps(r2 Range) bool { return !(r1.Start >= r2.End || r1.End <= r2.Start) }
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
type CIDR struct {
Start Address
PrefixLen int
@@ -39,7 +45,10 @@ func ParseCIDR(s string) (Address, CIDR, error) {
}
func (cidr CIDR) Size() Offset { return 1 << uint(32-cidr.PrefixLen) }
// End is exclusive, same as in Range
func (cidr CIDR) End() Address { return Add(cidr.Start, cidr.Size()) }
func (cidr CIDR) Range() Range { return Range{Start: cidr.Start, End: Add(cidr.Start, cidr.Size())} }
func (cidr CIDR) String() string {
return fmt.Sprintf("%s/%d", cidr.Start.String(), cidr.PrefixLen)
+9 -11
View File
@@ -14,7 +14,7 @@ type allocate struct {
resultChan chan<- allocateResult
hasBeenCancelled func() bool
ident string
subnet address.CIDR
r address.Range // Range we are trying to allocate within
}
// Try returns true if the request is completed, false if pending
@@ -27,31 +27,29 @@ func (g *allocate) Try(alloc *Allocator) bool {
// If we have previously stored an address for this container in this subnet, return it.
if addrs, found := alloc.owned[g.ident]; found {
for _, addr := range addrs {
if g.subnet.Contains(addr) {
if g.r.Contains(addr) {
g.resultChan <- allocateResult{addr, nil}
return true
}
}
}
if !alloc.universe.Overlaps(g.subnet) {
g.resultChan <- allocateResult{0, fmt.Errorf("Subnet %s out of bounds: %s", g.subnet, alloc.universe)}
if !alloc.universe.Overlaps(g.r) {
g.resultChan <- allocateResult{0, fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
return true
}
// Respect RFC1122 exclusions of first and last addresses
start, end := g.subnet.Start+1, g.subnet.End()-1
if ok, addr := alloc.space.Allocate(start, end); ok {
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.subnet)
if ok, addr := alloc.space.Allocate(g.r); ok {
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
alloc.addOwned(g.ident, addr)
g.resultChan <- allocateResult{addr, nil}
return true
}
// out of space
if donor, err := alloc.ring.ChoosePeerToAskForSpace(start, end); err == nil {
alloc.debugln("Decided to ask peer", donor, "for space in subnet", g.subnet)
alloc.sendSpaceRequest(donor, g.subnet)
if donor, err := alloc.ring.ChoosePeerToAskForSpace(g.r.Start, g.r.End); err == nil {
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
alloc.sendSpaceRequest(donor, g.r)
}
return false
+16 -14
View File
@@ -45,7 +45,7 @@ type operation interface {
type Allocator struct {
actionChan chan<- func()
ourName router.PeerName
universe address.CIDR // superset of all ranges
universe address.Range // superset of all ranges
ring *ring.Ring // information on ranges owned by all peers
space space.Space // more detail on ranges owned by us
owned map[string][]address.Address // who owns what address, indexed by container-ID
@@ -60,7 +60,7 @@ type Allocator struct {
}
// NewAllocator creates and initialises a new Allocator
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, universe address.CIDR, quorum uint) *Allocator {
func NewAllocator(ourName router.PeerName, ourUID router.PeerUID, ourNickname string, universe address.Range, quorum uint) *Allocator {
return &Allocator{
ourName: ourName,
universe: universe,
@@ -180,7 +180,9 @@ func hasBeenCancelled(cancelChan <-chan bool) func() bool {
// if there isn't any space we block indefinitely
func (alloc *Allocator) Allocate(ident string, subnet address.CIDR, cancelChan <-chan bool) (address.CIDR, error) {
resultChan := make(chan allocateResult)
op := &allocate{subnet: subnet, resultChan: resultChan, ident: ident,
// Respect RFC1122 exclusions of first and last addresses
r := address.Range{Start: subnet.Start + 1, End: subnet.End() - 1}
op := &allocate{r: r, resultChan: resultChan, ident: ident,
hasBeenCancelled: hasBeenCancelled(cancelChan)}
alloc.doOperation(op, &alloc.pendingAllocates)
result := <-resultChan
@@ -347,12 +349,12 @@ func (alloc *Allocator) OnGossipUnicast(sender router.PeerName, msg []byte) erro
case msgSpaceRequest:
// some other peer asked us for space
decoder := gob.NewDecoder(bytes.NewReader(msg[1:]))
var cidr address.CIDR
if err := decoder.Decode(&cidr); err != nil {
var r address.Range
if err := decoder.Decode(&r); err != nil {
resultChan <- err
return
}
alloc.donateSpace(cidr, sender)
alloc.donateSpace(r, sender)
resultChan <- nil
case msgRingUpdate:
resultChan <- alloc.update(msg[1:])
@@ -570,10 +572,10 @@ func (alloc *Allocator) propose() {
alloc.gossip.GossipBroadcast(alloc.Gossip())
}
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, subnet address.CIDR) {
func (alloc *Allocator) sendSpaceRequest(dest router.PeerName, r address.Range) {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(subnet); err != nil {
if err := enc.Encode(r); err != nil {
panic(err)
}
msg := router.Concat([]byte{msgSpaceRequest}, buf.Bytes())
@@ -633,7 +635,7 @@ func (alloc *Allocator) update(msg []byte) error {
return nil
}
func (alloc *Allocator) donateSpace(subnet address.CIDR, to router.PeerName) {
func (alloc *Allocator) donateSpace(r address.Range, to router.PeerName) {
// No matter what we do, we'll send a unicast gossip
// of our ring back to tha chap who asked for space.
// This serves to both tell him of any space we might
@@ -642,15 +644,15 @@ func (alloc *Allocator) donateSpace(subnet address.CIDR, to router.PeerName) {
defer alloc.sendRingUpdate(to)
alloc.debugln("Peer", to, "asked me for space")
start, end, ok := alloc.space.Donate(subnet.Start, subnet.End())
chunk, ok := alloc.space.Donate(r)
if !ok {
free := alloc.space.NumFreeAddressesInRange(subnet.Start, subnet.End())
free := alloc.space.NumFreeAddressesInRange(r)
common.Assert(free == 0)
alloc.debugln("No space to give to peer", to)
return
}
alloc.debugln("Giving range", start, end, "to", to)
alloc.ring.GrantRangeToHost(start, end, to)
alloc.debugln("Giving range", chunk, "to", to)
alloc.ring.GrantRangeToHost(chunk.Start, chunk.End, to)
}
func (alloc *Allocator) assertInvariants() {
@@ -678,7 +680,7 @@ func (alloc *Allocator) reportFreeSpace() {
freespace := make(map[address.Address]address.Offset)
for _, r := range ranges {
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r.Start, r.End)
freespace[r.Start] = alloc.space.NumFreeAddressesInRange(r)
}
alloc.ring.ReportFree(freespace)
}
+1 -1
View File
@@ -173,7 +173,7 @@ func TestCancel(t *testing.T) {
alloc1, subnet := makeAllocator("01:00:00:02:00:00", CIDR, 2)
alloc1.SetInterfaces(router.connect(alloc1.ourName, alloc1))
alloc2, subnet := makeAllocator("02:00:00:02:00:00", CIDR, 2)
alloc2, _ := makeAllocator("02:00:00:02:00:00", CIDR, 2)
alloc2.SetInterfaces(router.connect(alloc2.ourName, alloc2))
alloc1.claimRingForTesting(alloc1, alloc2)
alloc2.claimRingForTesting(alloc1, alloc2)
+32 -33
View File
@@ -39,40 +39,41 @@ func (s *Space) Clear() {
// Walk down the free list calling f() on the in-range portions, until
// f() returns true or we run out of free space. Return true iff f() returned true
func (s *Space) walkFree(rangeStart, rangeEnd address.Address, f func(start, end address.Address) bool) bool {
if rangeStart >= rangeEnd { // degenerate case
func (s *Space) walkFree(r address.Range, f func(address.Range) bool) bool {
if r.Start >= r.End { // degenerate case
return false
}
for i := 0; i < len(s.free); i += 2 {
start, end := s.free[i], s.free[i+1]
if end <= rangeStart { // this chunk comes before the range
chunk := address.Range{Start: s.free[i], End: s.free[i+1]}
if chunk.End <= r.Start { // this chunk comes before the range
continue
}
if start >= rangeEnd {
if chunk.Start >= r.End {
// all remaining free space is completely after range
break
}
// at this point we know end>start && end>rangeStart && rangeEnd>start && rangeEnd>rangeStart
// therefore max(start, rangeStart) < min(end, rangeEnd)
// at this point we know chunk.End>chunk.Start &&
// chunk.End>r.Start && r.End>chunk.Start && r.End>r.Start
// therefore max(start, r.Start) < min(end, r.End)
// Restrict this block of free space to be in range
if start < rangeStart {
start = rangeStart
if chunk.Start < r.Start {
chunk.Start = r.Start
}
if end > rangeEnd {
end = rangeEnd
if chunk.End > r.End {
chunk.End = r.End
}
// at this point we know start<end
if f(start, end) {
if f(chunk) {
return true
}
}
return false
}
func (s *Space) Allocate(rangeStart, rangeEnd address.Address) (bool, address.Address) {
func (s *Space) Allocate(r address.Range) (bool, address.Address) {
var result address.Address
return s.walkFree(rangeStart, rangeEnd, func(start, end address.Address) bool {
result = start
return s.walkFree(r, func(chunk address.Range) bool {
result = chunk.Start
s.ours = add(s.ours, result, result+1)
s.free = subtract(s.free, result, result+1)
return true
@@ -89,10 +90,10 @@ func (s *Space) Claim(addr address.Address) error {
return nil
}
func (s *Space) NumFreeAddressesInRange(rangeStart, rangeEnd address.Address) address.Offset {
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Offset {
res := address.Offset(0)
s.walkFree(rangeStart, rangeEnd, func(start, end address.Address) bool {
res += address.Subtract(end, start)
s.walkFree(r, func(chunk address.Range) bool {
res += chunk.Size()
return false
})
return res
@@ -111,12 +112,11 @@ func (s *Space) Free(addr address.Address) error {
return nil
}
func (s *Space) biggestFreeRange(rangeStart, rangeEnd address.Address) (biggestStart, biggestEnd address.Address) {
var biggestSize address.Offset
s.walkFree(rangeStart, rangeEnd, func(start, end address.Address) bool {
size := address.Subtract(end, start)
if size >= biggestSize {
biggestStart, biggestEnd = start, end
func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
biggestSize := address.Offset(0)
s.walkFree(r, func(chunk address.Range) bool {
if size := chunk.Size(); size >= biggestSize {
biggest = chunk
biggestSize = size
}
return false
@@ -124,21 +124,20 @@ func (s *Space) biggestFreeRange(rangeStart, rangeEnd address.Address) (biggestS
return
}
func (s *Space) Donate(rangeStart, rangeEnd address.Address) (address.Address, address.Address, bool) {
start, end := s.biggestFreeRange(rangeStart, rangeEnd)
size := address.Subtract(end, start)
func (s *Space) Donate(r address.Range) (address.Range, bool) {
biggest := s.biggestFreeRange(r)
if size == 0 {
return 0, 0, false
if biggest.Size() == 0 {
return address.Range{}, false
}
// Donate half of that biggest free range. Note size/2 rounds down, so
// the resulting donation size rounds up, and in particular can't be empty.
start = address.Add(start, size/2)
biggest.Start = address.Add(biggest.Start, biggest.Size()/2)
s.ours = subtract(s.ours, start, end)
s.free = subtract(s.free, start, end)
return start, end, true
s.ours = subtract(s.ours, biggest.Start, biggest.End)
s.free = subtract(s.free, biggest.Start, biggest.End)
return biggest, true
}
func firstGreater(a []address.Address, x address.Address) int {
+36 -37
View File
@@ -62,12 +62,12 @@ func TestLowlevel(t *testing.T) {
s := New()
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(0))
ok, got := s.Allocate(0, 1000)
ok, got := s.Allocate(address.MakeRange(0, 1000))
wt.AssertFalse(t, ok, "allocate in empty space should fail")
s.Add(100, 100)
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(100))
ok, got = s.Allocate(0, 1000)
ok, got = s.Allocate(address.MakeRange(0, 1000))
wt.AssertTrue(t, ok && got == 100, "allocate")
wt.AssertEquals(t, s.NumFreeAddresses(), address.Offset(99))
wt.AssertNoErr(t, s.Claim(150))
@@ -77,19 +77,19 @@ func TestLowlevel(t *testing.T) {
wt.AssertErrorInterface(t, s.Free(0), (*error)(nil), "free not allocated")
wt.AssertErrorInterface(t, s.Free(100), (*error)(nil), "double free")
start, end, ok := s.Donate(0, 1000)
wt.AssertTrue(t, ok && start == 125 && address.Subtract(end, start) == 25, "donate")
r, ok := s.Donate(address.MakeRange(0, 1000))
wt.AssertTrue(t, ok && r.Start == 125 && r.Size() == 25, "donate")
// test Donate when addresses are scarce
s = New()
start, end, ok = s.Donate(0, 1000)
r, ok = s.Donate(address.MakeRange(0, 1000))
wt.AssertTrue(t, !ok, "donate on empty space should fail")
s.Add(0, 3)
wt.AssertNoErr(t, s.Claim(0))
wt.AssertNoErr(t, s.Claim(2))
start, end, ok = s.Donate(0, 1000)
wt.AssertTrue(t, ok && start == 1 && end == 2, "donate")
start, end, ok = s.Donate(0, 1000)
r, ok = s.Donate(address.MakeRange(0, 1000))
wt.AssertTrue(t, ok && r.Start == 1 && r.End == 2, "donate")
r, ok = s.Donate(address.MakeRange(0, 1000))
wt.AssertTrue(t, !ok, "donate should fail")
}
@@ -104,23 +104,22 @@ func TestSpaceAllocate(t *testing.T) {
)
var (
start = ip(testAddr1)
end = address.Add(ip(testAddr1), 20)
)
space1 := makeSpace(start, size)
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(20))
space1.assertInvariants()
_, addr1 := space1.Allocate(start, end)
_, addr1 := space1.Allocate(address.MakeRange(start, size))
wt.AssertEqualString(t, addr1.String(), testAddr1, "address")
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(19))
space1.assertInvariants()
_, addr2 := space1.Allocate(start, end)
_, addr2 := space1.Allocate(address.MakeRange(start, size))
wt.AssertFalse(t, addr2.String() == testAddr1, "address")
wt.AssertEquals(t, space1.NumFreeAddresses(), address.Offset(18))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddrx)), address.Offset(13))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(ip(testAddr1), ip(testAddry)), address.Offset(18))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}), address.Offset(13))
wt.AssertEquals(t, space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}), address.Offset(18))
space1.assertInvariants()
space1.Free(addr2)
@@ -141,32 +140,33 @@ func TestSpaceFree(t *testing.T) {
containerID = "deadbeef"
)
entireRange := address.MakeRange(ip(testAddr1), 20)
space := makeSpace(ip(testAddr1), 20)
// Check we are prepared to give up the entire space
start, end := space.biggestFreeRange(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, start == ip(testAddr1) && address.Subtract(end, start) == 20, "Wrong space")
r := space.biggestFreeRange(entireRange)
wt.AssertTrue(t, r.Start == ip(testAddr1) && r.Size() == 20, "Wrong space")
for i := 0; i < 20; i++ {
ok, _ := space.Allocate(ip(testAddr1), ip(testAddry))
ok, _ := space.Allocate(entireRange)
wt.AssertTrue(t, ok, "Failed to get address")
}
// Check we are full
ok, _ := space.Allocate(ip(testAddr1), ip(testAddry))
ok, _ := space.Allocate(entireRange)
wt.AssertTrue(t, !ok, "Should have failed to get address")
start, end, ok = space.Donate(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, address.Subtract(end, start) == 0, "Wrong space")
r, ok = space.Donate(entireRange)
wt.AssertTrue(t, r.Size() == 0, "Wrong space")
// Free in the middle
wt.AssertSuccess(t, space.Free(ip("10.0.3.13")))
start, end = space.biggestFreeRange(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, start == ip("10.0.3.13") && address.Subtract(end, start) == 1, "Wrong space")
r = space.biggestFreeRange(entireRange)
wt.AssertTrue(t, r.Start == ip("10.0.3.13") && r.Size() == 1, "Wrong space")
// Free one at the end
wt.AssertSuccess(t, space.Free(ip("10.0.3.23")))
start, end = space.biggestFreeRange(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, start == ip("10.0.3.23") && address.Subtract(end, start) == 1, "Wrong space")
r = space.biggestFreeRange(entireRange)
wt.AssertTrue(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
// Now free a few at the end
wt.AssertSuccess(t, space.Free(ip("10.0.3.22")))
@@ -175,8 +175,8 @@ func TestSpaceFree(t *testing.T) {
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(4))
// Now get the biggest free space; should be 3.21
start, end = space.biggestFreeRange(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, start == ip("10.0.3.21") && address.Subtract(end, start) == 3, "Wrong space")
r = space.biggestFreeRange(entireRange)
wt.AssertTrue(t, r.Start == ip("10.0.3.21") && r.Size() == 3, "Wrong space")
// Now free a few in the middle
wt.AssertSuccess(t, space.Free(ip("10.0.3.12")))
@@ -186,8 +186,8 @@ func TestSpaceFree(t *testing.T) {
wt.AssertEquals(t, space.NumFreeAddresses(), address.Offset(7))
// Now get the biggest free space; should be 3.21
start, end = space.biggestFreeRange(ip(testAddr1), ip(testAddry))
wt.AssertTrue(t, start == ip("10.0.3.10") && address.Subtract(end, start) == 4, "Wrong space")
r = space.biggestFreeRange(entireRange)
wt.AssertTrue(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "Wrong space")
wt.AssertEquals(t, space.OwnedRanges(), []address.Range{address.Range{Start: ip("10.0.3.4"), End: ip("10.0.3.24")}})
}
@@ -210,21 +210,21 @@ func TestDonateSimple(t *testing.T) {
ps1 := makeSpace(ipAddr1, size)
// Empty space set should split in two and give me the second half
start, end, ok := ps1.Donate(ip(testAddr1), ip(testAddr1)+size)
numGivenUp := address.Subtract(end, start)
r, ok := ps1.Donate(address.MakeRange(ip(testAddr1), size))
numGivenUp := r.Size()
wt.AssertTrue(t, ok, "Donate result")
wt.AssertEqualString(t, start.String(), "10.0.1.24", "Invalid start")
wt.AssertEqualString(t, r.Start.String(), "10.0.1.24", "Invalid start")
wt.AssertEquals(t, numGivenUp, address.Offset(size/2))
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(size/2))
// Now check we can give the rest up.
count := 0 // count to avoid infinite loop
for ; count < 1000; count++ {
start, end, ok := ps1.Donate(ip(testAddr1), ip(testAddr1)+size)
r, ok := ps1.Donate(address.MakeRange(ip(testAddr1), size))
if !ok {
break
}
numGivenUp += address.Subtract(end, start)
numGivenUp += r.Size()
}
wt.AssertEquals(t, ps1.NumFreeAddresses(), address.Offset(0))
wt.AssertEquals(t, numGivenUp, address.Offset(size))
@@ -235,13 +235,12 @@ func TestDonateHard(t *testing.T) {
var (
start = ip("10.0.1.0")
size address.Offset = 48
end = address.Add(start, size)
)
// Fill a fresh space
spaceset := makeSpace(start, size)
for i := address.Offset(0); i < size; i++ {
ok, _ := spaceset.Allocate(start, address.Add(start, size))
ok, _ := spaceset.Allocate(address.MakeRange(start, size))
wt.AssertTrue(t, ok, "Failed to get IP!")
}
@@ -254,10 +253,10 @@ func TestDonateHard(t *testing.T) {
}
// Now split
newRange, end, ok := spaceset.Donate(start, end)
newRange, ok := spaceset.Donate(address.MakeRange(start, size))
wt.AssertTrue(t, ok, "GiveUpSpace result")
wt.AssertEquals(t, newRange, ip("10.0.1.23"))
wt.AssertEquals(t, address.Subtract(end, newRange), address.Offset(24))
wt.AssertEquals(t, newRange.Start, ip("10.0.1.23"))
wt.AssertEquals(t, newRange.Size(), address.Offset(24))
wt.AssertEquals(t, spaceset.NumFreeAddresses(), address.Offset(23))
//Space set should now have 2 spaces
+2 -2
View File
@@ -140,7 +140,7 @@ func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, addres
}
alloc := NewAllocator(peername, router.PeerUID(rand.Int63()),
"nick-"+name, cidr, quorum)
"nick-"+name, cidr.Range(), quorum)
return alloc, cidr
}
@@ -165,7 +165,7 @@ func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
func (alloc *Allocator) NumFreeAddresses(subnet address.CIDR) address.Offset {
resultChan := make(chan address.Offset)
alloc.actionChan <- func() {
resultChan <- alloc.space.NumFreeAddressesInRange(subnet.Start+1, subnet.End()-1)
resultChan <- alloc.space.NumFreeAddressesInRange(address.MakeRange(subnet.Start+1, subnet.Size()-2))
}
return <-resultChan
}
+1 -1
View File
@@ -218,7 +218,7 @@ func createAllocator(router *weave.Router, apiPath string, ipRangeStr string, de
log.Fatalf("Default subnet %s out of bounds: %s", defaultSubnet, ipRange)
}
}
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange, quorum)
allocator := ipam.NewAllocator(router.Ourself.Peer.Name, router.Ourself.Peer.UID, router.Ourself.Peer.NickName, ipRange.Range(), quorum)
allocator.SetInterfaces(router.NewGossip("IPallocation", allocator))
allocator.Start()
+1 -1
View File
@@ -2,7 +2,7 @@ package router
const (
Protocol = "weave"
ProtocolVersion = 19
ProtocolVersion = 20
)
type ProtocolTag byte