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.xls
No support, help yourself.
Extract NetID assignments and generate table for documentation
netids.xlsx
go run htdvisser.dev/docnetids@latest ./netids.xls
No 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 | |
} |