Skip to content

Commit

Permalink
all: imp uniq validation err msgs
Browse files Browse the repository at this point in the history
  • Loading branch information
ainar-g committed Dec 28, 2021
1 parent 2ed1f93 commit d9fc625
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 128 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ and this project adheres to

- `windows/arm64` support ([#3057]).

### Changed

- The validation error message for duplicated allow- and blocklists in DNS
settings now shows the duplicated elements ([#3975]).

### Deprecated

<!--
Expand All @@ -44,6 +49,7 @@ and this project adheres to

[#3057]: https://github.com/AdguardTeam/AdGuardHome/issues/3057
[#3868]: https://github.com/AdguardTeam/AdGuardHome/issues/3868
[#3975]: https://github.com/AdguardTeam/AdGuardHome/issues/3975
[#3987]: https://github.com/AdguardTeam/AdGuardHome/issues/3987
[#4008]: https://github.com/AdguardTeam/AdGuardHome/issues/4008
[#4016]: https://github.com/AdguardTeam/AdGuardHome/issues/4016
Expand Down
74 changes: 74 additions & 0 deletions internal/aghalgo/aghalgo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Package aghalgo contains common generic algorithms and data structures.
//
// TODO(a.garipov): Update to use type parameters in Go 1.18.
package aghalgo

import (
"fmt"
"sort"
)

// any is a convenient alias for interface{}
//
// TODO(a.garipov): Remove in Go 1.18.
type any = interface{}

// UniquenessValidator allows validating uniqueness of comparable items.
type UniquenessValidator map[any]int64

// Add adds a value to the validator. v must not be nil.
func (v UniquenessValidator) Add(elems ...any) {
for _, e := range elems {
v[e]++
}
}

// Merge returns a validator containing data from both v and other.
func (v UniquenessValidator) Merge(other UniquenessValidator) (merged UniquenessValidator) {
merged = make(UniquenessValidator, len(v)+len(other))
for elem, num := range v {
merged[elem] += num
}

for elem, num := range other {
merged[elem] += num
}

return merged
}

// Validate returns an error enumerating all elements that aren't unique.
// isBefore is an optional sorting function to make the error message
// deterministic.
func (v UniquenessValidator) Validate(isBefore func(a, b any) (less bool)) (err error) {
var dup []any
for elem, num := range v {
if num > 1 {
dup = append(dup, elem)
}
}

if len(dup) == 0 {
return nil
}

if isBefore != nil {
sort.Slice(dup, func(i, j int) (less bool) {
return isBefore(dup[i], dup[j])
})
}

return fmt.Errorf("duplicated values: %v", dup)
}

// IntIsBefore is a helper sort function for UniquenessValidator.Validate.
// a and b must be of type int.
func IntIsBefore(a, b any) (less bool) {
return a.(int) < b.(int)
}

// StringIsBefore is a helper sort function for UniquenessValidator.Validate.
// a and b must be of type string.
func StringIsBefore(a, b any) (less bool) {
return a.(string) < b.(string)
}
68 changes: 26 additions & 42 deletions internal/dnsforward/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"net/http"
"strings"

"github.com/AdguardTeam/AdGuardHome/internal/aghalgo"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/stringutil"
Expand Down Expand Up @@ -194,62 +194,46 @@ func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
}
}

func isUniq(slice []string) (ok bool, uniqueMap map[string]unit) {
exists := make(map[string]unit)
for _, key := range slice {
if _, has := exists[key]; has {
return false, nil
}
exists[key] = unit{}
}
return true, exists
}

func intersect(mapA, mapB map[string]unit) bool {
for key := range mapA {
if _, has := mapB[key]; has {
return true
}
}
return false
}

// validateAccessSet checks the internal accessListJSON lists. To search for
// duplicates, we cannot compare the new stringutil.Set and []string, because
// creating a set for a large array can be an unnecessary algorithmic complexity
func validateAccessSet(list accessListJSON) (err error) {
const (
errAllowedDup errors.Error = "duplicates in allowed clients"
errDisallowedDup errors.Error = "duplicates in disallowed clients"
errBlockedDup errors.Error = "duplicates in blocked hosts"
errIntersect errors.Error = "some items in allowed and " +
"disallowed lists at the same time"
)

ok, allowedClients := isUniq(list.AllowedClients)
if !ok {
return errAllowedDup
func validateAccessSet(list *accessListJSON) (err error) {
allowed, err := validateStrUniq(list.AllowedClients)
if err != nil {
return fmt.Errorf("validating allowed clients: %w", err)
}

ok, disallowedClients := isUniq(list.DisallowedClients)
if !ok {
return errDisallowedDup
disallowed, err := validateStrUniq(list.DisallowedClients)
if err != nil {
return fmt.Errorf("validating disallowed clients: %w", err)
}

ok, _ = isUniq(list.BlockedHosts)
if !ok {
return errBlockedDup
_, err = validateStrUniq(list.BlockedHosts)
if err != nil {
return fmt.Errorf("validating blocked hosts: %w", err)
}

if intersect(allowedClients, disallowedClients) {
return errIntersect
merged := allowed.Merge(disallowed)
err = merged.Validate(aghalgo.StringIsBefore)
if err != nil {
return fmt.Errorf("items in allowed and disallowed clients intersect: %w", err)
}

return nil
}

// validateStrUniq returns an informative error if clients are not unique.
func validateStrUniq(clients []string) (uv aghalgo.UniquenessValidator, err error) {
uv = make(aghalgo.UniquenessValidator, len(clients))
for _, c := range clients {
uv.Add(c)
}

return uv, uv.Validate(aghalgo.StringIsBefore)
}

func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
list := accessListJSON{}
list := &accessListJSON{}
err := json.NewDecoder(r.Body).Decode(&list)
if err != nil {
aghhttp.Error(r, w, http.StatusBadRequest, "decoding request: %s", err)
Expand Down
24 changes: 19 additions & 5 deletions internal/home/config.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package home

import (
"fmt"
"net"
"os"
"path/filepath"
"sync"

"github.com/AdguardTeam/AdGuardHome/internal/aghalgo"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/filtering"
Expand Down Expand Up @@ -286,22 +288,25 @@ func parseConfig() (err error) {
return err
}

pm := portsMap{}
pm.add(
uv := aghalgo.UniquenessValidator{}
addPorts(
uv,
config.BindPort,
config.BetaBindPort,
config.DNS.Port,
)

if config.TLS.Enabled {
pm.add(
addPorts(
uv,
config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt,
)
}
if err = pm.validate(); err != nil {
return err
if err = uv.Validate(aghalgo.IntIsBefore); err != nil {
return fmt.Errorf("validating ports: %w", err)
}

if !checkFiltersUpdateIntervalHours(config.DNS.FiltersUpdateIntervalHours) {
Expand All @@ -315,6 +320,15 @@ func parseConfig() (err error) {
return nil
}

// addPorts is a helper for ports validation. It skips zero ports.
func addPorts(uv aghalgo.UniquenessValidator, ports ...int) {
for _, p := range ports {
if p != 0 {
uv.Add(p)
}
}
}

// readConfigFile reads configuration file contents.
func readConfigFile() (fileData []byte, err error) {
if len(config.fileData) > 0 {
Expand Down
18 changes: 13 additions & 5 deletions internal/home/controlinstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/aghalgo"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/golibs/errors"
Expand Down Expand Up @@ -102,9 +103,15 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
return
}

pm := portsMap{}
pm.add(config.BindPort, config.BetaBindPort, reqData.Web.Port)
if err = pm.validate(); err != nil {
uv := aghalgo.UniquenessValidator{}
addPorts(
uv,
config.BindPort,
config.BetaBindPort,
reqData.Web.Port,
)
if err = uv.Validate(aghalgo.IntIsBefore); err != nil {
err = fmt.Errorf("validating ports: %w", err)
respData.Web.Status = err.Error()
} else if reqData.Web.Port != 0 {
err = aghnet.CheckPort("tcp", reqData.Web.IP, reqData.Web.Port)
Expand All @@ -113,8 +120,9 @@ func (web *Web) handleInstallCheckConfig(w http.ResponseWriter, r *http.Request)
}
}

pm.add(reqData.DNS.Port)
if err = pm.validate(); err != nil {
addPorts(uv, reqData.DNS.Port)
if err = uv.Validate(aghalgo.IntIsBefore); err != nil {
err = fmt.Errorf("validating ports: %w", err)
respData.DNS.Status = err.Error()
} else if reqData.DNS.Port != 0 {
err = aghnet.CheckPort("udp", reqData.DNS.IP, reqData.DNS.Port)
Expand Down
15 changes: 9 additions & 6 deletions internal/home/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"syscall"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/aghalgo"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpd"
Expand Down Expand Up @@ -295,22 +296,24 @@ func setupConfig(args options) (err error) {
Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts)

if args.bindPort != 0 {
pm := portsMap{}
pm.add(
uv := aghalgo.UniquenessValidator{}
addPorts(
uv,
args.bindPort,
config.BetaBindPort,
config.DNS.Port,
)
if config.TLS.Enabled {
pm.add(
addPorts(
uv,
config.TLS.PortHTTPS,
config.TLS.PortDNSOverTLS,
config.TLS.PortDNSOverQUIC,
config.TLS.PortDNSCrypt,
)
}
if err = pm.validate(); err != nil {
return err
if err = uv.Validate(aghalgo.IntIsBefore); err != nil {
return fmt.Errorf("validating ports: %w", err)
}

config.BindPort = args.bindPort
Expand Down Expand Up @@ -374,7 +377,7 @@ func fatalOnError(err error) {
}
}

// run performs configurating and starts AdGuard Home.
// run configures and starts AdGuard Home.
func run(args options, clientBuildFS fs.FS) {
var err error

Expand Down
Loading

0 comments on commit d9fc625

Please sign in to comment.