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 }