inital working commit
This commit is contained in:
136
.gitignore
vendored
Normal file
136
.gitignore
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/goland+all,linux,windows
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=goland+all,linux,windows
|
||||
|
||||
### GoLand+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### GoLand+all Patch ###
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
### Linux ###
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/goland+all,linux,windows
|
||||
|
||||
config.yaml
|
||||
|
5
config.sample.yaml
Normal file
5
config.sample.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
logging:
|
||||
level: DEBUG
|
||||
|
||||
station_ip: "ip here"
|
||||
password: "password here"
|
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module VodaDocsis
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/influxdata/line-protocol/v2 v2.2.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3
|
||||
golang.org/x/crypto v0.2.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
)
|
51
go.sum
Normal file
51
go.sum
Normal file
@@ -0,0 +1,51 @@
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
|
||||
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98=
|
||||
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig=
|
||||
github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo=
|
||||
github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY=
|
||||
github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY=
|
||||
github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE=
|
||||
github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3 h1:shC1HB1UogxN5Ech3Yqaaxj1X/P656PPCB4RbojIJqc=
|
||||
github.com/wercker/journalhook v0.0.0-20180428041537-5d0a5ae867b3/go.mod h1:XCsSkdKK4gwBMNrOCZWww0pX6AOt+2gYc5Z6jBRrNVg=
|
||||
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
|
||||
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
478
main.go
Normal file
478
main.go
Normal file
@@ -0,0 +1,478 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/influxdata/line-protocol/v2/lineprotocol"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/wercker/journalhook"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
Logging struct {
|
||||
Level string
|
||||
}
|
||||
StationIP string `yaml:"station_ip"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
|
||||
var (
|
||||
configFlag = flag.String("config", "config.yaml", "path of config file to use")
|
||||
journalLogFlag = flag.Bool("journal", false, "log to systemd journal instead of stdout/stderr")
|
||||
conf = Conf{}
|
||||
httpClient *http.Client
|
||||
)
|
||||
|
||||
const (
|
||||
ITERATIONS = 1000
|
||||
KEYSIZEBITS = 128
|
||||
USERNAME = "admin"
|
||||
)
|
||||
|
||||
type ChannelType string
|
||||
|
||||
//goland:noinspection ALL
|
||||
const (
|
||||
OFDM ChannelType = "OFDM"
|
||||
SCQAM ChannelType = "SC-QAM"
|
||||
OFDMA ChannelType = "OFDMA"
|
||||
)
|
||||
|
||||
func (c ChannelType) String() string {
|
||||
return string(c)
|
||||
}
|
||||
|
||||
type ChannelDirection string
|
||||
|
||||
const (
|
||||
UP ChannelDirection = "UP"
|
||||
DOWN ChannelDirection = "DOWN"
|
||||
)
|
||||
|
||||
func (c ChannelDirection) String() string {
|
||||
return strings.ToLower(string(c))
|
||||
}
|
||||
|
||||
type BaseResponse struct {
|
||||
Error string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Intf string `json:"intf"`
|
||||
User string `json:"user"`
|
||||
UID string `json:"uid"`
|
||||
Dpd string `json:"Dpd"`
|
||||
RemoteAddr string `json:"remoteAddr"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
HTTPReferer string `json:"httpReferer"`
|
||||
}
|
||||
|
||||
type DOCSISResponse struct {
|
||||
OfdmDownstream []*DownstreamOFDMChannelInfo `json:"ofdm_downstream"`
|
||||
Downstream []*DownstreamChannelInfo `json:"downstream"`
|
||||
OfdmaUpstream []*UpstreamOFDMChannelInfo `json:"ofdma_upstream"`
|
||||
Upstream []*UpstreamChannelInfo `json:"upstream"`
|
||||
}
|
||||
|
||||
type DownstreamOFDMChannelInfo struct {
|
||||
ID string `json:"__id"`
|
||||
ChannelIDOfdm string `json:"channelid_ofdm"`
|
||||
StartFrequency string `json:"start_frequency"`
|
||||
EndFrequency string `json:"end_frequency"`
|
||||
CentralFrequencyOfdm string `json:"CentralFrequency_ofdm"`
|
||||
Bandwidth string `json:"bandwidth"`
|
||||
PowerOfdm string `json:"power_ofdm"`
|
||||
SNROfdm string `json:"SNR_ofdm"`
|
||||
FFTOfdm string `json:"FFT_ofdm"`
|
||||
LockedOfdm string `json:"locked_ofdm"`
|
||||
ChannelType string `json:"ChannelType"`
|
||||
}
|
||||
|
||||
type UpstreamOFDMChannelInfo struct {
|
||||
ID string `json:"__id"`
|
||||
ChannelIDUp string `json:"channelidup"`
|
||||
StartFrequency string `json:"start_frequency"`
|
||||
EndFrequency string `json:"end_frequency"`
|
||||
Power string `json:"power"`
|
||||
CentralFrequency string `json:"CentralFrequency"`
|
||||
Bandwidth string `json:"bandwidth"`
|
||||
FFT string `json:"FFT"`
|
||||
ChannelType string `json:"ChannelType"`
|
||||
RangingStatus string `json:"RangingStatus"`
|
||||
}
|
||||
|
||||
type DownstreamChannelInfo struct {
|
||||
ID string `json:"__id"`
|
||||
ChannelID string `json:"channelid"`
|
||||
CentralFrequency string `json:"CentralFrequency"`
|
||||
Power string `json:"power"`
|
||||
SNR string `json:"SNR"`
|
||||
FFT string `json:"FFT"`
|
||||
Locked string `json:"locked"`
|
||||
ChannelType string `json:"ChannelType"`
|
||||
}
|
||||
|
||||
type UpstreamChannelInfo struct {
|
||||
ID string `json:"__id"`
|
||||
ChannelIDUp string `json:"channelidup"`
|
||||
CentralFrequency string `json:"CentralFrequency"`
|
||||
Power string `json:"power"`
|
||||
ChannelType string `json:"ChannelType"`
|
||||
FFT string `json:"FFT"`
|
||||
RangingStatus string `json:"RangingStatus"`
|
||||
}
|
||||
|
||||
type SaltResponse struct {
|
||||
Error string `json:"error"`
|
||||
Salt string `json:"salt"`
|
||||
SaltWebUI string `json:"saltwebui"`
|
||||
}
|
||||
|
||||
type DOCSISChannelInfo struct {
|
||||
Type ChannelType
|
||||
Power float64
|
||||
RangingStatus *string
|
||||
SNR *float64
|
||||
Direction ChannelDirection
|
||||
Frequency uint64
|
||||
ID string
|
||||
}
|
||||
|
||||
func main() {
|
||||
killSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(killSignals, syscall.SIGINT, syscall.SIGTERM)
|
||||
reloadSignals := make(chan os.Signal, 1)
|
||||
signal.Notify(reloadSignals, syscall.SIGUSR1)
|
||||
flag.Parse()
|
||||
confStr, err := os.ReadFile(*configFlag)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to open config: %v", err)
|
||||
}
|
||||
err = yaml.Unmarshal(confStr, &conf)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to parse config: %v", err)
|
||||
}
|
||||
lvl, err := log.ParseLevel(conf.Logging.Level)
|
||||
if err != nil {
|
||||
log.Fatalf("Failure setting logging level: %v", err)
|
||||
}
|
||||
log.SetLevel(lvl)
|
||||
if *journalLogFlag {
|
||||
journalhook.Enable()
|
||||
}
|
||||
|
||||
cj, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating jar")
|
||||
}
|
||||
|
||||
httpClient = &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
Jar: cj,
|
||||
}
|
||||
|
||||
if err := login(); err != nil {
|
||||
log.Fatalf("error logging in: %v", err)
|
||||
}
|
||||
|
||||
docsisStart := time.Now()
|
||||
var dData *DOCSISResponse
|
||||
if dData, err = DOCSISStatus(); err != nil {
|
||||
log.Errorf("error getting docsis status: %v", err)
|
||||
err = logout()
|
||||
if err != nil {
|
||||
log.Fatalf("error logging out: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
docsisTime := time.Now()
|
||||
|
||||
var enc lineprotocol.Encoder
|
||||
enc.SetPrecision(lineprotocol.Microsecond)
|
||||
|
||||
enc.StartLine("docsis_diagnostic")
|
||||
enc.AddField("response_time_ms", lineprotocol.MustNewValue(time.Since(docsisStart).Milliseconds()))
|
||||
enc.EndLine(docsisTime)
|
||||
|
||||
for _, channel := range transformDOCSIS(dData) {
|
||||
enc.StartLine("docsis")
|
||||
enc.AddTag("channel_id", channel.ID)
|
||||
enc.AddTag("direction", channel.Direction.String())
|
||||
enc.AddTag("type", channel.Type.String())
|
||||
enc.AddField("frequency", lineprotocol.MustNewValue(channel.Frequency))
|
||||
enc.AddField("power", lineprotocol.MustNewValue(channel.Power))
|
||||
|
||||
if channel.Direction == UP {
|
||||
enc.AddField("ranging_status", lineprotocol.MustNewValue(*channel.RangingStatus))
|
||||
} else {
|
||||
enc.AddField("snr", lineprotocol.MustNewValue(*channel.SNR))
|
||||
}
|
||||
|
||||
enc.EndLine(docsisTime)
|
||||
}
|
||||
|
||||
if err = enc.Err(); err != nil {
|
||||
log.Fatalf("influx line protocol encoding error: %v", err)
|
||||
}
|
||||
fmt.Printf("%s", enc.Bytes())
|
||||
|
||||
if err = logout(); err != nil {
|
||||
log.Fatalf("error logging out: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func login() error {
|
||||
postData := url.Values{}
|
||||
postData.Add("username", USERNAME)
|
||||
postData.Add("password", "seeksalthash")
|
||||
postData.Add("logout", "true")
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "http://"+path.Join(conf.StationIP, "/api/v1/session/login"), strings.NewReader(postData.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
||||
req.Header.Set("Referer", "http://"+conf.StationIP+"/")
|
||||
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
|
||||
sResp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sResp.StatusCode != 200 {
|
||||
return fmt.Errorf("login failed")
|
||||
}
|
||||
|
||||
rData, err := io.ReadAll(sResp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sResp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
saltResp := new(SaltResponse)
|
||||
if err = json.Unmarshal(rData, saltResp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if saltResp.Error != "ok" {
|
||||
return fmt.Errorf("login failed: %+v", saltResp.Error)
|
||||
}
|
||||
|
||||
finalKey := Key(Key(conf.Password, saltResp.Salt), saltResp.SaltWebUI)
|
||||
|
||||
postData = url.Values{}
|
||||
postData.Add("username", USERNAME)
|
||||
postData.Add("password", finalKey)
|
||||
|
||||
nResp, err := APIRequest(http.MethodPost, "/api/v1/session/login", postData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loginData := new(LoginResponse)
|
||||
if err = json.Unmarshal(nResp.Data, loginData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = APIRequest(http.MethodGet, "/api/v1/session/menu", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DOCSISStatus() (*DOCSISResponse, error) {
|
||||
resp, err := APIRequest(http.MethodGet, "/api/v1/sta_docsis_status", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docsisData := new(DOCSISResponse)
|
||||
if err = json.Unmarshal(resp.Data, docsisData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("docsis response: %+v", docsisData)
|
||||
|
||||
return docsisData, nil
|
||||
}
|
||||
|
||||
func logout() error {
|
||||
_, err := APIRequest(http.MethodPost, "/api/v1/session/logout", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Key(pw string, salt string) string {
|
||||
return hex.EncodeToString(pbkdf2.Key([]byte(pw), []byte(salt), ITERATIONS, KEYSIZEBITS/8, sha256.New))
|
||||
}
|
||||
|
||||
func APIRequest(method string, endpoint string, postData url.Values) (*BaseResponse, error) {
|
||||
log.Debugf("[API] %s: %s", method, endpoint)
|
||||
|
||||
var bodyReader io.Reader
|
||||
if postData != nil {
|
||||
bodyReader = strings.NewReader(postData.Encode())
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, "http://"+path.Join(conf.StationIP, endpoint), bodyReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
|
||||
req.Header.Set("Referer", "http://"+conf.StationIP+"/")
|
||||
req.Header.Set("X-Requested-With", "XMLHttpRequest")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rData, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("[API] calling %s failed with %d: %s", endpoint, resp.StatusCode, string(rData))
|
||||
}
|
||||
|
||||
baseResp := new(BaseResponse)
|
||||
if err = json.Unmarshal(rData, baseResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if baseResp.Error != "ok" {
|
||||
var data any
|
||||
_ = json.Unmarshal(baseResp.Data, &data)
|
||||
return nil, fmt.Errorf("%s failed: %s (data: %+v)", endpoint, baseResp.Message, data)
|
||||
}
|
||||
|
||||
return baseResp, nil
|
||||
}
|
||||
|
||||
func transformDOCSIS(rawDOCSIS *DOCSISResponse) (nChannels []*DOCSISChannelInfo) {
|
||||
for _, channel := range rawDOCSIS.OfdmDownstream {
|
||||
nChannels = append(nChannels, &DOCSISChannelInfo{
|
||||
Type: ChannelType(channel.ChannelType),
|
||||
Power: powerStr2Power(channel.PowerOfdm),
|
||||
SNR: snrStr2SNR(channel.SNROfdm),
|
||||
Direction: DOWN,
|
||||
Frequency: freqStr2Hz(channel.CentralFrequencyOfdm),
|
||||
ID: channel.ChannelIDOfdm,
|
||||
})
|
||||
}
|
||||
|
||||
for _, channel := range rawDOCSIS.Downstream {
|
||||
nChannels = append(nChannels, &DOCSISChannelInfo{
|
||||
Type: ChannelType(channel.ChannelType),
|
||||
Power: powerStr2Power(channel.Power),
|
||||
SNR: snrStr2SNR(channel.SNR),
|
||||
Direction: DOWN,
|
||||
Frequency: freqStr2Hz(channel.CentralFrequency),
|
||||
ID: channel.ChannelID,
|
||||
})
|
||||
}
|
||||
|
||||
for _, channel := range rawDOCSIS.OfdmaUpstream {
|
||||
nChannels = append(nChannels, &DOCSISChannelInfo{
|
||||
Type: ChannelType(channel.ChannelType),
|
||||
Power: powerStr2Power(channel.Power),
|
||||
RangingStatus: &channel.RangingStatus,
|
||||
Direction: UP,
|
||||
Frequency: freqStr2Hz(channel.CentralFrequency),
|
||||
ID: channel.ChannelIDUp,
|
||||
})
|
||||
}
|
||||
|
||||
for _, channel := range rawDOCSIS.Upstream {
|
||||
nChannels = append(nChannels, &DOCSISChannelInfo{
|
||||
Type: ChannelType(channel.ChannelType),
|
||||
Power: powerStr2Power(channel.Power),
|
||||
RangingStatus: &channel.RangingStatus,
|
||||
Direction: UP,
|
||||
Frequency: freqStr2Hz(channel.CentralFrequency),
|
||||
ID: channel.ChannelIDUp,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func snrStr2SNR(snrStr string) *float64 {
|
||||
rSNR, unit := strUnit2Elements(snrStr)
|
||||
|
||||
if strings.ToLower(unit) != "db" {
|
||||
panic(fmt.Sprintf("error parsing power unit %s", snrStr))
|
||||
}
|
||||
|
||||
return &rSNR
|
||||
}
|
||||
|
||||
func powerStr2Power(powerStr string) float64 {
|
||||
rPower, unit := strUnit2Elements(powerStr)
|
||||
|
||||
if strings.ToLower(unit) != "dbmv" {
|
||||
panic(fmt.Sprintf("error parsing power unit %s", powerStr))
|
||||
}
|
||||
|
||||
return rPower
|
||||
}
|
||||
|
||||
func freqStr2Hz(freqStr string) uint64 {
|
||||
rFreq, unit := strUnit2Elements(freqStr)
|
||||
|
||||
if strings.ToLower(unit) != "mhz" {
|
||||
panic(fmt.Sprintf("error parsing frequency unit %s", freqStr))
|
||||
}
|
||||
|
||||
return uint64(rFreq * 1000000)
|
||||
}
|
||||
|
||||
func strUnit2Elements(rawStr string) (float64, string) {
|
||||
splitStr := strings.Split(rawStr, " ")
|
||||
|
||||
if len(splitStr) < 2 {
|
||||
panic(fmt.Sprintf("error parsing floatUnit %s", rawStr))
|
||||
}
|
||||
|
||||
rFloat, err := strconv.ParseFloat(splitStr[0], 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error parsing floatUnit %s", splitStr[0]))
|
||||
}
|
||||
|
||||
return rFloat, splitStr[1]
|
||||
}
|
Reference in New Issue
Block a user