package ldap

import (
	"fmt"
	"strconv"

	ber "github.com/go-asn1-ber/asn1-ber"
	"github.com/google/uuid"
)

const (
	// ControlTypePaging - https://www.ietf.org/rfc/rfc2696.txt
	ControlTypePaging = "1.2.840.113556.1.4.319"
	// ControlTypeBeheraPasswordPolicy - https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
	ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1"
	// ControlTypeVChuPasswordMustChange - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
	ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4"
	// ControlTypeVChuPasswordWarning - https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
	ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5"
	// ControlTypeManageDsaIT - https://tools.ietf.org/html/rfc3296
	ControlTypeManageDsaIT = "2.16.840.1.113730.3.4.2"
	// ControlTypeWhoAmI - https://tools.ietf.org/html/rfc4532
	ControlTypeWhoAmI = "1.3.6.1.4.1.4203.1.11.3"
	// ControlTypeSubtreeDelete - https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02
	ControlTypeSubtreeDelete = "1.2.840.113556.1.4.805"

	// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
	ControlTypeServerSideSorting = "1.2.840.113556.1.4.473"
	// ControlTypeServerSideSorting - https://www.ietf.org/rfc/rfc2891.txt
	ControlTypeServerSideSortingResult = "1.2.840.113556.1.4.474"

	// ControlTypeMicrosoftNotification - https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
	ControlTypeMicrosoftNotification = "1.2.840.113556.1.4.528"
	// ControlTypeMicrosoftShowDeleted - https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
	ControlTypeMicrosoftShowDeleted = "1.2.840.113556.1.4.417"
	// ControlTypeMicrosoftServerLinkTTL - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
	ControlTypeMicrosoftServerLinkTTL = "1.2.840.113556.1.4.2309"
	// ControlTypeDirSync - Active Directory DirSync - https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
	ControlTypeDirSync = "1.2.840.113556.1.4.841"

	// ControlTypeSyncRequest - https://www.ietf.org/rfc/rfc4533.txt
	ControlTypeSyncRequest = "1.3.6.1.4.1.4203.1.9.1.1"
	// ControlTypeSyncState - https://www.ietf.org/rfc/rfc4533.txt
	ControlTypeSyncState = "1.3.6.1.4.1.4203.1.9.1.2"
	// ControlTypeSyncDone - https://www.ietf.org/rfc/rfc4533.txt
	ControlTypeSyncDone = "1.3.6.1.4.1.4203.1.9.1.3"
	// ControlTypeSyncInfo - https://www.ietf.org/rfc/rfc4533.txt
	ControlTypeSyncInfo = "1.3.6.1.4.1.4203.1.9.1.4"
)

// Flags for DirSync control
const (
	DirSyncIncrementalValues   int64 = 2147483648
	DirSyncPublicDataOnly      int64 = 8192
	DirSyncAncestorsFirstOrder int64 = 2048
	DirSyncObjectSecurity      int64 = 1
)

// ControlTypeMap maps controls to text descriptions
var ControlTypeMap = map[string]string{
	ControlTypePaging:                  "Paging",
	ControlTypeBeheraPasswordPolicy:    "Password Policy - Behera Draft",
	ControlTypeManageDsaIT:             "Manage DSA IT",
	ControlTypeSubtreeDelete:           "Subtree Delete Control",
	ControlTypeMicrosoftNotification:   "Change Notification - Microsoft",
	ControlTypeMicrosoftShowDeleted:    "Show Deleted Objects - Microsoft",
	ControlTypeMicrosoftServerLinkTTL:  "Return TTL-DNs for link values with associated expiry times - Microsoft",
	ControlTypeServerSideSorting:       "Server Side Sorting Request - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
	ControlTypeServerSideSortingResult: "Server Side Sorting Results - LDAP Control Extension for Server Side Sorting of Search Results (RFC2891)",
	ControlTypeDirSync:                 "DirSync",
	ControlTypeSyncRequest:             "Sync Request",
	ControlTypeSyncState:               "Sync State",
	ControlTypeSyncDone:                "Sync Done",
	ControlTypeSyncInfo:                "Sync Info",
}

// Control defines an interface controls provide to encode and describe themselves
type Control interface {
	// GetControlType returns the OID
	GetControlType() string
	// Encode returns the ber packet representation
	Encode() *ber.Packet
	// String returns a human-readable description
	String() string
}

// ControlString implements the Control interface for simple controls
type ControlString struct {
	ControlType  string
	Criticality  bool
	ControlValue string
}

// GetControlType returns the OID
func (c *ControlString) GetControlType() string {
	return c.ControlType
}

// Encode returns the ber packet representation
func (c *ControlString) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")"))
	if c.Criticality {
		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
	}
	if c.ControlValue != "" {
		packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value"))
	}
	return packet
}

