From 208f094726ebac7b700fe842f3b91c57af24054f Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Thu, 6 Mar 2025 15:49:24 +0100 Subject: [PATCH 1/8] start subnet command --- .github/workflows/release.yml | 4 +- .github/workflows/test.yml | 25 ++++ go.mod | 8 +- go.sum | 2 + internal/cidr/cidr_block.go | 132 +++++++++++++++++ internal/cidr/cidr_block_test.go | 196 +++++++++++++++++++++++++ internal/commands/ranges/cidr_block.go | 7 - internal/commands/ranges/command.go | 71 ++------- internal/commands/subnet/command.go | 97 ++++++++++++ internal/list/list.go | 65 ++++++++ main.go | 46 ++++-- 11 files changed, 573 insertions(+), 80 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 internal/cidr/cidr_block.go create mode 100644 internal/cidr/cidr_block_test.go delete mode 100644 internal/commands/ranges/cidr_block.go create mode 100644 internal/commands/subnet/command.go create mode 100644 internal/list/list.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65487b7..457a018 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ env: REPO_NAME: ${{ github.event.repository.name }} OUT_FOLDER: ${{ github.event.repository.name }} EXECUTABLE_NAME: ${{ github.event.repository.name }} - GO_VERSION: '1.23' + GO_VERSION: '1.24' permissions: contents: write @@ -47,7 +47,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Build ${{ env.OS_GOOS }}/${{ env.OS_GOARCH }} - run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe + run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe -ldflags "-X main.Version=${{ env.VERSION }}" shell: bash - name: Create archive diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..24ff8d2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: + pull_request: + branches: [ "main" ] + +env: + GO_VERSION: '1.24' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... \ No newline at end of file diff --git a/go.mod b/go.mod index 741671c..fc2f4b8 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,4 @@ module cider go 1.23.2 -require github.com/urfave/cli/v2 v2.27.5 - -require ( - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect -) +require github.com/urfave/cli/v3 v3.0.0-beta1 diff --git a/go.sum b/go.sum index c8b6c7e..3c92cd0 100644 --- a/go.sum +++ b/go.sum @@ -4,5 +4,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go new file mode 100644 index 0000000..d93a71c --- /dev/null +++ b/internal/cidr/cidr_block.go @@ -0,0 +1,132 @@ +package cidr + +import ( + "cider/internal/list" + "fmt" + "math" + "strconv" + "strings" +) + +const ( + INT_SIZE = 32 +) + +type CIDRBlock struct { + Network string + HostPortion int +} + +func NewBlock(network string) *CIDRBlock { + networkAndHostPortion := strings.Split(network, "/") + + networkPortion := networkAndHostPortion[0] + hostPortion := networkAndHostPortion[1] + + return &CIDRBlock{ + Network: networkPortion, + HostPortion: must(strconv.Atoi(hostPortion)), + } +} + +func padLeft(s string, paddingChar rune, totalWidth int) string { + if len(s) >= totalWidth { + return s + } + + padding := totalWidth - len(s) + + return strings.Repeat(string(paddingChar), padding) + s +} + +func (b *CIDRBlock) NetworkPortionBinary() string { + octets := strings.Split(b.Network, ".") + octets = list.Map(octets, toBin) + + return fmt.Sprintf("%s.%s.%s.%s", octets[0], octets[1], octets[2], octets[3]) +} + +func toBin(s string) string { + asInt := must(strconv.ParseInt(s, 10, INT_SIZE)) + asBinaryString := strconv.FormatInt(asInt, 2) + paddedBynaryString := padLeft(asBinaryString, '0', 8) + return paddedBynaryString +} + +func (b *CIDRBlock) SubnetMask() string { + ones := strings.Repeat("1", b.HostPortion) + zeroes := strings.Repeat("0", INT_SIZE-b.HostPortion) + + mask := ones + zeroes + + base := 2 + octet1 := must(strconv.ParseInt(mask[0:8], base, INT_SIZE)) + octet2 := must(strconv.ParseInt(mask[8:16], base, INT_SIZE)) + octet3 := must(strconv.ParseInt(mask[16:24], base, INT_SIZE)) + octet4 := must(strconv.ParseInt(mask[24:32], base, INT_SIZE)) + + return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) +} + +func (b *CIDRBlock) AvailableHosts() uint { + numAddresses := math.Pow(2, float64(INT_SIZE)-float64(b.HostPortion)) + + return uint(numAddresses) +} + +func (b *CIDRBlock) StartAddressOfNextBlock() string { + octets := strings.Split(b.BroadcastAddress(), ".") + octets = list.Map(octets, toBin) + binStr := strings.Join(octets, "") + + next := must(strconv.ParseInt(binStr, 2, INT_SIZE)) + 1 + + asBinaryString := strconv.FormatInt(next, 2) + asBinaryString = padLeft(asBinaryString, '0', 32) + + base := 2 + octet1 := must(strconv.ParseInt(asBinaryString[0:8], base, INT_SIZE)) + octet2 := must(strconv.ParseInt(asBinaryString[8:16], base, INT_SIZE)) + octet3 := must(strconv.ParseInt(asBinaryString[16:24], base, INT_SIZE)) + octet4 := must(strconv.ParseInt(asBinaryString[24:32], base, INT_SIZE)) + + return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) +} + +func (b *CIDRBlock) NetworkAddress() string { + ipBin := strings.ReplaceAll(b.NetworkPortionBinary(), ".", "")[0:b.HostPortion] + + broadcast := ipBin + strings.Repeat("0", INT_SIZE-b.HostPortion) + + base := 2 + octet1 := must(strconv.ParseInt(broadcast[0:8], base, INT_SIZE)) + octet2 := must(strconv.ParseInt(broadcast[8:16], base, INT_SIZE)) + octet3 := must(strconv.ParseInt(broadcast[16:24], base, INT_SIZE)) + octet4 := must(strconv.ParseInt(broadcast[24:32], base, INT_SIZE)) + + return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) +} + +func (b *CIDRBlock) BroadcastAddress() string { + // https://stackoverflow.com/questions/1470792/how-to-calculate-the-ip-range-when-the-ip-address-and-the-netmask-is-given + + ipBin := strings.ReplaceAll(b.NetworkPortionBinary(), ".", "")[0:b.HostPortion] + + broadcast := ipBin + strings.Repeat("1", INT_SIZE-b.HostPortion) + + base := 2 + octet1 := must(strconv.ParseInt(broadcast[0:8], base, INT_SIZE)) + octet2 := must(strconv.ParseInt(broadcast[8:16], base, INT_SIZE)) + octet3 := must(strconv.ParseInt(broadcast[16:24], base, INT_SIZE)) + octet4 := must(strconv.ParseInt(broadcast[24:32], base, INT_SIZE)) + + return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) +} + +func must[T any](x T, e error) T { + if e != nil { + panic(e) + } + + return x +} diff --git a/internal/cidr/cidr_block_test.go b/internal/cidr/cidr_block_test.go new file mode 100644 index 0000000..12fe5aa --- /dev/null +++ b/internal/cidr/cidr_block_test.go @@ -0,0 +1,196 @@ +package cidr_test + +import ( + "cider/internal/cidr" + "testing" +) + +func Test_AvailableHosts(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected uint + }{ + "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: 4294967296}, + "/1": {input: cidr.NewBlock("10.0.0.0/1"), expected: 2147483648}, + "/2": {input: cidr.NewBlock("10.0.0.0/2"), expected: 1073741824}, + "/3": {input: cidr.NewBlock("10.0.0.0/3"), expected: 536870912}, + "/4": {input: cidr.NewBlock("10.0.0.0/4"), expected: 268435456}, + "/5": {input: cidr.NewBlock("10.0.0.0/5"), expected: 134217728}, + "/6": {input: cidr.NewBlock("10.0.0.0/6"), expected: 67108864}, + "/7": {input: cidr.NewBlock("10.0.0.0/7"), expected: 33554432}, + "/8": {input: cidr.NewBlock("10.0.0.0/8"), expected: 16777216}, + "/9": {input: cidr.NewBlock("10.0.0.0/9"), expected: 8388608}, + "/10": {input: cidr.NewBlock("10.0.0.0/10"), expected: 4194304}, + "/11": {input: cidr.NewBlock("10.0.0.0/11"), expected: 2097152}, + "/12": {input: cidr.NewBlock("10.0.0.0/12"), expected: 1048576}, + "/13": {input: cidr.NewBlock("10.0.0.0/13"), expected: 524288}, + "/14": {input: cidr.NewBlock("10.0.0.0/14"), expected: 262144}, + "/15": {input: cidr.NewBlock("10.0.0.0/15"), expected: 131072}, + "/16": {input: cidr.NewBlock("10.0.0.0/16"), expected: 65536}, + "/17": {input: cidr.NewBlock("10.0.0.0/17"), expected: 32768}, + "/18": {input: cidr.NewBlock("10.0.0.0/18"), expected: 16384}, + "/19": {input: cidr.NewBlock("10.0.0.0/19"), expected: 8192}, + "/20": {input: cidr.NewBlock("10.0.0.0/20"), expected: 4096}, + "/21": {input: cidr.NewBlock("10.0.0.0/21"), expected: 2048}, + "/22": {input: cidr.NewBlock("10.0.0.0/22"), expected: 1024}, + "/23": {input: cidr.NewBlock("10.0.0.0/23"), expected: 512}, + "/24": {input: cidr.NewBlock("10.0.0.0/24"), expected: 256}, + "/25": {input: cidr.NewBlock("10.0.0.0/25"), expected: 128}, + "/26": {input: cidr.NewBlock("10.0.0.0/26"), expected: 64}, + "/27": {input: cidr.NewBlock("10.0.0.0/27"), expected: 32}, + "/28": {input: cidr.NewBlock("10.0.0.0/28"), expected: 16}, + "/29": {input: cidr.NewBlock("10.0.0.0/29"), expected: 8}, + "/30": {input: cidr.NewBlock("10.0.0.0/30"), expected: 4}, + "/31": {input: cidr.NewBlock("10.0.0.0/31"), expected: 2}, + "/32": {input: cidr.NewBlock("10.0.0.0/32"), expected: 1}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.AvailableHosts() + + if actual != test.expected { + t.Fatalf("%s returns correct number of addresses: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_SubnetMask(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: "0.0.0.0"}, + "/1": {input: cidr.NewBlock("10.0.0.0/1"), expected: "128.0.0.0"}, + "/2": {input: cidr.NewBlock("10.0.0.0/2"), expected: "192.0.0.0"}, + "/3": {input: cidr.NewBlock("10.0.0.0/3"), expected: "224.0.0.0"}, + "/4": {input: cidr.NewBlock("10.0.0.0/4"), expected: "240.0.0.0"}, + "/5": {input: cidr.NewBlock("10.0.0.0/5"), expected: "248.0.0.0"}, + "/6": {input: cidr.NewBlock("10.0.0.0/6"), expected: "252.0.0.0"}, + "/7": {input: cidr.NewBlock("10.0.0.0/7"), expected: "254.0.0.0"}, + "/8": {input: cidr.NewBlock("10.0.0.0/8"), expected: "255.0.0.0"}, + "/9": {input: cidr.NewBlock("10.0.0.0/9"), expected: "255.128.0.0"}, + "/10": {input: cidr.NewBlock("10.0.0.0/10"), expected: "255.192.0.0"}, + "/11": {input: cidr.NewBlock("10.0.0.0/11"), expected: "255.224.0.0"}, + "/12": {input: cidr.NewBlock("10.0.0.0/12"), expected: "255.240.0.0"}, + "/13": {input: cidr.NewBlock("10.0.0.0/13"), expected: "255.248.0.0"}, + "/14": {input: cidr.NewBlock("10.0.0.0/14"), expected: "255.252.0.0"}, + "/15": {input: cidr.NewBlock("10.0.0.0/15"), expected: "255.254.0.0"}, + "/16": {input: cidr.NewBlock("10.0.0.0/16"), expected: "255.255.0.0"}, + "/17": {input: cidr.NewBlock("10.0.0.0/17"), expected: "255.255.128.0"}, + "/18": {input: cidr.NewBlock("10.0.0.0/18"), expected: "255.255.192.0"}, + "/19": {input: cidr.NewBlock("10.0.0.0/19"), expected: "255.255.224.0"}, + "/20": {input: cidr.NewBlock("10.0.0.0/20"), expected: "255.255.240.0"}, + "/21": {input: cidr.NewBlock("10.0.0.0/21"), expected: "255.255.248.0"}, + "/22": {input: cidr.NewBlock("10.0.0.0/22"), expected: "255.255.252.0"}, + "/23": {input: cidr.NewBlock("10.0.0.0/23"), expected: "255.255.254.0"}, + "/24": {input: cidr.NewBlock("10.0.0.0/24"), expected: "255.255.255.0"}, + "/25": {input: cidr.NewBlock("10.0.0.0/25"), expected: "255.255.255.128"}, + "/26": {input: cidr.NewBlock("10.0.0.0/26"), expected: "255.255.255.192"}, + "/27": {input: cidr.NewBlock("10.0.0.0/27"), expected: "255.255.255.224"}, + "/28": {input: cidr.NewBlock("10.0.0.0/28"), expected: "255.255.255.240"}, + "/29": {input: cidr.NewBlock("10.0.0.0/29"), expected: "255.255.255.248"}, + "/30": {input: cidr.NewBlock("10.0.0.0/30"), expected: "255.255.255.252"}, + "/31": {input: cidr.NewBlock("10.0.0.0/31"), expected: "255.255.255.254"}, + "/32": {input: cidr.NewBlock("10.0.0.0/32"), expected: "255.255.255.255"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.SubnetMask() + + if actual != test.expected { + t.Fatalf("%s returns correct subnet mask: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_NetworkPortionBinary(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: "00001010.00000000.00000000.00000000"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.NetworkPortionBinary() + + if actual != test.expected { + t.Fatalf("%s returns correct binary representation: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_NetworkAddress(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.64"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.NetworkAddress() + + if actual != test.expected { + t.Fatalf("%s returns correct network address: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_BroadcastAddress(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.127"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.BroadcastAddress() + + if actual != test.expected { + t.Fatalf("%s returns correct broadcast address: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_StartAddressOfNextBlock(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/17": {input: cidr.NewBlock("10.0.0.0/17"), expected: "10.0.128.0"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.StartAddressOfNextBlock() + + if actual != test.expected { + t.Fatalf("%s returns next block address: got %v expected %v", name, actual, test.expected) + } + }) + } +} diff --git a/internal/commands/ranges/cidr_block.go b/internal/commands/ranges/cidr_block.go deleted file mode 100644 index f4af900..0000000 --- a/internal/commands/ranges/cidr_block.go +++ /dev/null @@ -1,7 +0,0 @@ -package ranges - -type CIDRBlock struct { - NetworkPortion uint // 0 - SubnetMask string // 0.0.0.0 - AvailableHosts uint // 4294967296 -} diff --git a/internal/commands/ranges/command.go b/internal/commands/ranges/command.go index 66d6b95..aaddaf2 100644 --- a/internal/commands/ranges/command.go +++ b/internal/commands/ranges/command.go @@ -1,11 +1,10 @@ package ranges import ( + "cider/internal/cidr" "fmt" - "math" "os" "strconv" - "strings" "text/tabwriter" ) @@ -28,27 +27,27 @@ func (*handler) Handle(arg string) error { } // argument was given - try to parse it - cidr, err := strconv.ParseInt(arg, 10, INT_SIZE) + hostPortion, err := strconv.ParseInt(arg, 10, INT_SIZE) if err != nil { return fmt.Errorf("%s is not a valid integer", arg) } - if cidr < 0 || cidr > INT_SIZE { - return fmt.Errorf("%v is not a valid size - must be between 0 and 32", cidr) + if hostPortion < 0 || hostPortion > INT_SIZE { + return fmt.Errorf("%v is not a valid size - must be between 0 and 32", hostPortion) } - block := calculateCIDRBlock(int(cidr)) + block := calculateCIDRBlock(int(hostPortion)) - table := []*CIDRBlock{block} + table := []*cidr.CIDRBlock{block} return printCIDRBlocks(table) } -func calculateAllCIDRBlocks() []*CIDRBlock { - blocks := []*CIDRBlock{} - for cidr := 0; cidr < INT_SIZE+1; cidr++ { - block := calculateCIDRBlock(cidr) +func calculateAllCIDRBlocks() []*cidr.CIDRBlock { + blocks := []*cidr.CIDRBlock{} + for i := 0; i < INT_SIZE+1; i++ { + block := calculateCIDRBlock(i) blocks = append(blocks, block) } @@ -56,41 +55,11 @@ func calculateAllCIDRBlocks() []*CIDRBlock { return blocks } -func calculateCIDRBlock(cidr int) *CIDRBlock { - numAddresses := calculateNumAddresses(INT_SIZE, cidr) - mask := calculateSubnetMask(cidr) - - block := &CIDRBlock{ - NetworkPortion: uint(cidr), - SubnetMask: mask, - AvailableHosts: numAddresses, - } - - return block -} - -func calculateSubnetMask(cidr int) string { - ones := strings.Repeat("1", cidr) - zeroes := strings.Repeat("0", INT_SIZE-cidr) - - mask := ones + zeroes - - base := 2 - octet1 := must(strconv.ParseInt(mask[0:8], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(mask[8:16], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(mask[16:24], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(mask[24:32], base, INT_SIZE)) - - return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) +func calculateCIDRBlock(hostPortion int) *cidr.CIDRBlock { + return cidr.NewBlock(fmt.Sprintf("10.0.0.0/%v", hostPortion)) } -func calculateNumAddresses(addressLength, prefixLength int) uint { - numAddresses := math.Pow(2, float64(addressLength)-float64(prefixLength)) - - return uint(numAddresses) -} - -func printCIDRBlocks(blocks []*CIDRBlock) error { +func printCIDRBlocks(blocks []*cidr.CIDRBlock) error { w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, ' ', 0) fmt.Fprint(w, "CIDR\tSubnet Mask\tAddresses\tAzure Addresses\n") @@ -100,20 +69,12 @@ func printCIDRBlocks(blocks []*CIDRBlock) error { // Azure reserves the first four addresses and the last address, for a total of five IP addresses within each subnet // https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets - if block.AvailableHosts >= 5 { - availableAzureAddresses = fmt.Sprintf("%v", block.AvailableHosts-5) + if block.AvailableHosts() >= 5 { + availableAzureAddresses = fmt.Sprintf("%v", block.AvailableHosts()-5) } - fmt.Fprintf(w, "/%v\t%s\t%v\t%s\n", block.NetworkPortion, block.SubnetMask, block.AvailableHosts, availableAzureAddresses) + fmt.Fprintf(w, "/%v\t%s\t%v\t%s\n", block.HostPortion, block.SubnetMask(), block.AvailableHosts(), availableAzureAddresses) } return w.Flush() } - -func must[T any](x T, e error) T { - if e != nil { - panic(e) - } - - return x -} diff --git a/internal/commands/subnet/command.go b/internal/commands/subnet/command.go new file mode 100644 index 0000000..3c27379 --- /dev/null +++ b/internal/commands/subnet/command.go @@ -0,0 +1,97 @@ +package subnet + +import ( + "fmt" +) + +type handler struct{} + +func New() *handler { + return &handler{} +} + +func (*handler) Handle(args []string) error { + rangeToSplit := args[0] + sizes := args[1:] + + fmt.Println(rangeToSplit, sizes) + + return nil +} + +/* + + public IEnumerable Subnet(string range, IEnumerable sizes) + { + var vnet = ToCidrBlock(range); + + // the smallest vnet is a /32 + if (vnet.HostPortion > 32) + { + throw new InvalidRangeException("Smallest vnet size is 32"); + } + + // the smallest subnet is a /32 + if (sizes.Any(x => x > 32)) + { + throw new InvalidRangeException("Smallest subnet size is 32"); + } + + var vnetLastIp = vnet.StartAndEndAddress().Item2; + + var result = new List(); + + sizes = sizes.Order(); + + var nextIp = vnet.NetworkPortion; + foreach (var subnetSize in sizes) + { + var cidrBlock = new CidrBlock { + NetworkPortion = nextIp, + HostPortion = subnetSize, + }; + + var c = cidrBlock.StartAddressOfNextBlock(); + + var octets = c.Split("."); + var octetsBytes = octets.Select(ToByte); + nextIp = octetsBytes; + + result.Add($"{cidrBlock.ToIp()}/{subnetSize}"); + } + + return result; + } + + private static CidrBlock ToCidrBlock(string ip) + { + var networkAndHostPortion = ip.Split("/"); + + var networkPortion = networkAndHostPortion[0]; // 10.0.0.0 + var octets = networkPortion.Split("."); + var octetsBytes = octets.Select(ToByte); + + var hostPortion = Convert.ToInt32(networkAndHostPortion[1]); // / 16 + + return new CidrBlock + { + NetworkPortion = octetsBytes, + HostPortion = hostPortion, + }; + } + + public static byte ToByte(string n) + { + var padded = n.PadLeft(8, '0'); + + return byte.Parse(padded); + } + + public static string ToBinary(byte b) + { + return Convert.ToString(b, 2).PadLeft(8, '0'); + } +} + + +*/ diff --git a/internal/list/list.go b/internal/list/list.go new file mode 100644 index 0000000..fe7bd74 --- /dev/null +++ b/internal/list/list.go @@ -0,0 +1,65 @@ +package list + +func Filter[T any](v []T, f func(T) bool) []T { + var res []T + for _, i := range v { + if !f(i) { + continue + } + + res = append(res, i) + } + + return res +} + +func Map[T, K any](v []T, f func(T) K) []K { + var res []K + for _, i := range v { + res = append(res, f(i)) + } + return res +} + +func FlatMap[T, K any](v []T, f func(T) []K) []K { + var res []K + for _, i := range v { + res = append(res, f(i)...) + } + return res +} + +func Contains[T any](v []T, f func(T) bool) bool { + return len(Filter(v, f)) > 0 +} + +func All[T any](v []T, f func(T) bool) bool { + return len(Filter(v, f)) == len(v) +} + +func First[T any](v []T, f func(T) bool) T { + for _, i := range v { + if f(i) { + return i + } + } + + panic("no element satisifies predicate") +} + +func FirstOrDefault[T any](v []T, def T, f func(T) bool) T { + for _, i := range v { + if f(i) { + return i + } + } + + return def +} + +func Fold[T, K any](v []T, acc K, f func(T, K) K) K { + for _, i := range v { + acc = f(i, acc) + } + return acc +} diff --git a/main.go b/main.go index 907fa5f..c5dc02a 100644 --- a/main.go +++ b/main.go @@ -3,24 +3,29 @@ package main import ( "cider/internal/commands/in" "cider/internal/commands/ranges" + "cider/internal/commands/subnet" + "context" + "fmt" "log" "os" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) +var version string + func main() { - app := &cli.App{ + cmd := &cli.Command{ Name: "cider", Usage: "CIDR cli tool", Commands: []*cli.Command{ { Name: "ranges", - Usage: "Displays all CIDR ranges", + Usage: "display all CIDR ranges", Aliases: []string{"r"}, - Action: func(c *cli.Context) error { - arg := c.Args().First() + Action: func(_ context.Context, command *cli.Command) error { + arg := command.Args().First() handler := ranges.New() @@ -30,10 +35,10 @@ func main() { }, { Name: "in", - Usage: "Determines if an ip falls within a range", + Usage: "determine if an ip falls within a range", Aliases: []string{"i"}, - Action: func(c *cli.Context) error { - args := c.Args().Slice() + Action: func(_ context.Context, command *cli.Command) error { + args := command.Args().Slice() handler := in.New() @@ -41,6 +46,29 @@ func main() { }, UsageText: "in [ip] [range1] [optional range2] [optional rangeN]", }, + { + Name: "subnet", + Usage: "split a range into multiple smaller ranges", + Aliases: []string{"s"}, + Action: func(_ context.Context, command *cli.Command) error { + args := command.Args().Slice() + + handler := subnet.New() + + return handler.Handle(args) + }, + UsageText: "subnet [range] [size1] [optional size2] [optional sizeN]", + }, + { + Name: "version", + Aliases: []string{"v"}, + Description: "show version", + Action: func(_ context.Context, command *cli.Command) error { + fmt.Println(version) + + return nil + }, + }, }, } @@ -55,7 +83,7 @@ func main() { } } - if err := app.Run(args); err != nil { + if err := cmd.Run(context.Background(), args); err != nil { log.Fatal(err) } } From 928b125c8bf5b421202e02f4ac757b77045881d8 Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Fri, 7 Mar 2025 13:02:22 +0100 Subject: [PATCH 2/8] add info command --- internal/cidr/cidr_block.go | 41 +++++++++++++++---- internal/cidr/cidr_block_test.go | 46 ++++++++++++++++++++++ internal/commands/in/command.go | 61 ++++++----------------------- internal/commands/info/command.go | 31 +++++++++++++++ internal/commands/ranges/command.go | 2 +- internal/utils/strings.go | 23 +++++++++++ main.go | 14 +++++++ 7 files changed, 162 insertions(+), 56 deletions(-) create mode 100644 internal/commands/info/command.go create mode 100644 internal/utils/strings.go diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go index d93a71c..8daa4dc 100644 --- a/internal/cidr/cidr_block.go +++ b/internal/cidr/cidr_block.go @@ -2,8 +2,10 @@ package cidr import ( "cider/internal/list" + "cider/internal/utils" "fmt" "math" + "slices" "strconv" "strings" ) @@ -29,14 +31,39 @@ func NewBlock(network string) *CIDRBlock { } } -func padLeft(s string, paddingChar rune, totalWidth int) string { - if len(s) >= totalWidth { - return s +func (b *CIDRBlock) Subnet(sizes []int) []string { + //lastAddress := b.BroadcastAddress() + + slices.Sort(sizes) + + subnets := []string{} + for _, size := range sizes { + subnets = append(subnets, fmt.Sprintf("%s/%v", b.Network, size)) } - padding := totalWidth - len(s) + return subnets +} + +// https://stackoverflow.com/questions/9622967/how-to-see-if-an-ip-address-belongs-inside-of-a-range-of-ips-using-cidr-notation +func (b *CIDRBlock) Contains(ip string) bool { + IP_addr := ipToDecimal(ip) + CIDR_addr := ipToDecimal(b.Network) + CIDR_mask := -1 << (INT_SIZE - b.HostPortion) + + return (IP_addr & CIDR_mask) == (CIDR_addr & CIDR_mask) +} + +// http://www.aboutmyip.com/AboutMyXApp/IP2Integer.jsp +func ipToDecimal(ip string) int { + parts := strings.Split(ip, ".") + + base := 10 + octet1 := must(strconv.ParseInt(parts[0], base, INT_SIZE)) + octet2 := must(strconv.ParseInt(parts[1], base, INT_SIZE)) + octet3 := must(strconv.ParseInt(parts[2], base, INT_SIZE)) + octet4 := must(strconv.ParseInt(parts[3], base, INT_SIZE)) - return strings.Repeat(string(paddingChar), padding) + s + return int((octet1 * 16777216) + (octet2 * 65536) + (octet3 * 256) + octet4) } func (b *CIDRBlock) NetworkPortionBinary() string { @@ -49,7 +76,7 @@ func (b *CIDRBlock) NetworkPortionBinary() string { func toBin(s string) string { asInt := must(strconv.ParseInt(s, 10, INT_SIZE)) asBinaryString := strconv.FormatInt(asInt, 2) - paddedBynaryString := padLeft(asBinaryString, '0', 8) + paddedBynaryString := utils.PadLeft(asBinaryString, '0', 8) return paddedBynaryString } @@ -82,7 +109,7 @@ func (b *CIDRBlock) StartAddressOfNextBlock() string { next := must(strconv.ParseInt(binStr, 2, INT_SIZE)) + 1 asBinaryString := strconv.FormatInt(next, 2) - asBinaryString = padLeft(asBinaryString, '0', 32) + asBinaryString = utils.PadLeft(asBinaryString, '0', 32) base := 2 octet1 := must(strconv.ParseInt(asBinaryString[0:8], base, INT_SIZE)) diff --git a/internal/cidr/cidr_block_test.go b/internal/cidr/cidr_block_test.go index 12fe5aa..140bffd 100644 --- a/internal/cidr/cidr_block_test.go +++ b/internal/cidr/cidr_block_test.go @@ -2,6 +2,7 @@ package cidr_test import ( "cider/internal/cidr" + "reflect" "testing" ) @@ -194,3 +195,48 @@ func Test_StartAddressOfNextBlock(t *testing.T) { }) } } + +func Test_Contains(t *testing.T) { + tests := map[string]struct { + ip string + ipRange *cidr.CIDRBlock + expected bool + }{ + "ip inside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/8"), expected: true}, + "ip outside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/28"), expected: false}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.ipRange.Contains(test.ip) + + if actual != test.expected { + t.Fatalf("%s: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_Subnet(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + sizes []int + expected []string + }{ + "/17": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{17, 17}, expected: []string{"10.0.0.0/17", "10.0.128.0/17"}}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.Subnet(test.sizes) + + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("%s returns correct subnet: got %v expected %v", name, actual, test.expected) + } + }) + } +} diff --git a/internal/commands/in/command.go b/internal/commands/in/command.go index aac8f9f..357d9d5 100644 --- a/internal/commands/in/command.go +++ b/internal/commands/in/command.go @@ -1,13 +1,9 @@ package in import ( + "cider/internal/cidr" + "cider/internal/list" "fmt" - "strconv" - "strings" -) - -const ( - INT_SIZE = 32 ) type handler struct{} @@ -21,54 +17,23 @@ func (h *handler) Handle(args []string) error { return fmt.Errorf("command expects at least 2 arguments") } - found := false ip := args[0] - for _, cidr := range args[1:] { - inRange := isInRange(ip, cidr) + ranges := list.Map(args[1:], func(i string) *cidr.CIDRBlock { + return cidr.NewBlock(i) + }) - found = found || inRange + inRange := list.Filter(ranges, func(cidr *cidr.CIDRBlock) bool { + return cidr.Contains(ip) + }) - if inRange { - fmt.Println(cidr) - } - } - - if !found { + if len(inRange) == 0 { fmt.Printf("%s is not in any of the provided ranges\n", ip) + return nil } - return nil -} - -// https://stackoverflow.com/questions/9622967/how-to-see-if-an-ip-address-belongs-inside-of-a-range-of-ips-using-cidr-notation -func isInRange(ipAddress, CIDRmask string) bool { - - parts := strings.Split(CIDRmask, "/") - - IP_addr := ipToDecimal(ipAddress) - CIDR_addr := ipToDecimal(parts[0]) - CIDR_mask := -1 << (INT_SIZE - must(strconv.ParseInt(parts[1], 10, INT_SIZE))) - - return (IP_addr & CIDR_mask) == (CIDR_addr & CIDR_mask) -} - -// http://www.aboutmyip.com/AboutMyXApp/IP2Integer.jsp -func ipToDecimal(ip string) int { - parts := strings.Split(ip, ".") - - base := 10 - octet1 := must(strconv.ParseInt(parts[0], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(parts[1], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(parts[2], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(parts[3], base, INT_SIZE)) - - return int((octet1 * 16777216) + (octet2 * 65536) + (octet3 * 256) + octet4) -} - -func must[T any](x T, e error) T { - if e != nil { - panic(e) + for _, ip := range inRange { + fmt.Println(ip) } - return x + return nil } diff --git a/internal/commands/info/command.go b/internal/commands/info/command.go new file mode 100644 index 0000000..c177f08 --- /dev/null +++ b/internal/commands/info/command.go @@ -0,0 +1,31 @@ +package info + +import ( + "cider/internal/cidr" + "fmt" + "os" + "text/tabwriter" +) + +type handler struct{} + +func New() *handler { + return &handler{} +} + +func (h *handler) Handle(args []string) error { + if len(args) != 1 { + return fmt.Errorf("command expects exactly one argument") + } + + ip := args[0] + + block := cidr.NewBlock(ip) + + w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, ' ', 0) + + fmt.Fprint(w, "Address range\tStart of next block\tCidr\tSubnet mask\tAddresses\n") + fmt.Fprintf(w, "%v - %v\t%s\t/%v\t%v\t%v\n", block.NetworkAddress(), block.BroadcastAddress(), block.StartAddressOfNextBlock(), block.HostPortion, block.SubnetMask(), block.AvailableHosts()) + + return w.Flush() +} diff --git a/internal/commands/ranges/command.go b/internal/commands/ranges/command.go index aaddaf2..335b067 100644 --- a/internal/commands/ranges/command.go +++ b/internal/commands/ranges/command.go @@ -62,7 +62,7 @@ func calculateCIDRBlock(hostPortion int) *cidr.CIDRBlock { func printCIDRBlocks(blocks []*cidr.CIDRBlock) error { w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, ' ', 0) - fmt.Fprint(w, "CIDR\tSubnet Mask\tAddresses\tAzure Addresses\n") + fmt.Fprint(w, "Cidr\tSubnet mask\tAddresses\tAzure addresses\n") for _, block := range blocks { availableAzureAddresses := "N/A" diff --git a/internal/utils/strings.go b/internal/utils/strings.go new file mode 100644 index 0000000..bae2766 --- /dev/null +++ b/internal/utils/strings.go @@ -0,0 +1,23 @@ +package utils + +import "strings" + +func PadLeft(s string, paddingChar rune, totalWidth int) string { + if len(s) >= totalWidth { + return s + } + + padding := totalWidth - len(s) + + return strings.Repeat(string(paddingChar), padding) + s +} + +func PadRight(s string, paddingChar rune, totalWidth int) string { + if len(s) >= totalWidth { + return s + } + + padding := totalWidth - len(s) + + return s + strings.Repeat(string(paddingChar), padding) +} diff --git a/main.go b/main.go index c5dc02a..ea0bf1e 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "cider/internal/commands/in" + "cider/internal/commands/info" "cider/internal/commands/ranges" "cider/internal/commands/subnet" "context" @@ -59,6 +60,19 @@ func main() { }, UsageText: "subnet [range] [size1] [optional size2] [optional sizeN]", }, + { + Name: "info", + Usage: "display information about an ip or range", + Aliases: []string{"f"}, + Action: func(_ context.Context, command *cli.Command) error { + args := command.Args().Slice() + + handler := info.New() + + return handler.Handle(args) + }, + UsageText: "info [range or ip]", + }, { Name: "version", Aliases: []string{"v"}, From 407ffbe3d3ea7a7e72a8dba88696bcbc21f2ff47 Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Fri, 7 Mar 2025 14:31:12 +0100 Subject: [PATCH 3/8] implement subnet --- internal/cidr/cidr_block.go | 18 ++++-- internal/cidr/cidr_block_test.go | 5 +- internal/commands/subnet/command.go | 92 +++++------------------------ main.go | 4 +- 4 files changed, 35 insertions(+), 84 deletions(-) diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go index 8daa4dc..5ec92a7 100644 --- a/internal/cidr/cidr_block.go +++ b/internal/cidr/cidr_block.go @@ -3,6 +3,7 @@ package cidr import ( "cider/internal/list" "cider/internal/utils" + "errors" "fmt" "math" "slices" @@ -31,17 +32,26 @@ func NewBlock(network string) *CIDRBlock { } } -func (b *CIDRBlock) Subnet(sizes []int) []string { - //lastAddress := b.BroadcastAddress() +func (b *CIDRBlock) Subnet(sizes []int) ([]string, error) { + // sort subnets largest to smallest to prevent fragmentation slices.Sort(sizes) + next := b subnets := []string{} for _, size := range sizes { - subnets = append(subnets, fmt.Sprintf("%s/%v", b.Network, size)) + subnetBlock := NewBlock(fmt.Sprintf("%s/%v", next.Network, size)) + + if !b.Contains(subnetBlock.Network) { + return nil, errors.New("error occured during subnetting") + } + + subnets = append(subnets, fmt.Sprintf("%s/%v", subnetBlock.Network, subnetBlock.HostPortion)) + + next = NewBlock(fmt.Sprintf("%s/%v", subnetBlock.StartAddressOfNextBlock(), size)) } - return subnets + return subnets, nil } // https://stackoverflow.com/questions/9622967/how-to-see-if-an-ip-address-belongs-inside-of-a-range-of-ips-using-cidr-notation diff --git a/internal/cidr/cidr_block_test.go b/internal/cidr/cidr_block_test.go index 140bffd..47240e1 100644 --- a/internal/cidr/cidr_block_test.go +++ b/internal/cidr/cidr_block_test.go @@ -225,14 +225,15 @@ func Test_Subnet(t *testing.T) { sizes []int expected []string }{ - "/17": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{17, 17}, expected: []string{"10.0.0.0/17", "10.0.128.0/17"}}, + "two even subnets": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{17, 17}, expected: []string{"10.0.0.0/17", "10.0.128.0/17"}}, + "three subnets": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{18, 17, 18}, expected: []string{"10.0.0.0/17", "10.0.128.0/18", "10.0.192.0/18"}}, } for name, test := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - actual := test.input.Subnet(test.sizes) + actual, _ := test.input.Subnet(test.sizes) if !reflect.DeepEqual(actual, test.expected) { t.Fatalf("%s returns correct subnet: got %v expected %v", name, actual, test.expected) diff --git a/internal/commands/subnet/command.go b/internal/commands/subnet/command.go index 3c27379..4f3b126 100644 --- a/internal/commands/subnet/command.go +++ b/internal/commands/subnet/command.go @@ -1,7 +1,10 @@ package subnet import ( + "cider/internal/cidr" + "cider/internal/list" "fmt" + "strconv" ) type handler struct{} @@ -12,86 +15,23 @@ func New() *handler { func (*handler) Handle(args []string) error { rangeToSplit := args[0] - sizes := args[1:] + sizes := list.Map(args[1:], func(s string) int { + n, _ := strconv.Atoi(s) - fmt.Println(rangeToSplit, sizes) + return n + }) - return nil -} - -/* - - public IEnumerable Subnet(string range, IEnumerable sizes) - { - var vnet = ToCidrBlock(range); - - // the smallest vnet is a /32 - if (vnet.HostPortion > 32) - { - throw new InvalidRangeException("Smallest vnet size is 32"); - } - - // the smallest subnet is a /32 - if (sizes.Any(x => x > 32)) - { - throw new InvalidRangeException("Smallest subnet size is 32"); - } - - var vnetLastIp = vnet.StartAndEndAddress().Item2; - - var result = new List(); - - sizes = sizes.Order(); - - var nextIp = vnet.NetworkPortion; - foreach (var subnetSize in sizes) - { - var cidrBlock = new CidrBlock { - NetworkPortion = nextIp, - HostPortion = subnetSize, - }; + block := cidr.NewBlock(rangeToSplit) - var c = cidrBlock.StartAddressOfNextBlock(); + subnets, err := block.Subnet(sizes) - var octets = c.Split("."); - var octetsBytes = octets.Select(ToByte); - nextIp = octetsBytes; + if err != nil { + return err + } - result.Add($"{cidrBlock.ToIp()}/{subnetSize}"); - } + for _, subnet := range subnets { + fmt.Println(subnet) + } - return result; - } - - private static CidrBlock ToCidrBlock(string ip) - { - var networkAndHostPortion = ip.Split("/"); - - var networkPortion = networkAndHostPortion[0]; // 10.0.0.0 - var octets = networkPortion.Split("."); - var octetsBytes = octets.Select(ToByte); - - var hostPortion = Convert.ToInt32(networkAndHostPortion[1]); // / 16 - - return new CidrBlock - { - NetworkPortion = octetsBytes, - HostPortion = hostPortion, - }; - } - - public static byte ToByte(string n) - { - var padded = n.PadLeft(8, '0'); - - return byte.Parse(padded); - } - - public static string ToBinary(byte b) - { - return Convert.ToString(b, 2).PadLeft(8, '0'); - } + return nil } - - -*/ diff --git a/main.go b/main.go index ea0bf1e..c117122 100644 --- a/main.go +++ b/main.go @@ -62,7 +62,7 @@ func main() { }, { Name: "info", - Usage: "display information about an ip or range", + Usage: "display information about a range", Aliases: []string{"f"}, Action: func(_ context.Context, command *cli.Command) error { args := command.Args().Slice() @@ -71,7 +71,7 @@ func main() { return handler.Handle(args) }, - UsageText: "info [range or ip]", + UsageText: "info [range]", }, { Name: "version", From 4b26d38cb03fe3cdf97e97068e519d655053c776 Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Fri, 7 Mar 2025 14:35:47 +0100 Subject: [PATCH 4/8] update pipeline --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 457a018..38bfa54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: go-version: ${{ env.GO_VERSION }} - name: Build ${{ env.OS_GOOS }}/${{ env.OS_GOARCH }} - run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe -ldflags "-X main.Version=${{ env.VERSION }}" + run: GOOS=${{ env.OS_GOOS }} GOARCH=${{ env.OS_GOARCH }} go build -o ${{ env.EXECUTABLE_NAME }}.exe -ldflags "-X main.version=${{ env.VERSION }}" shell: bash - name: Create archive From 3c6b04746090eec755165ed9a96e9242ae28d266 Mon Sep 17 00:00:00 2001 From: fremartini Date: Sat, 8 Mar 2025 22:20:40 +0100 Subject: [PATCH 5/8] adjust structure --- internal/cidr/cidr_block.go | 4 +- internal/cidr/cidr_block_test.go | 222 +++++++++++++++++-------------- 2 files changed, 123 insertions(+), 103 deletions(-) diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go index 5ec92a7..c1a99a8 100644 --- a/internal/cidr/cidr_block.go +++ b/internal/cidr/cidr_block.go @@ -3,7 +3,6 @@ package cidr import ( "cider/internal/list" "cider/internal/utils" - "errors" "fmt" "math" "slices" @@ -33,7 +32,6 @@ func NewBlock(network string) *CIDRBlock { } func (b *CIDRBlock) Subnet(sizes []int) ([]string, error) { - // sort subnets largest to smallest to prevent fragmentation slices.Sort(sizes) @@ -43,7 +41,7 @@ func (b *CIDRBlock) Subnet(sizes []int) ([]string, error) { subnetBlock := NewBlock(fmt.Sprintf("%s/%v", next.Network, size)) if !b.Contains(subnetBlock.Network) { - return nil, errors.New("error occured during subnetting") + return nil, fmt.Errorf("invalid configuration: subnet %s/%v is outside provided network range %s/%v", next.Network, size, b.Network, b.HostPortion) } subnets = append(subnets, fmt.Sprintf("%s/%v", subnetBlock.Network, subnetBlock.HostPortion)) diff --git a/internal/cidr/cidr_block_test.go b/internal/cidr/cidr_block_test.go index 47240e1..b34cfc6 100644 --- a/internal/cidr/cidr_block_test.go +++ b/internal/cidr/cidr_block_test.go @@ -6,54 +6,90 @@ import ( "testing" ) -func Test_AvailableHosts(t *testing.T) { +func Test_SubnetSuccess(t *testing.T) { tests := map[string]struct { input *cidr.CIDRBlock - expected uint + sizes []int + expected []string }{ - "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: 4294967296}, - "/1": {input: cidr.NewBlock("10.0.0.0/1"), expected: 2147483648}, - "/2": {input: cidr.NewBlock("10.0.0.0/2"), expected: 1073741824}, - "/3": {input: cidr.NewBlock("10.0.0.0/3"), expected: 536870912}, - "/4": {input: cidr.NewBlock("10.0.0.0/4"), expected: 268435456}, - "/5": {input: cidr.NewBlock("10.0.0.0/5"), expected: 134217728}, - "/6": {input: cidr.NewBlock("10.0.0.0/6"), expected: 67108864}, - "/7": {input: cidr.NewBlock("10.0.0.0/7"), expected: 33554432}, - "/8": {input: cidr.NewBlock("10.0.0.0/8"), expected: 16777216}, - "/9": {input: cidr.NewBlock("10.0.0.0/9"), expected: 8388608}, - "/10": {input: cidr.NewBlock("10.0.0.0/10"), expected: 4194304}, - "/11": {input: cidr.NewBlock("10.0.0.0/11"), expected: 2097152}, - "/12": {input: cidr.NewBlock("10.0.0.0/12"), expected: 1048576}, - "/13": {input: cidr.NewBlock("10.0.0.0/13"), expected: 524288}, - "/14": {input: cidr.NewBlock("10.0.0.0/14"), expected: 262144}, - "/15": {input: cidr.NewBlock("10.0.0.0/15"), expected: 131072}, - "/16": {input: cidr.NewBlock("10.0.0.0/16"), expected: 65536}, - "/17": {input: cidr.NewBlock("10.0.0.0/17"), expected: 32768}, - "/18": {input: cidr.NewBlock("10.0.0.0/18"), expected: 16384}, - "/19": {input: cidr.NewBlock("10.0.0.0/19"), expected: 8192}, - "/20": {input: cidr.NewBlock("10.0.0.0/20"), expected: 4096}, - "/21": {input: cidr.NewBlock("10.0.0.0/21"), expected: 2048}, - "/22": {input: cidr.NewBlock("10.0.0.0/22"), expected: 1024}, - "/23": {input: cidr.NewBlock("10.0.0.0/23"), expected: 512}, - "/24": {input: cidr.NewBlock("10.0.0.0/24"), expected: 256}, - "/25": {input: cidr.NewBlock("10.0.0.0/25"), expected: 128}, - "/26": {input: cidr.NewBlock("10.0.0.0/26"), expected: 64}, - "/27": {input: cidr.NewBlock("10.0.0.0/27"), expected: 32}, - "/28": {input: cidr.NewBlock("10.0.0.0/28"), expected: 16}, - "/29": {input: cidr.NewBlock("10.0.0.0/29"), expected: 8}, - "/30": {input: cidr.NewBlock("10.0.0.0/30"), expected: 4}, - "/31": {input: cidr.NewBlock("10.0.0.0/31"), expected: 2}, - "/32": {input: cidr.NewBlock("10.0.0.0/32"), expected: 1}, + "two even subnets": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{17, 17}, expected: []string{"10.0.0.0/17", "10.0.128.0/17"}}, + "subnets of different sizes": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{18, 17, 20}, expected: []string{"10.0.0.0/17", "10.0.128.0/18", "10.0.192.0/20"}}, } for name, test := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - actual := test.input.AvailableHosts() + actual, _ := test.input.Subnet(test.sizes) + + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("%s returns correct subnet: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_SubnetFailure(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + sizes []int + }{ + "invalid configuration": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{16, 16}}, + "insufficient space": {input: cidr.NewBlock("10.0.0.0/30"), sizes: []int{32, 32, 32, 32, 32}}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual, err := test.input.Subnet(test.sizes) + + if err == nil { + t.Fatalf("%s expected error: got %v", name, actual) + } + }) + } +} + +func Test_Contains(t *testing.T) { + tests := map[string]struct { + ip string + ipRange *cidr.CIDRBlock + expected bool + }{ + "ip inside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/8"), expected: true}, + "ip outside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/28"), expected: false}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.ipRange.Contains(test.ip) if actual != test.expected { - t.Fatalf("%s returns correct number of addresses: got %v expected %v", name, actual, test.expected) + t.Fatalf("%s: got %v expected %v", name, actual, test.expected) + } + }) + } +} + +func Test_NetworkPortionBinary(t *testing.T) { + tests := map[string]struct { + input *cidr.CIDRBlock + expected string + }{ + "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: "00001010.00000000.00000000.00000000"}, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + actual := test.input.NetworkPortionBinary() + + if actual != test.expected { + t.Fatalf("%s returns correct binary representation: got %v expected %v", name, actual, test.expected) } }) } @@ -112,64 +148,54 @@ func Test_SubnetMask(t *testing.T) { } } -func Test_NetworkPortionBinary(t *testing.T) { - tests := map[string]struct { - input *cidr.CIDRBlock - expected string - }{ - "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: "00001010.00000000.00000000.00000000"}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - actual := test.input.NetworkPortionBinary() - - if actual != test.expected { - t.Fatalf("%s returns correct binary representation: got %v expected %v", name, actual, test.expected) - } - }) - } -} - -func Test_NetworkAddress(t *testing.T) { - tests := map[string]struct { - input *cidr.CIDRBlock - expected string - }{ - "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.64"}, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - actual := test.input.NetworkAddress() - - if actual != test.expected { - t.Fatalf("%s returns correct network address: got %v expected %v", name, actual, test.expected) - } - }) - } -} - -func Test_BroadcastAddress(t *testing.T) { +func Test_AvailableHosts(t *testing.T) { tests := map[string]struct { input *cidr.CIDRBlock - expected string + expected uint }{ - "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.127"}, + "/0": {input: cidr.NewBlock("10.0.0.0/0"), expected: 4294967296}, + "/1": {input: cidr.NewBlock("10.0.0.0/1"), expected: 2147483648}, + "/2": {input: cidr.NewBlock("10.0.0.0/2"), expected: 1073741824}, + "/3": {input: cidr.NewBlock("10.0.0.0/3"), expected: 536870912}, + "/4": {input: cidr.NewBlock("10.0.0.0/4"), expected: 268435456}, + "/5": {input: cidr.NewBlock("10.0.0.0/5"), expected: 134217728}, + "/6": {input: cidr.NewBlock("10.0.0.0/6"), expected: 67108864}, + "/7": {input: cidr.NewBlock("10.0.0.0/7"), expected: 33554432}, + "/8": {input: cidr.NewBlock("10.0.0.0/8"), expected: 16777216}, + "/9": {input: cidr.NewBlock("10.0.0.0/9"), expected: 8388608}, + "/10": {input: cidr.NewBlock("10.0.0.0/10"), expected: 4194304}, + "/11": {input: cidr.NewBlock("10.0.0.0/11"), expected: 2097152}, + "/12": {input: cidr.NewBlock("10.0.0.0/12"), expected: 1048576}, + "/13": {input: cidr.NewBlock("10.0.0.0/13"), expected: 524288}, + "/14": {input: cidr.NewBlock("10.0.0.0/14"), expected: 262144}, + "/15": {input: cidr.NewBlock("10.0.0.0/15"), expected: 131072}, + "/16": {input: cidr.NewBlock("10.0.0.0/16"), expected: 65536}, + "/17": {input: cidr.NewBlock("10.0.0.0/17"), expected: 32768}, + "/18": {input: cidr.NewBlock("10.0.0.0/18"), expected: 16384}, + "/19": {input: cidr.NewBlock("10.0.0.0/19"), expected: 8192}, + "/20": {input: cidr.NewBlock("10.0.0.0/20"), expected: 4096}, + "/21": {input: cidr.NewBlock("10.0.0.0/21"), expected: 2048}, + "/22": {input: cidr.NewBlock("10.0.0.0/22"), expected: 1024}, + "/23": {input: cidr.NewBlock("10.0.0.0/23"), expected: 512}, + "/24": {input: cidr.NewBlock("10.0.0.0/24"), expected: 256}, + "/25": {input: cidr.NewBlock("10.0.0.0/25"), expected: 128}, + "/26": {input: cidr.NewBlock("10.0.0.0/26"), expected: 64}, + "/27": {input: cidr.NewBlock("10.0.0.0/27"), expected: 32}, + "/28": {input: cidr.NewBlock("10.0.0.0/28"), expected: 16}, + "/29": {input: cidr.NewBlock("10.0.0.0/29"), expected: 8}, + "/30": {input: cidr.NewBlock("10.0.0.0/30"), expected: 4}, + "/31": {input: cidr.NewBlock("10.0.0.0/31"), expected: 2}, + "/32": {input: cidr.NewBlock("10.0.0.0/32"), expected: 1}, } for name, test := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - actual := test.input.BroadcastAddress() + actual := test.input.AvailableHosts() if actual != test.expected { - t.Fatalf("%s returns correct broadcast address: got %v expected %v", name, actual, test.expected) + t.Fatalf("%s returns correct number of addresses: got %v expected %v", name, actual, test.expected) } }) } @@ -196,47 +222,43 @@ func Test_StartAddressOfNextBlock(t *testing.T) { } } -func Test_Contains(t *testing.T) { +func Test_NetworkAddress(t *testing.T) { tests := map[string]struct { - ip string - ipRange *cidr.CIDRBlock - expected bool + input *cidr.CIDRBlock + expected string }{ - "ip inside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/8"), expected: true}, - "ip outside range": {ip: "10.50.30.7", ipRange: cidr.NewBlock("10.0.0.0/28"), expected: false}, + "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.64"}, } for name, test := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - actual := test.ipRange.Contains(test.ip) + actual := test.input.NetworkAddress() if actual != test.expected { - t.Fatalf("%s: got %v expected %v", name, actual, test.expected) + t.Fatalf("%s returns correct network address: got %v expected %v", name, actual, test.expected) } }) } } -func Test_Subnet(t *testing.T) { +func Test_BroadcastAddress(t *testing.T) { tests := map[string]struct { input *cidr.CIDRBlock - sizes []int - expected []string + expected string }{ - "two even subnets": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{17, 17}, expected: []string{"10.0.0.0/17", "10.0.128.0/17"}}, - "three subnets": {input: cidr.NewBlock("10.0.0.0/16"), sizes: []int{18, 17, 18}, expected: []string{"10.0.0.0/17", "10.0.128.0/18", "10.0.192.0/18"}}, + "/26": {input: cidr.NewBlock("192.168.33.64/26"), expected: "192.168.33.127"}, } for name, test := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - actual, _ := test.input.Subnet(test.sizes) + actual := test.input.BroadcastAddress() - if !reflect.DeepEqual(actual, test.expected) { - t.Fatalf("%s returns correct subnet: got %v expected %v", name, actual, test.expected) + if actual != test.expected { + t.Fatalf("%s returns correct broadcast address: got %v expected %v", name, actual, test.expected) } }) } From 516d78adff6d3c993f7359e902858adcdc3784c6 Mon Sep 17 00:00:00 2001 From: fremartini Date: Sat, 8 Mar 2025 22:38:02 +0100 Subject: [PATCH 6/8] rework info --- internal/cidr/cidr_block.go | 10 +++++++++ internal/commands/info/command.go | 32 +++++++++++++++++++++++------ internal/commands/ranges/command.go | 11 +--------- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go index c1a99a8..2ae3d57 100644 --- a/internal/cidr/cidr_block.go +++ b/internal/cidr/cidr_block.go @@ -109,6 +109,16 @@ func (b *CIDRBlock) AvailableHosts() uint { return uint(numAddresses) } +func (b *CIDRBlock) AvailableAzureHosts() string { + // Azure reserves the first four addresses and the last address, for a total of five IP addresses within each subnet + // https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets + if b.AvailableHosts() >= 5 { + return fmt.Sprintf("%v", b.AvailableHosts()-5) + } + + return "N/A" +} + func (b *CIDRBlock) StartAddressOfNextBlock() string { octets := strings.Split(b.BroadcastAddress(), ".") octets = list.Map(octets, toBin) diff --git a/internal/commands/info/command.go b/internal/commands/info/command.go index c177f08..f1ac89d 100644 --- a/internal/commands/info/command.go +++ b/internal/commands/info/command.go @@ -2,9 +2,10 @@ package info import ( "cider/internal/cidr" + "cider/internal/list" + "cider/internal/utils" "fmt" - "os" - "text/tabwriter" + "math" ) type handler struct{} @@ -22,10 +23,29 @@ func (h *handler) Handle(args []string) error { block := cidr.NewBlock(ip) - w := tabwriter.NewWriter(os.Stdout, 2, 4, 1, ' ', 0) + entries := map[string]any{ + "Address range": fmt.Sprintf("%s - %s", block.NetworkAddress(), block.BroadcastAddress()), + "Start of next block": block.StartAddressOfNextBlock(), + "Cidr": fmt.Sprintf("/%v", block.HostPortion), + "Subnet mask": block.SubnetMask(), + "Addresses": block.AvailableHosts(), + "Azure addresses": block.AvailableAzureHosts(), + } + + keys := []string{} + for k := range entries { + keys = append(keys, k) + } - fmt.Fprint(w, "Address range\tStart of next block\tCidr\tSubnet mask\tAddresses\n") - fmt.Fprintf(w, "%v - %v\t%s\t/%v\t%v\t%v\n", block.NetworkAddress(), block.BroadcastAddress(), block.StartAddressOfNextBlock(), block.HostPortion, block.SubnetMask(), block.AvailableHosts()) + widestTitle := list.Fold(keys, 0, func(e string, acc int) int { + m := int(math.Max(float64(acc), float64(len(e)))) + + return m + }) + + for k, v := range entries { + fmt.Printf("%s : %v\n", utils.PadRight(k, ' ', widestTitle), v) + } - return w.Flush() + return nil } diff --git a/internal/commands/ranges/command.go b/internal/commands/ranges/command.go index 335b067..45637b0 100644 --- a/internal/commands/ranges/command.go +++ b/internal/commands/ranges/command.go @@ -64,16 +64,7 @@ func printCIDRBlocks(blocks []*cidr.CIDRBlock) error { fmt.Fprint(w, "Cidr\tSubnet mask\tAddresses\tAzure addresses\n") for _, block := range blocks { - - availableAzureAddresses := "N/A" - - // Azure reserves the first four addresses and the last address, for a total of five IP addresses within each subnet - // https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-faq#are-there-any-restrictions-on-using-ip-addresses-within-these-subnets - if block.AvailableHosts() >= 5 { - availableAzureAddresses = fmt.Sprintf("%v", block.AvailableHosts()-5) - } - - fmt.Fprintf(w, "/%v\t%s\t%v\t%s\n", block.HostPortion, block.SubnetMask(), block.AvailableHosts(), availableAzureAddresses) + fmt.Fprintf(w, "/%v\t%s\t%v\t%s\n", block.HostPortion, block.SubnetMask(), block.AvailableHosts(), block.AvailableAzureHosts()) } return w.Flush() From 7a672a57b9ab493c11d5868294d149e3a9ef215e Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Mon, 10 Mar 2025 14:43:18 +0100 Subject: [PATCH 7/8] ensure ordering --- internal/commands/info/command.go | 40 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/internal/commands/info/command.go b/internal/commands/info/command.go index f1ac89d..c34f016 100644 --- a/internal/commands/info/command.go +++ b/internal/commands/info/command.go @@ -14,6 +14,10 @@ func New() *handler { return &handler{} } +type pair struct { + a, b string +} + func (h *handler) Handle(args []string) error { if len(args) != 1 { return fmt.Errorf("command expects exactly one argument") @@ -23,29 +27,31 @@ func (h *handler) Handle(args []string) error { block := cidr.NewBlock(ip) - entries := map[string]any{ - "Address range": fmt.Sprintf("%s - %s", block.NetworkAddress(), block.BroadcastAddress()), - "Start of next block": block.StartAddressOfNextBlock(), - "Cidr": fmt.Sprintf("/%v", block.HostPortion), - "Subnet mask": block.SubnetMask(), - "Addresses": block.AvailableHosts(), - "Azure addresses": block.AvailableAzureHosts(), + entries := []pair{ + {a: "Address range", b: fmt.Sprintf("%s - %s", block.NetworkAddress(), block.BroadcastAddress())}, + {a: "Start of next block", b: block.StartAddressOfNextBlock()}, + {a: "Cidr", b: fmt.Sprintf("/%v", block.HostPortion)}, + {a: "Subnet mask", b: block.SubnetMask()}, + {a: "Addresses", b: fmt.Sprintf("%v", block.AvailableHosts())}, + {a: "Azure addresses", b: block.AvailableAzureHosts()}, } + printOutput(entries) + + return nil +} + +func printOutput(entries []pair) { keys := []string{} - for k := range entries { - keys = append(keys, k) + for _, pair := range entries { + keys = append(keys, pair.a) } - widestTitle := list.Fold(keys, 0, func(e string, acc int) int { - m := int(math.Max(float64(acc), float64(len(e)))) - - return m + longestTitle := list.Fold(keys, 0, func(title string, acc int) int { + return int(math.Max(float64(acc), float64(len(title)))) }) - for k, v := range entries { - fmt.Printf("%s : %v\n", utils.PadRight(k, ' ', widestTitle), v) + for _, pair := range entries { + fmt.Printf("%s : %v\n", utils.PadRight(pair.a, ' ', longestTitle), pair.b) } - - return nil } From e79fbec79fd329efe06f2d365d14ef206662350c Mon Sep 17 00:00:00 2001 From: Frederik Martini Date: Mon, 10 Mar 2025 14:48:16 +0100 Subject: [PATCH 8/8] refactor octet handling --- internal/cidr/cidr_block.go | 43 ++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/internal/cidr/cidr_block.go b/internal/cidr/cidr_block.go index 2ae3d57..32d13f3 100644 --- a/internal/cidr/cidr_block.go +++ b/internal/cidr/cidr_block.go @@ -94,13 +94,9 @@ func (b *CIDRBlock) SubnetMask() string { mask := ones + zeroes - base := 2 - octet1 := must(strconv.ParseInt(mask[0:8], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(mask[8:16], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(mask[16:24], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(mask[24:32], base, INT_SIZE)) + octets := stringToOctets(mask) - return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) + return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3]) } func (b *CIDRBlock) AvailableHosts() uint { @@ -129,13 +125,9 @@ func (b *CIDRBlock) StartAddressOfNextBlock() string { asBinaryString := strconv.FormatInt(next, 2) asBinaryString = utils.PadLeft(asBinaryString, '0', 32) - base := 2 - octet1 := must(strconv.ParseInt(asBinaryString[0:8], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(asBinaryString[8:16], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(asBinaryString[16:24], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(asBinaryString[24:32], base, INT_SIZE)) + octetsInt := stringToOctets(asBinaryString) - return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) + return fmt.Sprintf("%v.%v.%v.%v", octetsInt[0], octetsInt[1], octetsInt[2], octetsInt[3]) } func (b *CIDRBlock) NetworkAddress() string { @@ -143,29 +135,32 @@ func (b *CIDRBlock) NetworkAddress() string { broadcast := ipBin + strings.Repeat("0", INT_SIZE-b.HostPortion) - base := 2 - octet1 := must(strconv.ParseInt(broadcast[0:8], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(broadcast[8:16], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(broadcast[16:24], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(broadcast[24:32], base, INT_SIZE)) + octets := stringToOctets(broadcast) - return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) + return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3]) } func (b *CIDRBlock) BroadcastAddress() string { // https://stackoverflow.com/questions/1470792/how-to-calculate-the-ip-range-when-the-ip-address-and-the-netmask-is-given - ipBin := strings.ReplaceAll(b.NetworkPortionBinary(), ".", "")[0:b.HostPortion] broadcast := ipBin + strings.Repeat("1", INT_SIZE-b.HostPortion) + octets := stringToOctets(broadcast) + + return fmt.Sprintf("%v.%v.%v.%v", octets[0], octets[1], octets[2], octets[3]) +} + +func stringToOctets(ipString string) []int64 { + octets := make([]int64, 4) + base := 2 - octet1 := must(strconv.ParseInt(broadcast[0:8], base, INT_SIZE)) - octet2 := must(strconv.ParseInt(broadcast[8:16], base, INT_SIZE)) - octet3 := must(strconv.ParseInt(broadcast[16:24], base, INT_SIZE)) - octet4 := must(strconv.ParseInt(broadcast[24:32], base, INT_SIZE)) + octets[0] = must(strconv.ParseInt(ipString[0:8], base, INT_SIZE)) + octets[1] = must(strconv.ParseInt(ipString[8:16], base, INT_SIZE)) + octets[2] = must(strconv.ParseInt(ipString[16:24], base, INT_SIZE)) + octets[3] = must(strconv.ParseInt(ipString[24:32], base, INT_SIZE)) - return fmt.Sprintf("%v.%v.%v.%v", octet1, octet2, octet3, octet4) + return octets } func must[T any](x T, e error) T {