| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package l2topology |
| 4 | |
| 5 | import ( |
| 6 | "strings" |
| 7 | ) |
| 8 | |
| 9 | type topologySegmentPortRef struct { |
| 10 | deviceID string |
| 11 | ifName string |
| 12 | ifIndex string |
| 13 | bridgePort string |
| 14 | } |
| 15 | |
| 16 | func topologySegmentDisplayName(actor Actor, deviceDisplayByID map[string]string) string { |
| 17 | attrs := actor.Attributes |
| 18 | if len(attrs) == 0 { |
| 19 | return "" |
| 20 | } |
| 21 | |
| 22 | ref := parseTopologySegmentPortRef(topologyAttrString(attrs, "designated_port")) |
| 23 | parent := topologySegmentParentDisplayName(ref.deviceID, deviceDisplayByID) |
| 24 | port := topologySegmentPortDisplay(ref) |
| 25 | if parent != "" && port != "" { |
| 26 | return parent + "." + port + ".segment" |
| 27 | } |
| 28 | |
| 29 | parentCandidates := make(map[string]struct{}) |
| 30 | for _, candidate := range topologyAttrStringSlice(attrs, "parent_devices") { |
| 31 | if value := topologySegmentParentDisplayName(candidate, deviceDisplayByID); value != "" { |
| 32 | parentCandidates[value] = struct{}{} |
| 33 | } |
| 34 | } |
| 35 | portCandidates := make(map[string]struct{}) |
| 36 | for _, candidate := range topologyAttrStringSlice(attrs, "if_names") { |
| 37 | if candidate = strings.TrimSpace(candidate); candidate != "" { |
| 38 | portCandidates[candidate] = struct{}{} |
| 39 | } |
| 40 | } |
| 41 | if len(portCandidates) == 0 { |
| 42 | for _, candidate := range topologyAttrStringSlice(attrs, "bridge_ports") { |
| 43 | if candidate = strings.TrimSpace(candidate); candidate != "" { |
| 44 | portCandidates[candidate] = struct{}{} |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | parents := sortedTopologySet(parentCandidates) |
| 49 | ports := sortedTopologySet(portCandidates) |
| 50 | if len(parents) > 0 && len(ports) > 0 { |
| 51 | return parents[0] + "." + ports[0] + ".segment" |
| 52 | } |
| 53 | |
| 54 | return topologyCompactSegmentID(topologyAttrString(attrs, "segment_id")) |
| 55 | } |
| 56 | |
| 57 | func parseTopologySegmentPortRef(raw string) topologySegmentPortRef { |
| 58 | raw = strings.TrimSpace(raw) |
| 59 | if raw == "" { |
| 60 | return topologySegmentPortRef{} |
| 61 | } |
| 62 | parts := strings.Split(raw, keySep) |
| 63 | if len(parts) == 0 { |
| 64 | return topologySegmentPortRef{} |
| 65 | } |
| 66 | ref := topologySegmentPortRef{ |
| 67 | deviceID: strings.TrimSpace(parts[0]), |
| 68 | } |
| 69 | for _, part := range parts[1:] { |
| 70 | switch { |
| 71 | case strings.HasPrefix(part, "name:"): |
| 72 | ref.ifName = strings.TrimSpace(strings.TrimPrefix(part, "name:")) |
| 73 | case strings.HasPrefix(part, "if:"): |
| 74 | ref.ifIndex = strings.TrimSpace(strings.TrimPrefix(part, "if:")) |
| 75 | case strings.HasPrefix(part, "bp:"): |
| 76 | ref.bridgePort = strings.TrimSpace(strings.TrimPrefix(part, "bp:")) |
| 77 | } |
| 78 | } |
| 79 | return ref |
| 80 | } |
| 81 | |
| 82 | func topologySegmentPortDisplay(ref topologySegmentPortRef) string { |
| 83 | if name := strings.TrimSpace(ref.ifName); name != "" { |
| 84 | return name |
| 85 | } |
| 86 | if name := strings.TrimSpace(ref.bridgePort); name != "" { |
| 87 | return name |
| 88 | } |
| 89 | if index := strings.TrimSpace(ref.ifIndex); index != "" && index != "0" { |
| 90 | return index |
| 91 | } |
| 92 | return "" |
| 93 | } |
| 94 | |
| 95 | func topologySegmentParentDisplayName(raw string, deviceDisplayByID map[string]string) string { |
| 96 | raw = strings.TrimSpace(raw) |
| 97 | if raw == "" { |
| 98 | return "" |
| 99 | } |
| 100 | if deviceDisplayByID != nil { |
| 101 | if display := strings.TrimSpace(deviceDisplayByID[raw]); display != "" { |
| 102 | return display |
| 103 | } |
| 104 | } |
| 105 | lower := strings.ToLower(raw) |
| 106 | switch { |
| 107 | case strings.HasPrefix(lower, "management_ip:"): |
| 108 | return strings.TrimSpace(raw[len("management_ip:"):]) |
| 109 | case strings.HasPrefix(lower, "macaddress:"): |
| 110 | if mac := normalizeMAC(raw[len("macAddress:"):]); mac != "" { |
| 111 | return mac |
| 112 | } |
| 113 | return strings.TrimSpace(raw[len("macAddress:"):]) |
| 114 | } |
| 115 | return raw |
| 116 | } |
| 117 | |
| 118 | func topologyCompactSegmentID(segmentID string) string { |
| 119 | segmentID = strings.TrimSpace(segmentID) |
| 120 | if segmentID == "" { |
| 121 | return "" |
| 122 | } |
| 123 | const max = 48 |
| 124 | runes := []rune(segmentID) |
| 125 | if len(runes) <= max { |
| 126 | return segmentID |
| 127 | } |
| 128 | return string(runes[:max]) + "..." |
| 129 | } |