// package ntlm implements the NTLM client security service as described in
// MS-NLMP (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp).
//
// This package also contains client-side GSSAPI bindings (InitSecurityContext, Wrap, Unwrap and so on).
package ntlm

import (
	"bytes"
	"context"
	"errors"
	"time"

	"github.com/oiweiwei/go-msrpc/ssp/credential"
	"github.com/oiweiwei/go-msrpc/ssp/ntlm/internal"

	"github.com/oiweiwei/go-msrpc/text/encoding/utf16le"
)

var (
	ErrInvalidNTLMSignature = errors.New("invalid ntlmssp signature, expected 'NTLMSSP'")
	ErrInvalidMessageType   = errors.New("unexpected message type")
)

// Use password credential.
type Credential = credential.Credential

// The NTLM authentification version.
type NTLMVersion interface {
	// WithConfig function adds the configuration to the NTLM version.
	WithConfig(context.Context, *Config) NTLMVersion
	// LMOWF function is a NT LAN Manager (LM) one-way function used to create
	// a hash based on the user's password.
	LMOWF(context.Context, Credential) ([]byte, error)
	// NTOWF function is a NT LAN Manager (NT) one-way function used to create
	// a hash based on user's password.
	NTOWF(context.Context, Credential) ([]byte, error)
	// ChallengeResponse function computes the response for the server challenge message
	// based on client challenge `nonce` parameter and set of negotiated flags.
	ChallengeResponse(context.Context, Credential, *ChallengeMessage, []byte) (*ChallengeResponse, error)
	// KeyExchangeKey function returns the key used to protect the session key that is
	// generated by the client.
	KeyExchangeKey(context.Context, *ChallengeMessage, *ChallengeResponse) ([]byte, error)
}

// The computed challenge response.
type ChallengeResponse struct {
	// NT challenge response.
	NT []byte
	// LM challenge response.
	LM []byte
	// Tmp.
	Tmp []byte
	// The session base key.
	SessionBaseKey []byte
	// The NT one-way-function.
	KeyNT []byte
	// The LM one-way-function.
	KeyLM []byte
	// The key exchange key.
	KeyExchangeKey []byte
	// If 'true', username and password are empty.
	// (anonymous session).
	IsAnonymous bool
	// RequestMIC.
	RequestMIC bool
}

// The NTLM message type.
type MessageType uint32

const (
	// None.
	MessageTypeNone MessageType = 0x00000000
	// Negotiate Message.
	MessageTypeNegotiate MessageType = 0x00000001
	// Challenge Message.
	MessageTypeChallenge MessageType = 0x00000002
	// Authenticate Message.
	MessageTypeAuthenticate MessageType = 0x00000003
)

// The negotiation flag.
type Flag uint32

// IsSet returns `true` if flag is set.
func (f Flag) IsSet(ff Flag) bool {
	return f&ff != 0
}

// Set function sets the provided flag.
func (f Flag) Set(ff Flag) Flag {
	return f | ff
}

// Unset function unsets the provided flag.
func (f Flag) Unset(ff Flag) Flag {
	return f & ^ff
}

