package model import ( "fmt" "sort" "strings" "github.com/jinzhu/copier" "go.uber.org/zap" "k8s.io/utils/ptr" "github.com/bakito/adguardhome-sync/internal/utils" ) // Clone the config. func (c *DhcpStatus) Clone() *DhcpStatus { clone := &DhcpStatus{} _ = copier.Copy(clone, c) return clone } func (c *DhcpStatus) cleanV4V6() { if c.V4 != nil && !c.V4.isValid() { c.V4 = nil } if c.V6 != nil && !c.V6.isValid() { c.V6 = nil } } // CleanAndEquals dhcp server config equal check where V4 and V6 are cleaned in advance. func (c *DhcpStatus) CleanAndEquals(o *DhcpStatus) bool { c.cleanV4V6() o.cleanV4V6() return c.Equals(o) } // Equals dhcp server config equal check. func (c *DhcpStatus) Equals(o *DhcpStatus) bool { return utils.JSONEquals(c, o) } func (c *DhcpStatus) HasConfig() bool { return (c.V4 != nil && c.V4.isValid()) || (c.V6 != nil && c.V6.isValid()) } func (j DhcpConfigV4) isValid() bool { return j.GatewayIp != nil && *j.GatewayIp != "" && j.SubnetMask != nil && *j.SubnetMask != "" && j.RangeStart != nil && *j.RangeStart != "" && j.RangeEnd != nil && *j.RangeEnd != "" } func (j DhcpConfigV6) isValid() bool { return j.RangeStart != nil && *j.RangeStart != "" } type DhcpStaticLeases []DhcpStaticLease // MergeDhcpStaticLeases the leases. func MergeDhcpStaticLeases(l, other *[]DhcpStaticLease) (adds, removes DhcpStaticLeases) { var thisLeases []DhcpStaticLease var otherLeases []DhcpStaticLease if l != nil { thisLeases = *l } if other != nil { otherLeases = *other } current := make(map[string]DhcpStaticLease) for _, le := range thisLeases { current[le.Mac] = le } for _, le := range otherLeases { if _, ok := current[le.Mac]; ok { delete(current, le.Mac) } else { adds = append(adds, le) } } for _, rr := range current { removes = append(removes, rr) } return adds, removes } // Equals dns config equal check. func (c *DNSConfig) Equals(o *DNSConfig) bool { cc := c.Clone() oo := o.Clone() cc.Sort() oo.Sort() return utils.JSONEquals(cc, oo) } func (c *DNSConfig) Clone() *DNSConfig { return utils.Clone(c, &DNSConfig{}) } // Sort dns config. func (c *DNSConfig) Sort() { if c.UpstreamDns != nil { sort.Strings(*c.UpstreamDns) } if c.UpstreamDns != nil { sort.Strings(*c.BootstrapDns) } if c.UpstreamDns != nil { sort.Strings(*c.LocalPtrUpstreams) } } // Equals access list equal check. func (al *AccessList) Equals(o *AccessList) bool { return EqualsStringSlice(al.AllowedClients, o.AllowedClients, true) && EqualsStringSlice(al.DisallowedClients, o.DisallowedClients, true) && EqualsStringSlice(al.BlockedHosts, o.BlockedHosts, true) } func EqualsStringSlice(a, b *[]string, sortIt bool) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } aa := *a bb := *b if sortIt { sort.Strings(aa) sort.Strings(bb) } if len(aa) != len(bb) { return false } for i, v := range aa { if v != bb[i] { return false } } return true } // Sort clients. func (cl *Client) Sort() { if cl.Ids != nil { sort.Strings(*cl.Ids) } if cl.Tags != nil { sort.Strings(*cl.Tags) } if cl.BlockedServices != nil { sort.Strings(*cl.BlockedServices) } if cl.Upstreams != nil { sort.Strings(*cl.Upstreams) } } // PrepareDiff so we skip it in diff. func (cl *Client) PrepareDiff() *string { var tz *string bss := cl.BlockedServicesSchedule if bss != nil && bss.Mon == nil && bss.Tue == nil && bss.Wed == nil && bss.Thu == nil && bss.Fri == nil && bss.Sat == nil && bss.Sun == nil { tz = cl.BlockedServicesSchedule.TimeZone cl.BlockedServicesSchedule.TimeZone = nil } return tz } // AfterDiff reset after diff. func (cl *Client) AfterDiff(tz *string) { if cl.BlockedServicesSchedule != nil { cl.BlockedServicesSchedule.TimeZone = tz } } // Equals Clients equal check. func (cl *Client) Equals(o *Client) bool { cl.Sort() o.Sort() bssCl := cl.PrepareDiff() bssO := o.PrepareDiff() defer func() { cl.AfterDiff(bssCl) o.AfterDiff(bssO) }() return utils.JSONEquals(cl, o) } // Add ac client. func (clients *Clients) Add(cl Client) { if clients.Clients == nil { clients.Clients = &ClientsArray{cl} } else { a := append(*clients.Clients, cl) clients.Clients = &a } } // Merge merge Clients. func (clients *Clients) Merge(other *Clients) (adds, removes, updates []*Client) { current := make(map[string]*Client) if clients.Clients != nil { cc := *clients.Clients for _, client := range cc { current[*client.Name] = &client } } expected := make(map[string]*Client) if other.Clients != nil { oc := *other.Clients for _, client := range oc { expected[*client.Name] = &client } } for _, cl := range expected { if oc, ok := current[*cl.Name]; ok { if !cl.Equals(oc) { updates = append(updates, cl) } delete(current, *cl.Name) } else { adds = append(adds, cl) } } for _, rr := range current { removes = append(removes, rr) } return adds, updates, removes } // Key RewriteEntry key. func (re *RewriteEntry) Key() string { var d string var a string if re.Domain != nil { d = *re.Domain } if re.Answer != nil { a = *re.Answer } return fmt.Sprintf("%s#%s", d, a) } // RewriteEntries list of RewriteEntry. type RewriteEntries []RewriteEntry // Merge RewriteEntries. func (rwe *RewriteEntries) Merge(other *RewriteEntries) (adds, removes, duplicates RewriteEntries) { current := make(map[string]RewriteEntry) processed := make(map[string]bool) for _, rr := range *rwe { if _, ok := processed[rr.Key()]; !ok { current[rr.Key()] = rr processed[rr.Key()] = true } else { // remove duplicate removes = append(removes, rr) } } for _, rr := range *other { if _, ok := current[rr.Key()]; ok { delete(current, rr.Key()) } else { if _, ok := processed[rr.Key()]; !ok { adds = append(adds, rr) processed[rr.Key()] = true } else { // skip duplicate duplicates = append(duplicates, rr) } } } for _, rr := range current { removes = append(removes, rr) } return adds, removes, duplicates } func MergeFilters(this, other *[]Filter) (adds, updates, removes []Filter) { if this == nil && other == nil { return nil, nil, nil } current := make(map[string]*Filter) if this != nil { for _, fi := range *this { current[fi.Url] = &fi } } if other != nil { for _, rr := range *other { if c, ok := current[rr.Url]; ok { if !c.Equals(&rr) { updates = append(updates, rr) } delete(current, rr.Url) } else { adds = append(adds, rr) } } } for _, rr := range current { removes = append(removes, *rr) } return adds, updates, removes } // Equals Filter equal check. func (f *Filter) Equals(o *Filter) bool { return f.Enabled == o.Enabled && f.Url == o.Url && f.Name == o.Name } type QueryLogConfigWithIgnored struct { QueryLogConfig // Ignored List of host names, which should not be written to log Ignored []string `json:"ignored,omitempty"` } // Equals QueryLogConfig equal check. func (qlc *QueryLogConfigWithIgnored) Equals(o *QueryLogConfigWithIgnored) bool { return utils.JSONEquals(qlc, o) } // Equals QueryLogConfigInterval equal check. func (qlc *QueryLogConfigInterval) Equals(o *QueryLogConfigInterval) bool { return ptrEquals(qlc, o) } func ptrEquals[T comparable](a, b *T) bool { if a == nil && b == nil { return true } var aa T if a != nil { aa = *a } var bb T if b != nil { bb = *b } return aa == bb } // EnableConfig API struct. type EnableConfig struct { Enabled bool `json:"enabled"` } func (ssc *SafeSearchConfig) Equals(o *SafeSearchConfig) bool { return ptrEquals(ssc.Enabled, o.Enabled) && ptrEquals(ssc.Bing, o.Bing) && ptrEquals(ssc.Duckduckgo, o.Duckduckgo) && ptrEquals(ssc.Google, o.Google) && ptrEquals(ssc.Pixabay, o.Pixabay) && ptrEquals(ssc.Yandex, o.Yandex) && ptrEquals(ssc.Youtube, o.Youtube) } func (pi *ProfileInfo) Equals(o *ProfileInfo, withTheme bool) bool { return pi.Language == o.Language && (!withTheme || pi.Theme == o.Theme) } func (pi *ProfileInfo) ShouldSyncFor(o *ProfileInfo, withTheme bool) *ProfileInfo { if pi.Equals(o, withTheme) { return nil } merged := &ProfileInfo{Name: pi.Name, Language: pi.Language, Theme: pi.Theme} if o.Language != "" { merged.Language = o.Language } if withTheme && o.Theme != "" { merged.Theme = o.Theme } if merged.Name == "" || merged.Language == "" || merged.Equals(pi, false) { return nil } return merged } func (bss *BlockedServicesSchedule) Equals(o *BlockedServicesSchedule) bool { return utils.JSONEquals(bss, o) } func (bss *BlockedServicesSchedule) ServicesString() string { return ArrayString(bss.Ids) } func ArrayString(a *[]string) string { if a == nil { return "[]" } sorted := *a sort.Strings(sorted) return fmt.Sprintf("[%s]", strings.Join(sorted, ",")) } func (c *DNSConfig) Sanitize(l *zap.SugaredLogger) { // disable UsePrivatePtrResolvers if not configured // https://github.com/AdguardTeam/AdGuardHome/issues/6820 if c.UsePrivatePtrResolvers != nil && *c.UsePrivatePtrResolvers && (c.LocalPtrUpstreams == nil || len(*c.LocalPtrUpstreams) == 0) { l.Warn( "disabling replica 'Use private reverse DNS resolvers' as no 'Private reverse DNS servers' are configured on origin", ) c.UsePrivatePtrResolvers = utils.Ptr(false) } } // Equals GetStatsConfigResponse equal check. func (sc *GetStatsConfigResponse) Equals(o *GetStatsConfigResponse) bool { return utils.JSONEquals(sc, o) } func NewStats() *Stats { return &Stats{ NumBlockedFiltering: ptr.To(0), NumReplacedParental: ptr.To(0), NumReplacedSafesearch: ptr.To(0), NumReplacedSafebrowsing: ptr.To(0), NumDnsQueries: ptr.To(0), BlockedFiltering: ptr.To(make([]int, 24)), DnsQueries: ptr.To(make([]int, 24)), ReplacedParental: ptr.To(make([]int, 24)), ReplacedSafebrowsing: ptr.To(make([]int, 24)), } } func (s *Stats) Add(other *Stats) { s.NumBlockedFiltering = addInt(s.NumBlockedFiltering, other.NumBlockedFiltering) s.NumReplacedSafebrowsing = addInt(s.NumReplacedSafebrowsing, other.NumReplacedSafebrowsing) s.NumDnsQueries = addInt(s.NumDnsQueries, other.NumDnsQueries) s.NumReplacedSafesearch = addInt(s.NumReplacedSafesearch, other.NumReplacedSafesearch) s.NumReplacedParental = addInt(s.NumReplacedParental, other.NumReplacedParental) s.BlockedFiltering = sumUp(s.BlockedFiltering, other.BlockedFiltering) s.DnsQueries = sumUp(s.DnsQueries, other.DnsQueries) s.ReplacedParental = sumUp(s.ReplacedParental, other.ReplacedParental) s.ReplacedSafebrowsing = sumUp(s.ReplacedSafebrowsing, other.ReplacedSafebrowsing) } func addInt(t, add *int) *int { if add != nil { return ptr.To(*t + *add) } return t } func sumUp(t, o *[]int) *[]int { if o != nil { tt := *t oo := *o var sum []int for i := range tt { if len(oo) >= i { sum = append(sum, tt[i]+oo[i]) } } return &sum } return t } func (c *TlsConfig) Equals(config *TlsConfig) bool { return utils.JSONEquals(c, config) }