// String returns a human-readable description
func (c *ControlString) String() string {
	return fmt.Sprintf("Control Type: %s (%q)  Criticality: %t  Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue)
}

// ControlPaging implements the paging control described in https://www.ietf.org/rfc/rfc2696.txt
type ControlPaging struct {
	// PagingSize indicates the page size
	PagingSize uint32
	// Cookie is an opaque value returned by the server to track a paging cursor
	Cookie []byte
}

// GetControlType returns the OID
func (c *ControlPaging) GetControlType() string {
	return ControlTypePaging
}

// Encode returns the ber packet representation
func (c *ControlPaging) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")"))

	p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)")
	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value")
	seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.PagingSize), "Paging Size"))
	cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
	cookie.Value = c.Cookie
	cookie.Data.Write(c.Cookie)
	seq.AppendChild(cookie)
	p2.AppendChild(seq)

	packet.AppendChild(p2)
	return packet
}

// String returns a human-readable description
func (c *ControlPaging) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t  PagingSize: %d  Cookie: %q",
		ControlTypeMap[ControlTypePaging],
		ControlTypePaging,
		false,
		c.PagingSize,
		c.Cookie)
}

// SetCookie stores the given cookie in the paging control
func (c *ControlPaging) SetCookie(cookie []byte) {
	c.Cookie = cookie
}

// ControlBeheraPasswordPolicy implements the control described in https://tools.ietf.org/html/draft-behera-ldap-password-policy-10
type ControlBeheraPasswordPolicy struct {
	// Expire contains the number of seconds before a password will expire
	Expire int64
	// Grace indicates the remaining number of times a user will be allowed to authenticate with an expired password
	Grace int64
	// Error indicates the error code
	Error int8
	// ErrorString is a human readable error
	ErrorString string
}

// GetControlType returns the OID
func (c *ControlBeheraPasswordPolicy) GetControlType() string {
	return ControlTypeBeheraPasswordPolicy
}

// Encode returns the ber packet representation
func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")"))

	return packet
}

// String returns a human-readable description
func (c *ControlBeheraPasswordPolicy) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t  Expire: %d  Grace: %d  Error: %d, ErrorString: %s",
		ControlTypeMap[ControlTypeBeheraPasswordPolicy],
		ControlTypeBeheraPasswordPolicy,
		false,
		c.Expire,
		c.Grace,
		c.Error,
		c.ErrorString)
}

// ControlVChuPasswordMustChange implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
type ControlVChuPasswordMustChange struct {
	// MustChange indicates if the password is required to be changed
	MustChange bool
}

// GetControlType returns the OID
func (c *ControlVChuPasswordMustChange) GetControlType() string {
	return ControlTypeVChuPasswordMustChange
}

// Encode returns the ber packet representation
func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet {
	return nil
}

// String returns a human-readable description
func (c *ControlVChuPasswordMustChange) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t  MustChange: %v",
		ControlTypeMap[ControlTypeVChuPasswordMustChange],
		ControlTypeVChuPasswordMustChange,
		false,
		c.MustChange)
}

// ControlVChuPasswordWarning implements the control described in https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy-00
type ControlVChuPasswordWarning struct {
	// Expire indicates the time in seconds until the password expires
	Expire int64
}

// GetControlType returns the OID
func (c *ControlVChuPasswordWarning) GetControlType() string {
	return ControlTypeVChuPasswordWarning
}

// Encode returns the ber packet representation
func (c *ControlVChuPasswordWarning) Encode() *ber.Packet {
	return nil
}

// String returns a human-readable description
func (c *ControlVChuPasswordWarning) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t  Expire: %b",
		ControlTypeMap[ControlTypeVChuPasswordWarning],
		ControlTypeVChuPasswordWarning,
		false,
		c.Expire)
}

// ControlManageDsaIT implements the control described in https://tools.ietf.org/html/rfc3296
type ControlManageDsaIT struct {
	// Criticality indicates if this control is required
	Criticality bool
}

// GetControlType returns the OID
func (c *ControlManageDsaIT) GetControlType() string {
	return ControlTypeManageDsaIT
}

// Encode returns the ber packet representation
func (c *ControlManageDsaIT) Encode() *ber.Packet {
	// FIXME
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeManageDsaIT, "Control Type ("+ControlTypeMap[ControlTypeManageDsaIT]+")"))
	if c.Criticality {
		packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))
	}
	return packet
}

// String returns a human-readable description
func (c *ControlManageDsaIT) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t",
		ControlTypeMap[ControlTypeManageDsaIT],
		ControlTypeManageDsaIT,
		c.Criticality)
}

// NewControlManageDsaIT returns a ControlManageDsaIT control
func NewControlManageDsaIT(Criticality bool) *ControlManageDsaIT {
	return &ControlManageDsaIT{Criticality: Criticality}
}

// ControlMicrosoftNotification implements the control described in https://msdn.microsoft.com/en-us/library/aa366983(v=vs.85).aspx
type ControlMicrosoftNotification struct{}

// GetControlType returns the OID
func (c *ControlMicrosoftNotification) GetControlType() string {
	return ControlTypeMicrosoftNotification
}

// Encode returns the ber packet representation
func (c *ControlMicrosoftNotification) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftNotification, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftNotification]+")"))

	return packet
}

// String returns a human-readable description
func (c *ControlMicrosoftNotification) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)",
		ControlTypeMap[ControlTypeMicrosoftNotification],
		ControlTypeMicrosoftNotification)
}

