Skip to content

Instantly share code, notes, and snippets.

@htdvisser
Last active July 15, 2022 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save htdvisser/4da4e67a1930d7270e2eb8fa1b6fcd91 to your computer and use it in GitHub Desktop.
Save htdvisser/4da4e67a1930d7270e2eb8fa1b6fcd91 to your computer and use it in GitHub Desktop.
NetID Assignment Extraction Tool

NetID Assignment Extraction Tool

Extract NetID assignments and generate table for documentation

Preparation

  • Download the latest NetID Allocations sheet from the TC Workspace.
  • Save it as netids.xlsx

Usage

go run htdvisser.dev/docnetids@latest ./netids.xls

Support

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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment