From e21f66db0a0d128a8190cc3d1acbb7b8133ca9d8 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 8 Jun 2015 17:46:58 +0100 Subject: [PATCH] Use address.Range instead of (start,end) pairs --- ipam/address/address.go | 9 +++++ ipam/allocate.go | 20 +++++------ ipam/allocator.go | 30 +++++++++-------- ipam/allocator_test.go | 2 +- ipam/space/space.go | 65 ++++++++++++++++++----------------- ipam/space/space_test.go | 73 ++++++++++++++++++++-------------------- ipam/testutils_test.go | 4 +-- prog/weaver/main.go | 2 +- router/protocol.go | 2 +- 9 files changed, 107 insertions(+), 100 deletions(-) diff --git a/ipam/address/address.go b/ipam/address/address.go index 67915345..377e283f 100644 --- a/ipam/address/address.go +++ b/ipam/address/address.go @@ -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) diff --git a/ipam/allocate.go b/ipam/allocate.go index 71eb8e82..59239b80 100644 --- a/ipam/allocate.go +++ b/ipam/allocate.go @@ -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 diff --git a/ipam/allocator.go b/ipam/allocator.go index 809605e0..398b05bc 100644 --- a/ipam/allocator.go +++ b/ipam/allocator.go @@ -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) } diff --git a/ipam/allocator_test.go b/ipam/allocator_test.go index ccf1043a..03b30f83 100644 --- a/ipam/allocator_test.go +++ b/ipam/allocator_test.go @@ -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) diff --git a/ipam/space/space.go b/ipam/space/space.go index b456e0df..72b8b6ec 100644 --- a/ipam/space/space.go +++ b/ipam/space/space.go @@ -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= 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 { diff --git a/ipam/space/space_test.go b/ipam/space/space_test.go index 785b17f5..8ab0f369 100644 --- a/ipam/space/space_test.go +++ b/ipam/space/space_test.go @@ -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 diff --git a/ipam/testutils_test.go b/ipam/testutils_test.go index 6d1b892e..8d7da37a 100644 --- a/ipam/testutils_test.go +++ b/ipam/testutils_test.go @@ -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 } diff --git a/prog/weaver/main.go b/prog/weaver/main.go index 9608c4b7..0ab87b66 100644 --- a/prog/weaver/main.go +++ b/prog/weaver/main.go @@ -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() diff --git a/router/protocol.go b/router/protocol.go index 60482621..1ffe5f90 100644 --- a/router/protocol.go +++ b/router/protocol.go @@ -2,7 +2,7 @@ package router const ( Protocol = "weave" - ProtocolVersion = 19 + ProtocolVersion = 20 ) type ProtocolTag byte