const (
	// If set, requests 56-bit encryption. If the client sends NegotiateSeal
	// or NegotiateSign with Negotiate56 to the server in the
	// NegotiateMessage, the server MUST return Negotiate56 to the client
	// in the ChallengeMessage. Otherwise it is ignored. If both Negotiate56
	// and Negotiate128 are requested and supported by the client and server,
	// Negotiate56 and Negotiate128 will both be returned to the client.
	// Clients and servers that set NegotiateSeal SHOULD set Negotiate56
	// if it is supported.
	Negotiate56 Flag = 1 << (31 - 0)
	// If set, requests an explicit key exchange. This capability SHOULD be used because
	// it improves security for message integrity or confidentiality.
	NegotiateKeyExchange Flag = 1 << (31 - 1)
	// If set, requests 128-bit session key negotiation. If the client sends Negotiate128
	// to the server in the NegotiateMessage, the server MUST return Negotiate128
	// to the client in the ChallengeMessage only if the client sets NegotiateSeal
	// or NegotiateSign. Otherwise it is ignored. If both Negotiate56 and
	// Negotiate128 are requested and supported by the client and server,
	// Negotiate56 and Negotiate128 will both be returned to the client.
	// Clients and servers that set NegotiateSeal SHOULD set Negotiate128
	// if it is supported.
	Negotiate128 Flag = 1 << (31 - 2)
	// This bit is unused and MUST be zero.
	R1 Flag = 1 << (31 - 3)
	// This bit is unused and MUST be zero.
	R2 Flag = 1 << (31 - 4)
	// This bit is unused and MUST be zero.
	R3 Flag = 1 << (31 - 5)
	// If set, requests the protocol version number. The data corresponding to this flag is
	// provided in the Version field of the NegotiateMessage, the ChallengeMessage, and
	// the AuthenticateMessage.
	NegotiateVersion Flag = 1 << (31 - 6)
	// This bit is unused and MUST be zero.
	R4 Flag = 1 << (31 - 7)
	// If set, indicates that the TargetInfo fields in the ChallengeMessage
	// are populated.
	NegotiateTargetInfo Flag = 1 << (31 - 8)
	// If set, requests the usage of the LMOWF.
	RequestNonNTSessionKey Flag = 1 << (31 - 9)
	// This bit is unused and MUST be zero.
	R5 Flag = 1 << (31 - 10)
	// If set, requests an identify level token.
	NegotiateIdentify Flag = 1 << (31 - 11)
	// If set, requests usage of the NTLM v2 session security. NTLM v2 session security
	// is a misnomer because it is not NTLM v2. It is NTLM v1 using the extended session
	// security that is also in NTLM v2. NegotiateLMKey and
	// NegotiateExtendedSessionSecurity are mutually exclusive. If both
	// NegotiateExtendedSessionSecurity and NegotiateLMKey are
	// requested, NegotiateExtendedSessionSecurity alone MUST be returned
	// to the client. NTLM v2 authentication session key generation MUST be supported
	// by both the client and the DC in order to be used, and extended session security
	// signing and sealing requires support from the client and the server in order
	// to be used.
	NegotiateExtendedSessionSecurity Flag = 1 << (31 - 12)
	// This bit is unused and MUST be zero.
	R6 Flag = 1 << (31 - 13)
	// If set, TargetName MUST be a server name. The data corresponding to this flag
	// is provided by the server in the TargetName field of the ChallengeMessage.
	// If this bit is set, then TARGET_TYPE_DOMAIN MUST NOT be set.
	// This flag MUST be ignored in the NegotiateMessage and the AuthenticateMessage.
	TargetTypeServer Flag = 1 << (31 - 14)
	// If set, TargetName MUST be a domain name. The data corresponding to this flag is
	// provided by the server in the TargetName field of the ChallengeMessage. If set,
	// then TARGET_TYPE_SERVER MUST NOT be set. This flag MUST be ignored in
	// the NegotiateMessage and the AuthenticateMessage.
	TargetTypeDomain Flag = 1 << (31 - 15)
	// If set, a session key is generated regardless of the states of NegotiateSign
	// and NegotiateSeal. A session key MUST always exist to generate the MIC in
	// the authenticate message. NegotiateAlwaysSign MUST be set in the
	// NegotiateMessage to the server and the ChallengeMessage to the client.
	// NegotiateAlwaysSign is overridden by NegotiateSign and
	// NegotiateSeal, if they are supported.
	NegotiateAlwaysSign Flag = 1 << (31 - 16)
	// This bit is unused and MUST be zero.
	R7 Flag = 1 << (31 - 17)
	// This flag indicates whether the Workstation field is present.
	// If this flag is not set, the Workstation field MUST be ignored.
	// If this flag is set, the length of the Workstation field specifies
	// whether the workstation name is nonempty or not.
	NegotiateOEMWorkstationSupplied Flag = 1 << (31 - 18)
	// If set, the domain name is provided.
	NegotiateOEMDomainSupplied Flag = 1 << (31 - 19)
	// If set, the connection SHOULD be anonymous.
	Anonymous Flag = 1 << (31 - 20)
	// This bit is unused and MUST be zero.
	R8 Flag = 1 << (31 - 21)
	// If set, requests usage of the NTLM v1 session security protocol.
	// NegotiateNTLM MUST be set in the NegotiateMessage to
	// the server and the ChallengeMessage to the client.
	NegotiateNTLM Flag = 1 << (31 - 22)
	// This bit is unused and MUST be zero.
	R9 Flag = 1 << (31 - 23)
	// If set, requests LAN Manager (LM) session key computation.
	// NegotiateLMKey and NegotiateExtendedSessionSecurity
	// are mutually exclusive. If both NegotiateLMKey and
	// NegotiateExtendedSessionSecurity are requested,
	// NegotiateExtendedSessionSecurity alone MUST be returned
	// to the client. NTLM v2 authentication session key generation MUST be
	// supported by both the client and the DC in order to be used, and extended
	// session security signing and sealing requires support from the client and
	// the server to be used.
	NegotiateLMKey Flag = 1 << (31 - 24)
	// If set, requests connectionless authentication. If NegotiateDatagram
	// is set, then NegotiateKeyExchange MUST always be set in the
	// AuthenticateMessage to the server and the ChallengeMessage to the client.
	NegotiateDatagram Flag = 1 << (31 - 25)
	// If set, requests session key negotiation for message confidentiality.
	// If the client sends NegotiateSeal to the server in the
	// NegotiateMessage, the server MUST return NegotiateSeal to the
	// client in the ChallengeMessage. Clients and servers that set
	// NegotiateSeal SHOULD always set Negotiate56 and
	// Negotiate128, if they are supported.
	NegotiateSeal Flag = 1 << (31 - 26)
	// If set, requests session key negotiation for message signatures.
	// If the client sends NegotiateSign to the server in the
	// NegotiateMessage, the server MUST return NegotiateSign
	// to the client in the ChallengeMessage.
	NegotiateSign Flag = 1 << (31 - 27)
	// This bit is unused and MUST be zero.
	R10 Flag = 1 << (31 - 28)
	// If set, a TargetName field of the ChallengeMessage MUST be supplied.
	RequestTarget Flag = 1 << (31 - 29)
	// If set, requests OEM character set encoding.
	NegotiateOEM Flag = 1 << (31 - 30)
	// If set, requests Unicode character set encoding.
	NegotiateUnicode Flag = 1 << (31 - 31)
)

