Extract NetID assignments and generate table for documentation
- Download the latest NetID Allocations sheet from the TC Workspace.
- Save it as
netids.xlsx
go run htdvisser.dev/docnetids@latest ./netids.xlsNo support, help yourself.
Extract NetID assignments and generate table for documentation
netids.xlsxgo run htdvisser.dev/docnetids@latest ./netids.xlsNo support, help yourself.
| module htdvisser.dev/docnetids | |
| go 1.18 | |
| require github.com/tealeg/xlsx v1.0.5 |
| github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | |
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | |
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |
| github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | |
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | |
| github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE= | |
| github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM= | |
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= | |
| gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
| package main | |
| import ( | |
| "fmt" | |
| "io/ioutil" | |
| "log" | |
| "os" | |
| "strings" | |
| "github.com/tealeg/xlsx" | |
| ) | |
| func main() { | |
| if len(os.Args) != 2 { | |
| log.Fatalf("Usage: %s [filename.xlsx]", os.Args[0]) | |
| } | |
| if err := Main(os.Args[1]); err != nil { | |
| log.Fatal(err) | |
| } | |
| } | |
| func Main(filename string) error { | |
| data, err := ioutil.ReadFile(filename) | |
| if err != nil { | |
| return err | |
| } | |
| doc, err := xlsx.OpenBinary(data) | |
| if err != nil { | |
| return err | |
| } | |
| fmt.Println("| **NetID** | **DevAddr Prefix** | **DevAddr Range** | **Operator** | **Region** |") | |
| fmt.Println("| --------- | ------------------ | ----------------- | ------------ | ---------- |") | |
| for _, sheet := range doc.Sheets { | |
| switch sheet.Name { | |
| case "Master": | |
| if v := sheet.Cell(0, 0).String(); v != "NetID (hex)" { | |
| return fmt.Errorf("Unexpected cell: got: %q, want: %q", v, "NetID (hex)") | |
| } | |
| if v := sheet.Cell(0, 3).String(); v != "Geographical scope" { | |
| return fmt.Errorf("Unexpected cell: got: %q, want: %q", v, "Geographical scope") | |
| } | |
| if v := sheet.Cell(0, 4).String(); v != "Operator/Network name" { | |
| return fmt.Errorf("Unexpected cell: got: %q, want: %q", v, "Operator/Network name") | |
| } | |
| default: | |
| continue | |
| } | |
| for rowIdx := 1; rowIdx < len(sheet.Rows); rowIdx++ { | |
| netIDStr := strings.TrimSpace(sheet.Cell(rowIdx, 0).String()) | |
| region := strings.TrimSpace(sheet.Cell(rowIdx, 3).String()) | |
| operator := strings.TrimSpace(sheet.Cell(rowIdx, 4).String()) | |
| if !strings.HasPrefix(netIDStr, "0x") { | |
| continue | |
| } | |
| var netID NetID | |
| if err = netID.UnmarshalText([]byte(strings.TrimPrefix(netIDStr, "0x"))); err != nil { | |
| continue | |
| } | |
| min, err := NewDevAddr(netID, nil) | |
| if err != nil { | |
| return err | |
| } | |
| prefixLength := uint8(32 - netID.NwkAddrBits()) | |
| prefix := DevAddrPrefix{ | |
| DevAddr: min.Mask(prefixLength), | |
| Length: prefixLength, | |
| } | |
| max := DevAddr{0xff, 0xff, 0xff, 0xff}.WithPrefix(prefix) | |
| if err != nil { | |
| return err | |
| } | |
| assignment := strings.TrimSpace(operator) | |
| switch assignment { | |
| case "<unassigned>": | |
| assignment = "_unassigned_" | |
| } | |
| switch operator { | |
| case "The Things Network": | |
| assignment = "**The Things Network**" | |
| } | |
| fmt.Printf("| `%s` | `%s` | `%s` - `%s` | %s | %s |\n", netID, prefix, min, max, assignment, region) | |
| } | |
| } | |
| return nil | |
| } |
| // Copyright © 2019 The Things Network Foundation, The Things Industries B.V. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // NOTE: This code was copied from go.thethings.network/lorawan-stack/pkg/types | |
| package main | |
| import ( | |
| "encoding/hex" | |
| "errors" | |
| "fmt" | |
| "strings" | |
| ) | |
| type NetID [3]byte | |
| func (id NetID) String() string { return strings.ToUpper(hex.EncodeToString(id[:])) } | |
| func (id NetID) Type() byte { | |
| return id[0] >> 5 | |
| } | |
| func (id NetID) ID() []byte { | |
| switch id.Type() { | |
| case 0, 1: | |
| return []byte{id[2] & 0x3f} | |
| case 2: | |
| return []byte{id[1] & 0x01, id[2]} | |
| case 3, 4, 5, 6, 7: | |
| return []byte{id[0] & 0x1f, id[1], id[2]} | |
| default: | |
| panic("unmatched NetID type") | |
| } | |
| } | |
| func (id NetID) NwkAddrBits() uint { | |
| switch id.Type() { | |
| case 0: | |
| return 25 | |
| case 1: | |
| return 24 | |
| case 2: | |
| return 20 | |
| case 3: | |
| return 17 | |
| case 4: | |
| return 15 | |
| case 5: | |
| return 13 | |
| case 6: | |
| return 10 | |
| case 7: | |
| return 7 | |
| default: | |
| panic("unmatched NetID type") | |
| } | |
| } | |
| func (id *NetID) UnmarshalText(data []byte) error { | |
| *id = [3]byte{} | |
| return unmarshalTextBytes(id[:], data) | |
| } | |
| func unmarshalTextBytes(dst, data []byte) error { | |
| if len(data) == 0 { | |
| return nil | |
| } | |
| b := make([]byte, hex.DecodedLen(len(data))) | |
| n, err := hex.Decode(b, data) | |
| if err != nil { | |
| return err | |
| } | |
| if n != len(dst) || copy(dst, b) != len(dst) { | |
| return errors.New("invalid length") | |
| } | |
| return nil | |
| } | |
| type DevAddrPrefix struct { | |
| DevAddr DevAddr | |
| Length uint8 | |
| } | |
| func (prefix DevAddrPrefix) String() string { | |
| return fmt.Sprintf("%s/%d", prefix.DevAddr, prefix.Length) | |
| } | |
| type DevAddr [4]byte | |
| func (addr DevAddr) String() string { | |
| return strings.ToUpper(hex.EncodeToString(addr[:])) | |
| } | |
| var MinDevAddr = DevAddr{0x00, 0x00, 0x00, 0x00} | |
| var MaxDevAddr = DevAddr{0xFF, 0xFF, 0xFF, 0xFF} | |
| func (addr DevAddr) WithPrefix(prefix DevAddrPrefix) (prefixed DevAddr) { | |
| k := uint(prefix.Length) | |
| for i := 0; i < 4; i++ { | |
| if k >= 8 { | |
| prefixed[i] = prefix.DevAddr[i] & 0xff | |
| k -= 8 | |
| continue | |
| } | |
| prefixed[i] = (prefix.DevAddr[i] & ^byte(0xff>>k)) | (addr[i] & byte(0xff>>k)) | |
| k = 0 | |
| } | |
| return | |
| } | |
| func (addr DevAddr) Mask(bits uint8) DevAddr { | |
| return (DevAddr{}).WithPrefix(DevAddrPrefix{addr, bits}) | |
| } | |
| func NewDevAddr(netID NetID, nwkAddr []byte) (addr DevAddr, err error) { | |
| if len(nwkAddr) < 4 { | |
| nwkAddr = append(make([]byte, 4-len(nwkAddr)), nwkAddr...) | |
| } | |
| if nwkAddr[0]&(0xfe<<((netID.NwkAddrBits()-1)%8)) > 0 { | |
| return DevAddr{}, errors.New("invalid length") | |
| } | |
| copy(addr[:], nwkAddr) | |
| nwkID := netID.ID() | |
| t := netID.Type() | |
| switch t { | |
| case 0: | |
| addr[0] |= nwkID[0] << 1 | |
| case 1: | |
| addr[0] |= nwkID[0] | |
| case 2: | |
| addr[1] |= nwkID[1] << 4 | |
| addr[0] |= nwkID[1] >> 4 | |
| addr[0] |= nwkID[0] << 4 | |
| case 3: | |
| addr[1] |= nwkID[2] << 1 | |
| addr[0] |= nwkID[2] >> 7 | |
| addr[0] |= nwkID[1] << 1 | |
| case 4: | |
| addr[2] |= nwkID[2] << 7 | |
| addr[1] |= nwkID[2] >> 1 | |
| addr[1] |= nwkID[1] << 7 | |
| addr[0] |= nwkID[1] >> 1 | |
| case 5: | |
| addr[2] |= nwkID[2] << 5 | |
| addr[1] |= nwkID[2] >> 3 | |
| addr[1] |= nwkID[1] << 5 | |
| addr[0] |= nwkID[1] >> 3 | |
| case 6: | |
| addr[2] |= nwkID[2] << 2 | |
| addr[1] |= nwkID[2] >> 6 | |
| addr[1] |= nwkID[1] << 2 | |
| addr[0] |= nwkID[1] >> 6 | |
| case 7: | |
| addr[3] |= nwkID[2] << 7 | |
| addr[2] |= nwkID[2] >> 1 | |
| addr[2] |= nwkID[1] << 7 | |
| addr[1] |= nwkID[1] >> 1 | |
| addr[1] |= nwkID[0] << 7 | |
| } | |
| addr[0] |= 0xfe << (7 - t) | |
| return addr, nil | |
| } |