// NewControlMicrosoftNotification returns a ControlMicrosoftNotification control
func NewControlMicrosoftNotification() *ControlMicrosoftNotification {
	return &ControlMicrosoftNotification{}
}

// ControlMicrosoftShowDeleted implements the control described in https://msdn.microsoft.com/en-us/library/aa366989(v=vs.85).aspx
type ControlMicrosoftShowDeleted struct{}

// GetControlType returns the OID
func (c *ControlMicrosoftShowDeleted) GetControlType() string {
	return ControlTypeMicrosoftShowDeleted
}

// Encode returns the ber packet representation
func (c *ControlMicrosoftShowDeleted) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftShowDeleted, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftShowDeleted]+")"))

	return packet
}

// String returns a human-readable description
func (c *ControlMicrosoftShowDeleted) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)",
		ControlTypeMap[ControlTypeMicrosoftShowDeleted],
		ControlTypeMicrosoftShowDeleted)
}

// NewControlMicrosoftShowDeleted returns a ControlMicrosoftShowDeleted control
func NewControlMicrosoftShowDeleted() *ControlMicrosoftShowDeleted {
	return &ControlMicrosoftShowDeleted{}
}

// ControlMicrosoftServerLinkTTL implements the control described in https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f4f523a8-abc0-4b3a-a471-6b2fef135481?redirectedfrom=MSDN
type ControlMicrosoftServerLinkTTL struct{}

// GetControlType returns the OID
func (c *ControlMicrosoftServerLinkTTL) GetControlType() string {
	return ControlTypeMicrosoftServerLinkTTL
}

// Encode returns the ber packet representation
func (c *ControlMicrosoftServerLinkTTL) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeMicrosoftServerLinkTTL, "Control Type ("+ControlTypeMap[ControlTypeMicrosoftServerLinkTTL]+")"))

	return packet
}

// String returns a human-readable description
func (c *ControlMicrosoftServerLinkTTL) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)",
		ControlTypeMap[ControlTypeMicrosoftServerLinkTTL],
		ControlTypeMicrosoftServerLinkTTL)
}

// NewControlMicrosoftServerLinkTTL returns a ControlMicrosoftServerLinkTTL control
func NewControlMicrosoftServerLinkTTL() *ControlMicrosoftServerLinkTTL {
	return &ControlMicrosoftServerLinkTTL{}
}

// FindControl returns the first control of the given type in the list, or nil
func FindControl(controls []Control, controlType string) Control {
	for _, c := range controls {
		if c.GetControlType() == controlType {
			return c
		}
	}
	return nil
}