var (
	// The default NTLM signature.
	NTLMSignature = []byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}
)

// The NegotiateMessage defines an NTLM negotiate message that is sent from
// the client to the server. This message allows the client to specify its
// supported NTLM options to the server.
type NegotiateMessage struct {
	// An 8-byte character array that MUST contain the ASCII
	// string ('N', 'T', 'L', 'M', 'S', 'S', 'P', '\0').
	Signature []byte
	// A 32-bit unsigned integer that indicates the message type.
	// This field MUST be set to 0x00000001.
	MessageType MessageType
	// A NEGOTIATE structure that contains a set of flags.
	// The client sets flags to indicate options it supports.
	Negotiate Flag
	// A 16-bit unsigned integer that defines the size, in bytes,
	// of DomainName in the Payload.
	DomainNameLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// DomainNameLen, and MUST be ignored on receipt.
	DomainNameMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the NegotiateMessage to DomainName in
	// Payload.
	DomainNameOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes,
	// of WorkStationName in the Payload.
	WorkstationLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// WorkstationLen and MUST be ignored on receipt.
	WorkstationMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes, from
	// the beginning of the NegotiateMessage to WorkstationName in the
	// Payload.
	WorkstationOffset uint32
	// A Version structure that is populated only when the NegotiateVersion
	// flag is set in the NegotiateFlags field; otherwise, it MUST be set to
	// all zero. This structure SHOULD be used for debugging purposes only.
	// In normal (nondebugging) protocol messages, it is ignored and does not
	// affect the NTLM message processing.
	Version Version
	// If DomainNameLen does not equal 0x0000, DomainName MUST be a byte-array
	// that contains the name of the client authentication domain that MUST be
	// encoded using the OEM character set. Otherwise, this data is not present.
	DomainName string
	// If WorkstationLen does not equal 0x0000, WorkstationName MUST be a byte
	// array that contains the name of the client machine that MUST be encoded
	// using the OEM character set. Otherwise, this data is not present.
	Workstation string
}

