Files
elmprodvpn/selective-vpn-api/app/transport_owner_locks_clear.go

106 lines
3.0 KiB
Go

package app
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/netip"
"sort"
"strings"
)
type transportOwnerLockClearFilter struct {
ClientID string
DestinationIPs []string
}
func normalizeTransportOwnerLockClearRequest(in TransportOwnerLocksClearRequest) (transportOwnerLockClearFilter, error) {
filter := transportOwnerLockClearFilter{
ClientID: sanitizeID(in.ClientID),
}
seen := map[string]struct{}{}
appendIP := func(raw string) error {
v := strings.TrimSpace(raw)
if v == "" {
return nil
}
addr, err := netip.ParseAddr(v)
if err != nil || !addr.Is4() {
return fmt.Errorf("invalid destination ip: %q", raw)
}
key := addr.String()
if _, ok := seen[key]; ok {
return nil
}
seen[key] = struct{}{}
filter.DestinationIPs = append(filter.DestinationIPs, key)
return nil
}
if err := appendIP(in.DestinationIP); err != nil {
return transportOwnerLockClearFilter{}, err
}
for _, raw := range in.DestinationIPs {
if err := appendIP(raw); err != nil {
return transportOwnerLockClearFilter{}, err
}
}
sort.Strings(filter.DestinationIPs)
if filter.ClientID == "" && len(filter.DestinationIPs) == 0 {
return transportOwnerLockClearFilter{}, fmt.Errorf("at least one selector is required: client_id or destination_ip(s)")
}
return filter, nil
}
func splitTransportOwnerLocksByFilter(items []TransportOwnerLockRecord, filter transportOwnerLockClearFilter) (matched, remaining []TransportOwnerLockRecord) {
matchIPs := map[string]struct{}{}
for _, ip := range filter.DestinationIPs {
matchIPs[ip] = struct{}{}
}
matched = make([]TransportOwnerLockRecord, 0, len(items))
remaining = make([]TransportOwnerLockRecord, 0, len(items))
for _, it := range items {
match := true
if filter.ClientID != "" && sanitizeID(it.ClientID) != filter.ClientID {
match = false
}
if len(matchIPs) > 0 {
dst := strings.TrimSpace(it.DestinationIP)
if addr, err := netip.ParseAddr(dst); err == nil && addr.Is4() {
dst = addr.String()
}
if _, ok := matchIPs[dst]; !ok {
match = false
}
}
if match {
matched = append(matched, it)
} else {
remaining = append(remaining, it)
}
}
return matched, remaining
}
func digestTransportOwnerLocksClear(baseRevision int64, filter transportOwnerLockClearFilter, matched []TransportOwnerLockRecord) string {
keys := make([]string, 0, len(matched))
for _, it := range matched {
keys = append(keys, strings.TrimSpace(it.ClientID)+"|"+strings.TrimSpace(it.DestinationIP))
}
sort.Strings(keys)
payload := struct {
BaseRevision int64 `json:"base_revision"`
ClientID string `json:"client_id,omitempty"`
DestinationIPs []string `json:"destination_ips,omitempty"`
Matches []string `json:"matches,omitempty"`
}{
BaseRevision: baseRevision,
ClientID: filter.ClientID,
DestinationIPs: append([]string(nil), filter.DestinationIPs...),
Matches: keys,
}
b, _ := json.Marshal(payload)
h := sha256.Sum256(b)
return hex.EncodeToString(h[:])
}