AdGuardDNS/internal/agd/device.go
Andrey Meshkov 41f7e6cb22 Sync v2.8.0
2024-07-10 19:49:07 +03:00

130 lines
3.7 KiB
Go

package agd
import (
"fmt"
"net/netip"
"unicode/utf8"
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
)
// Device is a device of a device attached to a profile.
//
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
type Device struct {
// Auth defines authentication settings of this device. It's never nil.
Auth *AuthSettings
// ID is the unique ID of the device.
ID DeviceID
// LinkedIP, when non-empty, allows AdGuard DNS to identify a device by its
// IP address when it can only use plain DNS.
LinkedIP netip.Addr
// Name is the human-readable name of the device.
Name DeviceName
// HumanIDLower is the lower-case version of the normalized [HumanID] used
// to create this device, if any.
HumanIDLower HumanIDLower
// DedicatedIPs are the destination (server) IP-addresses dedicated to this
// device, if any. A device can use one of these addresses as a DNS server
// address for AdGuard DNS to recognize it.
DedicatedIPs []netip.Addr
// FilteringEnabled defines whether queries from the device should be
// filtered in any way at all.
FilteringEnabled bool
}
// DeviceID is the ID of a device attached to a profile. It is an opaque
// string.
type DeviceID string
// The maximum and minimum lengths of a device ID.
const (
MaxDeviceIDLen = 8
MinDeviceIDLen = 1
)
// NewDeviceID converts a simple string into a DeviceID and makes sure that it's
// valid. This should be preferred to a simple type conversion.
func NewDeviceID(s string) (id DeviceID, err error) {
// Do not use errors.Annotate here, because it allocates even when the error
// is nil.
//
// TODO(a.garipov): Find out, why does it allocate and perhaps file an
// issue about that in the Go issue tracker.
defer func() {
if err != nil {
err = fmt.Errorf("bad device id %q: %w", s, err)
}
}()
err = ValidateInclusion(len(s), MaxDeviceIDLen, MinDeviceIDLen, UnitByte)
if err != nil {
// The error will be wrapped by the deferred helper.
return "", err
}
err = netutil.ValidateHostnameLabel(s)
if err != nil {
// Unwrap the error to replace the domain name label wrapper message
// with our own.
return "", errors.Unwrap(err)
}
return DeviceID(s), nil
}
// DeviceName is the human-readable name of a device attached to a profile.
type DeviceName string
// MaxDeviceNameRuneLen is the maximum length of a human-readable device name in
// runes.
const MaxDeviceNameRuneLen = 128
// NewDeviceName converts a simple string into a DeviceName and makes sure that
// it's valid. This should be preferred to a simple type conversion.
func NewDeviceName(s string) (n DeviceName, err error) {
// Do not use errors.Annotate here, because it allocates even when the error
// is nil.
//
// TODO(a.garipov): Same as the TODO in NewDeviceID.
defer func() {
if err != nil {
err = fmt.Errorf("bad device name %q: %w", s, err)
}
}()
err = ValidateInclusion(utf8.RuneCountInString(s), MaxDeviceNameRuneLen, 0, UnitRune)
if err != nil {
// The error will be wrapped by the deferred helper.
return "", err
}
return DeviceName(s), nil
}
// AuthSettings are the authentication settings of a device.
//
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
type AuthSettings struct {
// PasswordHash is the hash of the auth password. It is never nil.
PasswordHash agdpasswd.Authenticator
// Enabled tells whether the authentication should be enabled at all.
// This must be true in order for all parameters to work.
Enabled bool
// DoHAuthOnly defines if the device should only be authenticated through
// DoH protocol.
DoHAuthOnly bool
}