// Marshal function marshals the NegotiateMessage.
func (m *NegotiateMessage) Marshal(ctx context.Context) ([]byte, error) {

	e := internal.NewCodec(ctx, nil)

	// signature.
	if m.Signature == nil {
		m.Signature = NTLMSignature
	}
	e.WriteBytes(ctx, m.Signature, 8)
	// message_type.
	if m.MessageType == MessageTypeNone {
		m.MessageType = MessageTypeNegotiate
	}
	e.WriteData(ctx, (uint32)(m.MessageType))
	// negotiate.
	e.WriteData(ctx, (uint32)(m.Negotiate))
	// domain_name.
	e.WritePayload(ctx, internal.PayloadField(
		&m.DomainNameLen,
		&m.DomainNameMaxLen,
		&m.DomainNameOffset,
		m.DomainName,
	))
	// workstation.
	e.WritePayload(ctx, internal.PayloadField(
		&m.WorkstationLen,
		&m.WorkstationMaxLen,
		&m.WorkstationOffset,
		m.Workstation,
	))
	// version.
	if m.Negotiate.IsSet(NegotiateVersion) {
		if m.Version.IsZero() {
			m.Version = DefaultVersion
		}
		e.WriteData(ctx, m.Version)
	} else {
		e.WriteData(ctx, Version{})
	}

	return e.Bytes(ctx)
}

// Unmarshal function unmarshals the NegotiateMessage.
func (m *NegotiateMessage) Unmarshal(ctx context.Context, b []byte) error {

	e := internal.NewCodec(ctx, b)

	// signature.
	e.ReadBytes(ctx, &m.Signature, 8)
	if !bytes.Equal(m.Signature, NTLMSignature) {
		return ErrInvalidNTLMSignature
	}
	// message_type.
	e.ReadData(ctx, (*uint32)(&m.MessageType))
	if m.MessageType != MessageTypeNegotiate {
		return ErrInvalidMessageType
	}
	// negotiate.
	e.ReadData(ctx, (*uint32)(&m.Negotiate))
	// domain_name.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.DomainNameLen,
		&m.DomainNameMaxLen,
		&m.DomainNameOffset,
		&m.DomainName,
	))
	// workstation.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.WorkstationLen,
		&m.WorkstationMaxLen,
		&m.WorkstationOffset,
		&m.Workstation,
	))
	// version.
	e.ReadData(ctx, &m.Version)

	return e.ReadAll(ctx)
}