// DecodeControl returns a control read from the given packet, or nil if no recognized control can be made
func DecodeControl(packet *ber.Packet) (Control, error) {
	var (
		ControlType = ""
		Criticality = false
		value       *ber.Packet
	)

	switch len(packet.Children) {
	case 0:
		// at least one child is required for control type
		return nil, fmt.Errorf("at least one child is required for control type")

	case 1:
		// just type, no criticality or value
		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
		ControlType = packet.Children[0].Value.(string)

	case 2:
		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
		if packet.Children[0].Value != nil {
			ControlType = packet.Children[0].Value.(string)
		} else if packet.Children[0].Data != nil {
			ControlType = packet.Children[0].Data.String()
		} else {
			return nil, fmt.Errorf("not found where to get the control type")
		}

		// Children[1] could be criticality or value (both are optional)
		// duck-type on whether this is a boolean
		if _, ok := packet.Children[1].Value.(bool); ok {
			packet.Children[1].Description = "Criticality"
			Criticality = packet.Children[1].Value.(bool)
		} else {
			packet.Children[1].Description = "Control Value"
			value = packet.Children[1]
		}

	case 3:
		packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")"
		ControlType = packet.Children[0].Value.(string)

		packet.Children[1].Description = "Criticality"
		Criticality = packet.Children[1].Value.(bool)

		packet.Children[2].Description = "Control Value"
		value = packet.Children[2]

	default:
		// more than 3 children is invalid
		return nil, fmt.Errorf("more than 3 children is invalid for controls")
	}

	switch ControlType {
	case ControlTypeManageDsaIT:
		return NewControlManageDsaIT(Criticality), nil
	case ControlTypePaging:
		value.Description += " (Paging)"
		c := new(ControlPaging)
		if value.Value != nil {
			valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
			if err != nil {
				return nil, fmt.Errorf("failed to decode data bytes: %s", err)
			}
			value.Data.Truncate(0)
			value.Value = nil
			value.AppendChild(valueChildren)
		}
		value = value.Children[0]
		value.Description = "Search Control Value"
		value.Children[0].Description = "Paging Size"
		value.Children[1].Description = "Cookie"
		c.PagingSize = uint32(value.Children[0].Value.(int64))
		c.Cookie = value.Children[1].Data.Bytes()
		value.Children[1].Value = c.Cookie
		return c, nil
	case ControlTypeBeheraPasswordPolicy:
		value.Description += " (Password Policy - Behera)"
		c := NewControlBeheraPasswordPolicy()
		if value.Value != nil {
			valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
			if err != nil {
				return nil, fmt.Errorf("failed to decode data bytes: %s", err)
			}
			value.Data.Truncate(0)
			value.Value = nil
			value.AppendChild(valueChildren)
		}

		sequence := value.Children[0]

		for _, child := range sequence.Children {
			if child.Tag == 0 {
				// Warning
				warningPacket := child.Children[0]
				val, err := ber.ParseInt64(warningPacket.Data.Bytes())
				if err != nil {
					return nil, fmt.Errorf("failed to decode data bytes: %s", err)
				}
				if warningPacket.Tag == 0 {
					// timeBeforeExpiration
					c.Expire = val
					warningPacket.Value = c.Expire
				} else if warningPacket.Tag == 1 {
					// graceAuthNsRemaining
					c.Grace = val
					warningPacket.Value = c.Grace
				}
			} else if child.Tag == 1 {
				// Error
				bs := child.Data.Bytes()
				if len(bs) != 1 || bs[0] > 8 {
					return nil, fmt.Errorf("failed to decode data bytes: %s", "invalid PasswordPolicyResponse enum value")
				}
				val := int8(bs[0])
				c.Error = val
				child.Value = c.Error
				c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error]
			}
		}
		return c, nil
	case ControlTypeVChuPasswordMustChange:
		c := &ControlVChuPasswordMustChange{MustChange: true}
		return c, nil
	case ControlTypeVChuPasswordWarning:
		c := &ControlVChuPasswordWarning{Expire: -1}
		expireStr := ber.DecodeString(value.Data.Bytes())

		expire, err := strconv.ParseInt(expireStr, 10, 64)
		if err != nil {
			return nil, fmt.Errorf("failed to parse value as int: %s", err)
		}
		c.Expire = expire
		value.Value = c.Expire

		return c, nil
	case ControlTypeMicrosoftNotification:
		return NewControlMicrosoftNotification(), nil
	case ControlTypeMicrosoftShowDeleted:
		return NewControlMicrosoftShowDeleted(), nil
	case ControlTypeMicrosoftServerLinkTTL:
		return NewControlMicrosoftServerLinkTTL(), nil
	case ControlTypeSubtreeDelete:
		return NewControlSubtreeDelete(), nil
	case ControlTypeServerSideSorting:
		return NewControlServerSideSorting(value)
	case ControlTypeServerSideSortingResult:
		return NewControlServerSideSortingResult(value)
	case ControlTypeDirSync:
		value.Description += " (DirSync)"
		return NewResponseControlDirSync(value)
	case ControlTypeSyncState:
		value.Description += " (Sync State)"
		valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
		if err != nil {
			return nil, fmt.Errorf("failed to decode data bytes: %s", err)
		}
		return NewControlSyncState(valueChildren)
	case ControlTypeSyncDone:
		value.Description += " (Sync Done)"
		valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
		if err != nil {
			return nil, fmt.Errorf("failed to decode data bytes: %s", err)
		}
		return NewControlSyncDone(valueChildren)
	case ControlTypeSyncInfo:
		value.Description += " (Sync Info)"
		valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
		if err != nil {
			return nil, fmt.Errorf("failed to decode data bytes: %s", err)
		}
		return NewControlSyncInfo(valueChildren)
	default:
		c := new(ControlString)
		c.ControlType = ControlType
		c.Criticality = Criticality
		if value != nil {
			c.ControlValue = value.Value.(string)
		}
		return c, nil
	}
}

// NewControlString returns a generic control
func NewControlString(controlType string, criticality bool, controlValue string) *ControlString {
	return &ControlString{
		ControlType:  controlType,
		Criticality:  criticality,
		ControlValue: controlValue,
	}
}

// NewControlPaging returns a paging control
func NewControlPaging(pagingSize uint32) *ControlPaging {
	return &ControlPaging{PagingSize: pagingSize}
}

// NewControlBeheraPasswordPolicy returns a ControlBeheraPasswordPolicy
func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy {
	return &ControlBeheraPasswordPolicy{
		Expire: -1,
		Grace:  -1,
		Error:  -1,
	}
}

// ControlSubtreeDelete implements the subtree delete control described in
// https://datatracker.ietf.org/doc/html/draft-armijo-ldap-treedelete-02
type ControlSubtreeDelete struct{}

// GetControlType returns the OID
func (c *ControlSubtreeDelete) GetControlType() string {
	return ControlTypeSubtreeDelete
}

// NewControlSubtreeDelete returns a ControlSubtreeDelete control.
func NewControlSubtreeDelete() *ControlSubtreeDelete {
	return &ControlSubtreeDelete{}
}

// Encode returns the ber packet representation
func (c *ControlSubtreeDelete) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSubtreeDelete, "Control Type ("+ControlTypeMap[ControlTypeSubtreeDelete]+")"))

	return packet
}

func (c *ControlSubtreeDelete) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)",
		ControlTypeMap[ControlTypeSubtreeDelete],
		ControlTypeSubtreeDelete)
}

func encodeControls(controls []Control) *ber.Packet {
	packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls")
	for _, control := range controls {
		packet.AppendChild(control.Encode())
	}
	return packet
}

