generated from VLADIMIR/template
124 lines
2.8 KiB
Go
124 lines
2.8 KiB
Go
package cudy
|
|
|
|
import (
|
|
"net"
|
|
"net/netip"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
func parseClients(data []byte) ([]Client, error) {
|
|
doc, err := html.Parse(strings.NewReader(string(data)))
|
|
if err != nil {
|
|
return nil, ErrRead
|
|
}
|
|
clients := []Client{}
|
|
var table *html.Node
|
|
walk(doc, func(n *html.Node) {
|
|
if n.Data == "th" && nodeText(n) == "IP / MAC Address" {
|
|
for parent := n.Parent; parent != nil; parent = parent.Parent {
|
|
if parent.Data == "table" {
|
|
table = parent
|
|
break
|
|
}
|
|
}
|
|
}
|
|
})
|
|
if table == nil {
|
|
return nil, ErrRead
|
|
}
|
|
invalid := false
|
|
seen := map[string]bool{}
|
|
walk(table, func(row *html.Node) {
|
|
if row.Type != html.ElementNode {
|
|
return
|
|
}
|
|
if row.Data != "tr" {
|
|
return
|
|
}
|
|
hasData := false
|
|
for child := row.FirstChild; child != nil; child = child.NextSibling {
|
|
hasData = hasData || child.Data == "td"
|
|
}
|
|
if !hasData {
|
|
return
|
|
}
|
|
if !strings.HasPrefix(attr(row, "id"), "cbi-table-") {
|
|
invalid = true
|
|
return
|
|
}
|
|
fields := map[string]*html.Node{}
|
|
prefix := attr(row, "id") + "-"
|
|
walk(row, func(n *html.Node) {
|
|
if n.Data == "div" && strings.HasPrefix(attr(n, "id"), prefix) {
|
|
fields[strings.TrimPrefix(attr(n, "id"), prefix)] = n
|
|
}
|
|
})
|
|
var band string
|
|
wired := false
|
|
if hostname := fields["hostname"]; hostname != nil {
|
|
walk(hostname, func(n *html.Node) {
|
|
if n.Data == "span" {
|
|
label := nodeText(n)
|
|
wired = wired || label == "Ethernet"
|
|
if label == "2.4G WiFi" {
|
|
band = "2.4 GHz"
|
|
}
|
|
if label == "5G WiFi" {
|
|
band = "5 GHz"
|
|
}
|
|
}
|
|
})
|
|
}
|
|
// shortcut: stock TR3000 2.5.27 DOM and two bands; update parser fixtures for other firmware.
|
|
if band == "" {
|
|
if !wired {
|
|
invalid = true
|
|
}
|
|
return
|
|
}
|
|
client := Client{Band: band}
|
|
if field := fields["ipmac"]; field != nil {
|
|
walk(field, func(n *html.Node) {
|
|
if n.Data != "p" || client.MAC != "" {
|
|
return
|
|
}
|
|
for _, value := range strings.Fields(nodeText(n)) {
|
|
if ip, e := netip.ParseAddr(value); e == nil {
|
|
client.IP = ip.String()
|
|
}
|
|
if mac, e := net.ParseMAC(value); e == nil && len(mac) == 6 {
|
|
client.MAC = strings.ToUpper(mac.String())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if field := fields["signal"]; field != nil {
|
|
walk(field, func(n *html.Node) {
|
|
if n.Data == "p" && client.Signal == nil {
|
|
parts := strings.Fields(nodeText(n))
|
|
if len(parts) == 2 && parts[1] == "dBm" {
|
|
if value, e := strconv.Atoi(parts[0]); e == nil && value >= -120 && value <= 0 {
|
|
client.Signal = &value
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if client.MAC == "" || seen[client.MAC] {
|
|
invalid = true
|
|
return
|
|
}
|
|
seen[client.MAC] = true
|
|
clients = append(clients, client)
|
|
})
|
|
if invalid || len(clients) > 1024 {
|
|
return nil, ErrRead
|
|
}
|
|
sort.Slice(clients, func(i, j int) bool { return clients[i].MAC < clients[j].MAC })
|
|
return clients, nil
|
|
}
|