// The ChallengeMessage defines an NTLM challenge message that is sent from
// the server to the client. The ChallengeMessage is used by the server to
// challenge the client to prove its identity. For connection-oriented requests,
// the ChallengeMessage generated by the server is in response to the
// NegotiateMessage from the client.
type ChallengeMessage struct {
	// An 8-byte character array that MUST contain the ASCII
	// string ('N', 'T', 'L', 'M', 'S', 'S', 'P', '\0').
	Signature []byte
	// A 32-bit unsigned integer that indicates the message type.
	// This field MUST be set to 0x00000002.
	MessageType MessageType
	// A 16-bit unsigned integer that defines the size, in bytes,
	// of TargetInfo in Payload.
	TargetNameLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value
	// of TargetInfoLen and MUST be ignored on receipt.
	TargetNameMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the CHALLENGE_MESSAGE to TargetInfo in
	// Payload.
	TargetNameOffset uint32
	// A set of flags. The server sets flags to indicate options it
	// supports or, if there has been a NegotiateMessage, the choices
	// it has made from the options offered by the client.
	Negotiate Flag
	// A 64-bit value that contains the NTLM challenge. The challenge is a
	// 64-bit nonce.
	ServerChallenge []byte
	// An 8-byte array whose elements MUST be zero when sent and MUST be
	// ignored on receipt.
	_ [8]byte
	// A 16-bit unsigned integer that defines the size, in bytes, of
	// TargetInfo in Payload.
	TargetInfoLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// TargetInfoLen and MUST be ignored on receipt.
	TargetInfoMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the ChallengeMessage to TargetInfo in
	// Payload.
	TargetInfoOffset uint32
	// A VERSION structure that is populated only when the NegotiateVersion
	// flag is set in the NegotiateFlags field; otherwise, it MUST be set to
	// all zero. This structure SHOULD be used for debugging purposes only.
	// In normal (nondebugging) protocol messages, it is ignored and does not
	// affect the NTLM message processing.
	Version Version
	// If TargetNameLen does not equal 0x0000, TargetName MUST be a byte array
	// that contains the name of the server authentication realm, and MUST be
	// expressed in the negotiated character set. A server that is a member of
	// a domain returns the domain of which it is a member, and a server that
	// is not a member of a domain returns the server name.
	TargetName string
	// If TargetInfoLen does not equal 0x0000, TargetInfo MUST be a byte array
	// that contains a sequence of AV_PAIR structures.
	// The length of each AV_PAIR is determined by its AvLen field (plus 4 bytes).
	TargetInfo AttrValues
}

// Marshal function marshals the ChallengeMessage.
func (m *ChallengeMessage) Marshal(ctx context.Context) ([]byte, error) {

	e := internal.NewCodec(ctx, nil)

	// string_encoding.
	if m.Negotiate.IsSet(NegotiateUnicode) {
		e = e.WithStringCodec(ctx, utf16le.NewEncoding())
	}
	// signature.
	if m.Signature == nil {
		m.Signature = NTLMSignature
	}
	e.WriteBytes(ctx, m.Signature, 8)
	// message_type.
	if m.MessageType == MessageTypeNone {
		m.MessageType = MessageTypeChallenge
	}
	e.WriteData(ctx, (uint32)(m.MessageType))
	// target_name.
	e.WritePayload(ctx, internal.PayloadField(
		&m.TargetNameLen,
		&m.TargetNameMaxLen,
		&m.TargetNameOffset,
		m.TargetName,
	))
	// negotiate.
	e.WriteData(ctx, (uint32)(m.Negotiate))
	// server_challenge.
	e.WriteBytes(ctx, m.ServerChallenge, 8)
	// pad.
	e.WriteBytes(ctx, nil, 8)
	// target_info.
	e.WritePayload(ctx, internal.PayloadField(
		&m.TargetInfoLen,
		&m.TargetInfoMaxLen,
		&m.TargetInfoOffset,
		&m.TargetInfo,
	))
	// version.
	if m.Negotiate.IsSet(NegotiateVersion) {
		if m.Version.IsZero() {
			m.Version = DefaultVersion
		}
		e.WriteData(ctx, m.Version)
	} else {
		e.WriteData(ctx, Version{})
	}

	return e.Bytes(ctx)
}