// ControlDirSync implements the control described in https://msdn.microsoft.com/en-us/library/aa366978(v=vs.85).aspx
type ControlDirSync struct {
	Criticality  bool
	Flags        int64
	MaxAttrCount int64
	Cookie       []byte
}

// @deprecated Use NewRequestControlDirSync instead
func NewControlDirSync(flags int64, maxAttrCount int64, cookie []byte) *ControlDirSync {
	return NewRequestControlDirSync(flags, maxAttrCount, cookie)
}

// NewRequestControlDirSync returns a dir sync control
func NewRequestControlDirSync(
	flags int64, maxAttrCount int64, cookie []byte,
) *ControlDirSync {
	return &ControlDirSync{
		Criticality:  true,
		Flags:        flags,
		MaxAttrCount: maxAttrCount,
		Cookie:       cookie,
	}
}

// NewResponseControlDirSync returns a dir sync control
func NewResponseControlDirSync(value *ber.Packet) (*ControlDirSync, error) {
	if value.Value != nil {
		valueChildren, err := ber.DecodePacketErr(value.Data.Bytes())
		if err != nil {
			return nil, fmt.Errorf("failed to decode data bytes: %s", err)
		}
		value.Data.Truncate(0)
		value.Value = nil
		value.AppendChild(valueChildren)
	}
	child := value.Children[0]
	if len(child.Children) != 3 { // also on initial creation, Cookie is an empty string
		return nil, fmt.Errorf("invalid number of children in dirSync control")
	}
	child.Description = "DirSync Control Value"
	child.Children[0].Description = "Flags"
	child.Children[1].Description = "MaxAttrCount"
	child.Children[2].Description = "Cookie"

	cookie := child.Children[2].Data.Bytes()
	child.Children[2].Value = cookie
	return &ControlDirSync{
		Criticality:  true,
		Flags:        child.Children[0].Value.(int64),
		MaxAttrCount: child.Children[1].Value.(int64),
		Cookie:       cookie,
	}, nil
}

// GetControlType returns the OID
func (c *ControlDirSync) GetControlType() string {
	return ControlTypeDirSync
}

// String returns a human-readable description
func (c *ControlDirSync) String() string {
	return fmt.Sprintf(
		"ControlType: %s (%q) Criticality: %t ControlValue: Flags: %d MaxAttrCount: %d",
		ControlTypeMap[ControlTypeDirSync],
		ControlTypeDirSync,
		c.Criticality,
		c.Flags,
		c.MaxAttrCount,
	)
}

// Encode returns the ber packet representation
func (c *ControlDirSync) Encode() *ber.Packet {
	cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Cookie")
	if len(c.Cookie) != 0 {
		cookie.Value = c.Cookie
		cookie.Data.Write(c.Cookie)
	}

	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeDirSync, "Control Type ("+ControlTypeMap[ControlTypeDirSync]+")"))
	packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) // must be true always

	val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (DirSync)")
	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "DirSync Control Value")
	seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.Flags), "Flags"))
	seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, int64(c.MaxAttrCount), "MaxAttrCount"))
	seq.AppendChild(cookie)
	val.AppendChild(seq)

	packet.AppendChild(val)
	return packet
}

// SetCookie stores the given cookie in the dirSync control
func (c *ControlDirSync) SetCookie(cookie []byte) {
	c.Cookie = cookie
}

// ControlServerSideSorting

type SortKey struct {
	Reverse       bool
	AttributeType string
	MatchingRule  string
}

type ControlServerSideSorting struct {
	SortKeys []*SortKey
}

func (c *ControlServerSideSorting) GetControlType() string {
	return ControlTypeServerSideSorting
}

func NewControlServerSideSorting(value *ber.Packet) (*ControlServerSideSorting, error) {
	sortKeys := []*SortKey{}

	val := value.Children[1].Children

	if len(val) != 1 {
		return nil, fmt.Errorf("no sequence value in packet")
	}

	sequences := val[0].Children

	for i, sequence := range sequences {
		sortKey := &SortKey{}

		if len(sequence.Children) < 2 {
			return nil, fmt.Errorf("attributeType or matchingRule is missing from sequence %d", i)
		}

		sortKey.AttributeType = sequence.Children[0].Value.(string)
		sortKey.MatchingRule = sequence.Children[1].Value.(string)

		if len(sequence.Children) == 3 {
			sortKey.Reverse = sequence.Children[2].Value.(bool)
		}

		sortKeys = append(sortKeys, sortKey)
	}

	return &ControlServerSideSorting{SortKeys: sortKeys}, nil
}

func NewControlServerSideSortingWithSortKeys(sortKeys []*SortKey) *ControlServerSideSorting {
	return &ControlServerSideSorting{SortKeys: sortKeys}
}

func (c *ControlServerSideSorting) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	control := ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.GetControlType(), "Control Type")

	value := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value")
	seqs := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortKeyList")

	for _, f := range c.SortKeys {
		seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "")

		seq.AppendChild(
			ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, f.AttributeType, "attributeType"),
		)
		seq.AppendChild(
			ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, f.MatchingRule, "orderingRule"),
		)
		if f.Reverse {
			seq.AppendChild(
				ber.NewBoolean(ber.ClassContext, ber.TypePrimitive, 1, f.Reverse, "reverseOrder"),
			)
		}

		seqs.AppendChild(seq)
	}

	value.AppendChild(seqs)

	packet.AppendChild(control)
	packet.AppendChild(value)

	return packet
}

func (c *ControlServerSideSorting) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality:%t %+v",
		"Server Side Sorting",
		c.GetControlType(),
		false,
		c.SortKeys,
	)
}

// ControlServerSideSortingResponse

const (
	ControlServerSideSortingCodeSuccess                  ControlServerSideSortingCode = 0
	ControlServerSideSortingCodeOperationsError          ControlServerSideSortingCode = 1
	ControlServerSideSortingCodeTimeLimitExceeded        ControlServerSideSortingCode = 2
	ControlServerSideSortingCodeStrongAuthRequired       ControlServerSideSortingCode = 8
	ControlServerSideSortingCodeAdminLimitExceeded       ControlServerSideSortingCode = 11
	ControlServerSideSortingCodeNoSuchAttribute          ControlServerSideSortingCode = 16
	ControlServerSideSortingCodeInappropriateMatching    ControlServerSideSortingCode = 18
	ControlServerSideSortingCodeInsufficientAccessRights ControlServerSideSortingCode = 50
	ControlServerSideSortingCodeBusy                     ControlServerSideSortingCode = 51
	ControlServerSideSortingCodeUnwillingToPerform       ControlServerSideSortingCode = 53
	ControlServerSideSortingCodeOther                    ControlServerSideSortingCode = 80
)

var ControlServerSideSortingCodes = []ControlServerSideSortingCode{
	ControlServerSideSortingCodeSuccess,
	ControlServerSideSortingCodeOperationsError,
	ControlServerSideSortingCodeTimeLimitExceeded,
	ControlServerSideSortingCodeStrongAuthRequired,
	ControlServerSideSortingCodeAdminLimitExceeded,
	ControlServerSideSortingCodeNoSuchAttribute,
	ControlServerSideSortingCodeInappropriateMatching,
	ControlServerSideSortingCodeInsufficientAccessRights,
	ControlServerSideSortingCodeBusy,
	ControlServerSideSortingCodeUnwillingToPerform,
	ControlServerSideSortingCodeOther,
}

type ControlServerSideSortingCode int64

// Valid test the code contained in the control against the ControlServerSideSortingCodes slice and return an error if the code is unknown.
func (c ControlServerSideSortingCode) Valid() error {
	for _, validRet := range ControlServerSideSortingCodes {
		if c == validRet {
			return nil
		}
	}
	return fmt.Errorf("unknown return code : %d", c)
}

func NewControlServerSideSortingResult(pkt *ber.Packet) (*ControlServerSideSortingResult, error) {
	control := &ControlServerSideSortingResult{}

	if pkt == nil || len(pkt.Children) == 0 {
		return nil, fmt.Errorf("bad packet")
	}

	codeInt, err := ber.ParseInt64(pkt.Children[0].Data.Bytes())
	if err != nil {
		return nil, err
	}

	code := ControlServerSideSortingCode(codeInt)
	if err := code.Valid(); err != nil {
		return nil, err
	}

	return control, nil
}

type ControlServerSideSortingResult struct {
	Criticality bool

	Result ControlServerSideSortingCode

	// Not populated for now. I can't get openldap to send me this value, so I think this is specific to other directory server
	// AttributeType string
}

func (control *ControlServerSideSortingResult) GetControlType() string {
	return ControlTypeServerSideSortingResult
}

func (c *ControlServerSideSortingResult) Encode() *ber.Packet {
	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "SortResult sequence")
	sortResult := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, int64(c.Result), "SortResult")
	packet.AppendChild(sortResult)

	return packet
}

func (c *ControlServerSideSortingResult) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q) Criticality:%t ResultCode:%+v",
		"Server Side Sorting Result",
		c.GetControlType(),
		c.Criticality,
		c.Result,
	)
}

// Mode for ControlTypeSyncRequest
type ControlSyncRequestMode int64

const (
	SyncRequestModeRefreshOnly       ControlSyncRequestMode = 1
	SyncRequestModeRefreshAndPersist ControlSyncRequestMode = 3
)

// ControlSyncRequest implements the Sync Request Control described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncRequest struct {
	Criticality bool
	Mode        ControlSyncRequestMode
	Cookie      []byte
	ReloadHint  bool
}

func NewControlSyncRequest(
	mode ControlSyncRequestMode, cookie []byte, reloadHint bool,
) *ControlSyncRequest {
	return &ControlSyncRequest{
		Criticality: true,
		Mode:        mode,
		Cookie:      cookie,
		ReloadHint:  reloadHint,
	}
}

// GetControlType returns the OID
func (c *ControlSyncRequest) GetControlType() string {
	return ControlTypeSyncRequest
}