// Unmarshal function unmarshals the ChallengeMessage.
func (m *ChallengeMessage) Unmarshal(ctx context.Context, b []byte) error {

	e := internal.NewCodec(ctx, b)

	// signature.
	e.ReadBytes(ctx, &m.Signature, 8)
	if !bytes.Equal(m.Signature, NTLMSignature) {
		return ErrInvalidNTLMSignature
	}
	// message_type.
	e.ReadData(ctx, (*uint32)(&m.MessageType))
	if m.MessageType != MessageTypeChallenge {
		return ErrInvalidMessageType
	}
	// target_name.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.TargetNameLen,
		&m.TargetNameMaxLen,
		&m.TargetNameOffset,
		&m.TargetName,
	))
	// negotiate.
	e.ReadData(ctx, (*uint32)(&m.Negotiate))
	// string_encoding.
	if m.Negotiate.IsSet(NegotiateUnicode) {
		e = e.WithStringCodec(ctx, utf16le.NewEncoding())
	}
	// server_challenge.
	e.ReadBytes(ctx, &m.ServerChallenge, 8)
	// pad.
	e.ReadBytes(ctx, nil, 8)
	// target_info.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.TargetInfoLen,
		&m.TargetInfoMaxLen,
		&m.TargetInfoOffset,
		&m.TargetInfo,
	))
	// version.
	e.ReadData(ctx, &m.Version)

	return e.ReadAll(ctx)
}

// The AuthenticateMessage defines an NTLM authenticate message that is sent
// from the client to the server after the ChallengeMessage is processed by
// the client.
type AuthenticateMessage struct {
	// An 8-byte character array that MUST contain the ASCII
	// string ('N', 'T', 'L', 'M', 'S', 'S', 'P', '\0').
	Signature []byte
	// A 32-bit unsigned integer that indicates the message type.
	// This field MUST be set to 0x00000003.
	MessageType MessageType
	// A 16-bit unsigned integer that defines the size, in bytes,
	// of LmChallengeResponse in Payload.
	LMChallengeResponseLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// LmChallengeResponseLen and MUST be ignored on receipt.
	LMChallengeResponseMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the AuthenticateMessage to LmChallengeResponse
	// in Payload.
	LMChallengeResponseOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes, of
	// NtChallengeResponse in Payload.
	NTChallengeResponseLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// NtChallengeResponseLen and MUST be ignored on receipt.
	NTChallengeResponseMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the AuthenticateMessage to NtChallengeResponse
	// in Payload.
	NTChallengeResponseOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes, of DomainName
	// in Payload.
	DomainNameLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of DomainNameLen
	// and MUST be ignored on receipt.
	DomainNameMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes, from the
	// beginning of the AuthenticateMessage to DomainName in Payload. If DomainName
	// is a Unicode string, the values of DomainNameBufferOffset and DomainNameLen
	// MUST be multiples of 2.
	DomainNameOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes, of UserName in
	// Payload, not including a NULL terminator.
	UserNameLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of UserNameLen
	// and MUST be ignored on receipt.
	UserNameMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes, from the
	// beginning of the AuthenticateMessage to UserName in Payload. If the UserName
	// to be sent contains a Unicode string, the values of UserNameBufferOffset
	// and UserNameLen MUST be multiples of 2.
	UserNameOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes, of Workstation
	// in Payload.
	WorkstationLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of WorkstationLen
	// and MUST be ignored on receipt.
	WorkstationMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes, from the beginning
	// of the AuthenticateMessage to Workstation in Payload. If Workstation contains
	// a Unicode string, the values of WorkstationBufferOffset and WorkstationLen
	// MUST be multiples of 2.
	WorkstationOffset uint32
	// A 16-bit unsigned integer that defines the size, in bytes,
	// of EncryptedRandomSessionKey in Payload.
	EncryptedRandomSessionKeyLen uint16
	// A 16-bit unsigned integer that SHOULD be set to the value of
	// EncryptedRandomSessionKeyLen and MUST be ignored on receipt.
	EncryptedRandomSessionKeyMaxLen uint16
	// A 32-bit unsigned integer that defines the offset, in bytes,
	// from the beginning of the AuthenticateMessage to EncryptedRandomSessionKey
	// in Payload.
	EncryptedRandomSessionKeyOffset uint32
	// In connectionless mode, a NEGOTIATE structure that contains a set of
	// flags and represents the conclusion of negotiation—the choices the
	// client has made from the options the server offered in the ChallengeMessage.
	// In connection-oriented mode, a NEGOTIATE structure that contains the set of
	// bit flags negotiated in the previous messages.
	Negotiate Flag
	// A Version structure that is populated only when the NegotiateVersion
	// flag is set in the NegotiateFlags field; otherwise, it MUST be set to
	// all zero. This structure SHOULD be used for debugging purposes only.
	// In normal (nondebugging) protocol messages, it is ignored and does not
	// affect the NTLM message processing.
	Version Version
	// The message integrity for the NTLM NegotiateMessage, ChallengeMessage, and
	// AuthenticateMessage.
	MIC []byte
	// An LMResponse structure or an LMv2Response structure that contains
	// the computed LM response to the challenge. If NTLM v2 authentication is
	// configured, then LmChallengeResponse MUST be an LMv2Response structure.
	// Otherwise, it MUST be an LMResponse structure.
	LMChallengeResponse []byte
	// An NTLMResponse structure or NTLMv2Response structure
	// that contains the computed NT response to the challenge. If NTLM v2
	// authentication is configured, NtChallengeResponse MUST be an NTLMv2Response.
	// Otherwise, it MUST be an NTLMResponse.
	NTChallengeResponse []byte
	// The domain or computer name hosting the user account. DomainName MUST
	// be encoded in the negotiated character set.
	DomainName string
	// The name of the user to be authenticated. UserName MUST be encoded in the
	// negotiated character set.
	UserName string
	// The name of the computer to which the user is logged on. Workstation MUST
	// be encoded in the negotiated character set.
	Workstation string
	// The client's encrypted random session key.
	EncryptedRandomSessionKey []byte
}