// Encode encodes the control
func (c *ControlSyncRequest) Encode() *ber.Packet {
	_mode := int64(c.Mode)
	mode := ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, _mode, "Mode")
	var cookie *ber.Packet
	if len(c.Cookie) > 0 {
		cookie = ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie")
		cookie.Value = c.Cookie
		cookie.Data.Write(c.Cookie)
	}
	reloadHint := ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.ReloadHint, "Reload Hint")

	packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control")
	packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeSyncRequest, "Control Type ("+ControlTypeMap[ControlTypeSyncRequest]+")"))
	packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality"))

	val := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Sync Request)")
	seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Sync Request Value")
	seq.AppendChild(mode)
	if cookie != nil {
		seq.AppendChild(cookie)
	}
	seq.AppendChild(reloadHint)
	val.AppendChild(seq)

	packet.AppendChild(val)
	return packet
}

// String returns a human-readable description
func (c *ControlSyncRequest) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t Mode: %d Cookie: %s ReloadHint: %t",
		ControlTypeMap[ControlTypeSyncRequest],
		ControlTypeSyncRequest,
		c.Criticality,
		c.Mode,
		string(c.Cookie),
		c.ReloadHint,
	)
}

// State for ControlSyncState
type ControlSyncStateState int64

const (
	SyncStatePresent ControlSyncStateState = 0
	SyncStateAdd     ControlSyncStateState = 1
	SyncStateModify  ControlSyncStateState = 2
	SyncStateDelete  ControlSyncStateState = 3
)

// ControlSyncState implements the Sync State Control described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncState struct {
	Criticality bool
	State       ControlSyncStateState
	EntryUUID   uuid.UUID
	Cookie      []byte
}

func NewControlSyncState(pkt *ber.Packet) (*ControlSyncState, error) {
	var (
		state     ControlSyncStateState
		entryUUID uuid.UUID
		cookie    []byte
		err       error
	)
	switch len(pkt.Children) {
	case 0, 1:
		return nil, fmt.Errorf("at least two children are required: %d", len(pkt.Children))
	case 2:
		state = ControlSyncStateState(pkt.Children[0].Value.(int64))
		entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue)
		if err != nil {
			return nil, fmt.Errorf("failed to decode uuid: %w", err)
		}
	case 3:
		state = ControlSyncStateState(pkt.Children[0].Value.(int64))
		entryUUID, err = uuid.FromBytes(pkt.Children[1].ByteValue)
		if err != nil {
			return nil, fmt.Errorf("failed to decode uuid: %w", err)
		}
		cookie = pkt.Children[2].ByteValue
	}
	return &ControlSyncState{
		Criticality: false,
		State:       state,
		EntryUUID:   entryUUID,
		Cookie:      cookie,
	}, nil
}

// GetControlType returns the OID
func (c *ControlSyncState) GetControlType() string {
	return ControlTypeSyncState
}

// Encode encodes the control
func (c *ControlSyncState) Encode() *ber.Packet {
	return nil
}

// String returns a human-readable description
func (c *ControlSyncState) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t State: %d EntryUUID: %s Cookie: %s",
		ControlTypeMap[ControlTypeSyncState],
		ControlTypeSyncState,
		c.Criticality,
		c.State,
		c.EntryUUID.String(),
		string(c.Cookie),
	)
}

// ControlSyncDone implements the Sync Done Control described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncDone struct {
	Criticality    bool
	Cookie         []byte
	RefreshDeletes bool
}

func NewControlSyncDone(pkt *ber.Packet) (*ControlSyncDone, error) {
	var (
		cookie         []byte
		refreshDeletes bool
	)
	switch len(pkt.Children) {
	case 0:
		// have nothing to do
	case 1:
		cookie = pkt.Children[0].ByteValue
	case 2:
		cookie = pkt.Children[0].ByteValue
		refreshDeletes = pkt.Children[1].Value.(bool)
	}
	return &ControlSyncDone{
		Criticality:    false,
		Cookie:         cookie,
		RefreshDeletes: refreshDeletes,
	}, nil
}

// GetControlType returns the OID
func (c *ControlSyncDone) GetControlType() string {
	return ControlTypeSyncDone
}

// Encode encodes the control
func (c *ControlSyncDone) Encode() *ber.Packet {
	return nil
}

// String returns a human-readable description
func (c *ControlSyncDone) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t Cookie: %s RefreshDeletes: %t",
		ControlTypeMap[ControlTypeSyncDone],
		ControlTypeSyncDone,
		c.Criticality,
		string(c.Cookie),
		c.RefreshDeletes,
	)
}

// Tag For ControlSyncInfo
type ControlSyncInfoValue uint64

const (
	SyncInfoNewcookie      ControlSyncInfoValue = 0
	SyncInfoRefreshDelete  ControlSyncInfoValue = 1
	SyncInfoRefreshPresent ControlSyncInfoValue = 2
	SyncInfoSyncIdSet      ControlSyncInfoValue = 3
)

// ControlSyncInfoNewCookie implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncInfoNewCookie struct {
	Cookie []byte
}

// String returns a human-readable description
func (c *ControlSyncInfoNewCookie) String() string {
	return fmt.Sprintf(
		"NewCookie[Cookie: %s]",
		string(c.Cookie),
	)
}

// ControlSyncInfoRefreshDelete implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncInfoRefreshDelete struct {
	Cookie      []byte
	RefreshDone bool
}

// String returns a human-readable description
func (c *ControlSyncInfoRefreshDelete) String() string {
	return fmt.Sprintf(
		"RefreshDelete[Cookie: %s RefreshDone: %t]",
		string(c.Cookie),
		c.RefreshDone,
	)
}

// ControlSyncInfoRefreshPresent implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncInfoRefreshPresent struct {
	Cookie      []byte
	RefreshDone bool
}

// String returns a human-readable description
func (c *ControlSyncInfoRefreshPresent) String() string {
	return fmt.Sprintf(
		"RefreshPresent[Cookie: %s RefreshDone: %t]",
		string(c.Cookie),
		c.RefreshDone,
	)
}

// ControlSyncInfoSyncIdSet implements a part of syncInfoValue described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncInfoSyncIdSet struct {
	Cookie         []byte
	RefreshDeletes bool
	SyncUUIDs      []uuid.UUID
}

// String returns a human-readable description
func (c *ControlSyncInfoSyncIdSet) String() string {
	return fmt.Sprintf(
		"SyncIdSet[Cookie: %s RefreshDeletes: %t SyncUUIDs: %v]",
		string(c.Cookie),
		c.RefreshDeletes,
		c.SyncUUIDs,
	)
}

// ControlSyncInfo implements the Sync Info Control described in https://www.ietf.org/rfc/rfc4533.txt
type ControlSyncInfo struct {
	Criticality    bool
	Value          ControlSyncInfoValue
	NewCookie      *ControlSyncInfoNewCookie
	RefreshDelete  *ControlSyncInfoRefreshDelete
	RefreshPresent *ControlSyncInfoRefreshPresent
	SyncIdSet      *ControlSyncInfoSyncIdSet
}

func NewControlSyncInfo(pkt *ber.Packet) (*ControlSyncInfo, error) {
	var (
		cookie         []byte
		refreshDone    = true
		refreshDeletes bool
		syncUUIDs      []uuid.UUID
	)
	c := &ControlSyncInfo{Criticality: false}
	switch ControlSyncInfoValue(pkt.Identifier.Tag) {
	case SyncInfoNewcookie:
		c.Value = SyncInfoNewcookie
		c.NewCookie = &ControlSyncInfoNewCookie{
			Cookie: pkt.ByteValue,
		}
	case SyncInfoRefreshDelete:
		c.Value = SyncInfoRefreshDelete
		switch len(pkt.Children) {
		case 0:
			// have nothing to do
		case 1:
			cookie = pkt.Children[0].ByteValue
		case 2:
			cookie = pkt.Children[0].ByteValue
			refreshDone = pkt.Children[1].Value.(bool)
		}
		c.RefreshDelete = &ControlSyncInfoRefreshDelete{
			Cookie:      cookie,
			RefreshDone: refreshDone,
		}
	case SyncInfoRefreshPresent:
		c.Value = SyncInfoRefreshPresent
		switch len(pkt.Children) {
		case 0:
			// have nothing to do
		case 1:
			cookie = pkt.Children[0].ByteValue
		case 2:
			cookie = pkt.Children[0].ByteValue
			refreshDone = pkt.Children[1].Value.(bool)
		}
		c.RefreshPresent = &ControlSyncInfoRefreshPresent{
			Cookie:      cookie,
			RefreshDone: refreshDone,
		}
	case SyncInfoSyncIdSet:
		c.Value = SyncInfoSyncIdSet
		switch len(pkt.Children) {
		case 0:
			// have nothing to do
		case 1:
			cookie = pkt.Children[0].ByteValue
		case 2:
			cookie = pkt.Children[0].ByteValue
			refreshDeletes = pkt.Children[1].Value.(bool)
		case 3:
			cookie = pkt.Children[0].ByteValue
			refreshDeletes = pkt.Children[1].Value.(bool)
			syncUUIDs = make([]uuid.UUID, 0, len(pkt.Children[2].Children))
			for _, child := range pkt.Children[2].Children {
				u, err := uuid.FromBytes(child.ByteValue)
				if err != nil {
					return nil, fmt.Errorf("failed to decode uuid: %w", err)
				}
				syncUUIDs = append(syncUUIDs, u)
			}
		}
		c.SyncIdSet = &ControlSyncInfoSyncIdSet{
			Cookie:         cookie,
			RefreshDeletes: refreshDeletes,
			SyncUUIDs:      syncUUIDs,
		}
	default:
		return nil, fmt.Errorf("unknown sync info value: %d", pkt.Identifier.Tag)
	}
	return c, nil
}

// GetControlType returns the OID
func (c *ControlSyncInfo) GetControlType() string {
	return ControlTypeSyncInfo
}

// Encode encodes the control
func (c *ControlSyncInfo) Encode() *ber.Packet {
	return nil
}

// String returns a human-readable description
func (c *ControlSyncInfo) String() string {
	return fmt.Sprintf(
		"Control Type: %s (%q)  Criticality: %t Value: %d %s %s %s %s",
		ControlTypeMap[ControlTypeSyncInfo],
		ControlTypeSyncInfo,
		c.Criticality,
		c.Value,
		c.NewCookie,
		c.RefreshDelete,
		c.RefreshPresent,
		c.SyncIdSet,
	)
}