// Marshal function marshals the AuthenticateMessage.
func (m *AuthenticateMessage) Marshal(ctx context.Context) ([]byte, error) {

	e := internal.NewCodec(ctx, nil)

	// string_encoding.
	if m.Negotiate.IsSet(NegotiateUnicode) {
		e = e.WithStringCodec(ctx, utf16le.NewEncoding())
	}
	// signature.
	if m.Signature == nil {
		m.Signature = NTLMSignature
	}
	e.WriteBytes(ctx, m.Signature, 8)
	// message_type.
	if m.MessageType == MessageTypeNone {
		m.MessageType = MessageTypeAuthenticate
	}
	e.WriteData(ctx, (uint32)(m.MessageType))
	// lm_challenge_response.
	e.WritePayload(ctx, internal.PayloadField(
		&m.LMChallengeResponseLen,
		&m.LMChallengeResponseMaxLen,
		&m.LMChallengeResponseOffset,
		m.LMChallengeResponse,
	))
	// nt_challenge_response.
	if m.NTChallengeResponse != nil {
		e.WritePayload(ctx, internal.PayloadField(
			&m.NTChallengeResponseLen,
			&m.NTChallengeResponseMaxLen,
			&m.NTChallengeResponseOffset,
			m.NTChallengeResponse,
		))
	} else {
		// write zero-offset.
		e.WriteBytes(ctx, nil, 8)
	}
	// domain_name.
	e.WritePayload(ctx, internal.PayloadField(
		&m.DomainNameLen,
		&m.DomainNameMaxLen,
		&m.DomainNameOffset,
		m.DomainName,
	))
	// user_name.
	e.WritePayload(ctx, internal.PayloadField(
		&m.UserNameLen,
		&m.UserNameMaxLen,
		&m.UserNameOffset,
		m.UserName,
	))
	// workstation.
	e.WritePayload(ctx, internal.PayloadField(
		&m.WorkstationLen,
		&m.WorkstationMaxLen,
		&m.WorkstationOffset,
		m.Workstation,
	))
	// encrypted_random_session_key.
	e.WritePayload(ctx, internal.PayloadField(
		&m.EncryptedRandomSessionKeyLen,
		&m.EncryptedRandomSessionKeyMaxLen,
		&m.EncryptedRandomSessionKeyOffset,
		m.EncryptedRandomSessionKey,
	))
	// negotitate.
	e.WriteData(ctx, (uint32)(m.Negotiate))
	// version.
	if m.Negotiate.IsSet(NegotiateVersion) {
		if m.Version.IsZero() {
			m.Version = DefaultVersion
		}
		e.WriteData(ctx, m.Version)
	} else {
		e.WriteData(ctx, Version{})
	}
	// mic. (if present must be of length 16).
	if m.MIC != nil {
		e.WriteBytes(ctx, m.MIC, 16)
	}

	return e.Bytes(ctx)
}

// Unmarshal function unmarshals the AuthenticateMessage.
func (m *AuthenticateMessage) Unmarshal(ctx context.Context, b []byte) error {

	e := internal.NewCodec(ctx, b)

	// signature.
	e.ReadBytes(ctx, &m.Signature, 8)
	if !bytes.Equal(m.Signature, NTLMSignature) {
		return ErrInvalidNTLMSignature
	}
	// message_type.
	e.ReadData(ctx, (*uint32)(&m.MessageType))
	if m.MessageType != MessageTypeAuthenticate {
		return ErrInvalidMessageType
	}
	// lm_challenge_response.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.LMChallengeResponseLen,
		&m.LMChallengeResponseMaxLen,
		&m.LMChallengeResponseOffset,
		&m.LMChallengeResponse,
	))
	// nt_challenge_response.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.NTChallengeResponseLen,
		&m.NTChallengeResponseMaxLen,
		&m.NTChallengeResponseOffset,
		&m.NTChallengeResponse,
	))
	// domain_name.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.DomainNameLen,
		&m.DomainNameMaxLen,
		&m.DomainNameOffset,
		&m.DomainName,
	))
	// user_name.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.UserNameLen,
		&m.UserNameMaxLen,
		&m.UserNameOffset,
		&m.UserName,
	))
	// workstation.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.WorkstationLen,
		&m.WorkstationMaxLen,
		&m.WorkstationOffset,
		&m.Workstation,
	))
	// encrypted_random_session_key.
	e.ReadPayload(ctx, internal.PayloadField(
		&m.EncryptedRandomSessionKeyLen,
		&m.EncryptedRandomSessionKeyMaxLen,
		&m.EncryptedRandomSessionKeyOffset,
		&m.EncryptedRandomSessionKey,
	))
	// negotitate.
	e.ReadData(ctx, (*uint32)(&m.Negotiate))
	// string_encoding.
	if m.Negotiate.IsSet(NegotiateUnicode) {
		e = e.WithStringCodec(ctx, utf16le.NewEncoding())
	}
	// version.
	e.ReadData(ctx, &m.Version)

	// mic. (decode nt_challenge_response av_pairs).
	if m.NTChallengeResponseLen > 24 { // at position 24 av_pairs start.
		attrs := AttrValues{}
		if err := (&attrs).Unmarshal(ctx, b[m.NTChallengeResponseOffset+24:]); err != nil {
			return err
		}
		if attrs.Flag().IsSet(MICProvided) {
			e.ReadBytes(ctx, &m.MIC, 16)
		}
	}

	return e.ReadAll(ctx)
}

type Filetime uint64

// TimeToFiletime function converts the time.Time to filetime.
func TimeToFiletime(t time.Time) Filetime {
	if t.IsZero() {
		return Filetime(0)
	}
	return Filetime(t.UnixNano()/100 + 116444736000000000)
}

// AsTime function returns the time.Time.
func (ft Filetime) AsTime() time.Time {
	if ft == 0 {
		return time.Time{}
	}
	// 100-nanosecond intervals since January 1, 1601
	nsec := int64(ft)
	// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
	nsec -= 116444736000000000
	// convert into nanoseconds
	// nsec *= 100
	return time.Unix(nsec/10000000, nsec%10000000)
}
