package ims

import (
	"bufio"
	"context"
	"crypto/rand"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"net"
	"sort"
	"strconv"
	"strings"
)

type SecurityMode string

const (
	// SecurityRequired is the production default. A 401 response without a
	// supported ipsec-3gpp Security-Server offer fails closed.
	SecurityRequired SecurityMode = "required"
	// SecurityOptional advertises ipsec-3gpp but permits a carrier that
	// explicitly omits Security-Server to continue on the tunnel in plain SIP.
	SecurityOptional SecurityMode = "optional"
	// SecurityDisabled is intended for controlled interoperability testing.
	SecurityDisabled SecurityMode = "disabled"
)

var (
	ErrIPSecAgreementRequired = errors.New("ims: a supported ipsec-3gpp security agreement is required")
	ErrIPSecInstall           = errors.New("ims: install ipsec-3gpp security associations")
)

// IPSecSAConfig is the complete, evidence-derived 3GPP transport-mode SA set.
// The two UE SPIs identify inbound SAs; the two P-CSCF SPIs identify outbound
// SAs. EncryptionKey and IntegrityKey must be discarded after Install returns.
type IPSecSAConfig struct {
	LocalIP  net.IP
	RemoteIP net.IP

	UEClientSPI    uint32
	UEServerSPI    uint32
	PCSCFClientSPI uint32
	PCSCFServerSPI uint32

	UEClientPort    int
	UEServerPort    int
	PCSCFClientPort int
	PCSCFServerPort int

	EncryptionKey []byte
	IntegrityKey  []byte
}

type IPSecSAHandle interface {
	Close(context.Context) error
}

type IPSecSAInstaller interface {
	Install(context.Context, IPSecSAConfig) (IPSecSAHandle, error)
}

type securityProposal struct {
	spiClient  uint32
	spiServer  uint32
	portClient int
	portServer int
}

func newSecurityProposal(localIP net.IP, configuredClientPort int, configuredServerPort int) (securityProposal, error) {
	spiClient, err := randomSPI(0)
	if err != nil {
		return securityProposal{}, err
	}
	spiServer, err := randomSPI(spiClient)
	if err != nil {
		return securityProposal{}, err
	}
	portClient := configuredClientPort
	if portClient == 0 {
		portClient, err = availableProtectedPort(localIP, 0)
		if err != nil {
			return securityProposal{}, err
		}
	}
	portServer := configuredServerPort
	if portServer == 0 {
		portServer, err = availableProtectedPort(localIP, portClient)
		if err != nil {
			return securityProposal{}, err
		}
	}
	if !validProtectedPort(portClient) || !validProtectedPort(portServer) || portClient == portServer {
		return securityProposal{}, errors.New("ims: protected UE ports must be distinct non-standard SIP ports")
	}
	return securityProposal{
		spiClient:  spiClient,
		spiServer:  spiServer,
		portClient: portClient,
		portServer: portServer,
	}, nil
}

func (proposal securityProposal) headerValue() string {
	return fmt.Sprintf(
		"ipsec-3gpp;q=1.000;alg=hmac-sha-1-96;prot=esp;mod=trans;ealg=aes-cbc;spi-c=%010d;spi-s=%010d;port-c=%d;port-s=%d",
		proposal.spiClient,
		proposal.spiServer,
		proposal.portClient,
		proposal.portServer,
	)
}

func randomSPI(exclude uint32) (uint32, error) {
	for attempts := 0; attempts < 16; attempts++ {
		var value [4]byte
		if _, err := rand.Read(value[:]); err != nil {
			return 0, fmt.Errorf("ims: create protected SPI: %w", err)
		}
		spi := binary.BigEndian.Uint32(value[:])
		if spi >= 256 && spi != exclude {
			return spi, nil
		}
	}
	return 0, errors.New("ims: could not allocate a protected SPI")
}

func availableProtectedPort(localIP net.IP, exclude int) (int, error) {
	for attempts := 0; attempts < 32; attempts++ {
		var value [2]byte
		if _, err := rand.Read(value[:]); err != nil {
			return 0, fmt.Errorf("ims: create protected port: %w", err)
		}
		port := 20000 + int(binary.BigEndian.Uint16(value[:]))%44000
		if port == exclude || !validProtectedPort(port) {
			continue
		}
		address := &net.TCPAddr{IP: append(net.IP(nil), localIP...), Port: port}
		listener, err := net.ListenTCP("tcp", address)
		if err != nil {
			continue
		}
		_ = listener.Close()
		packet, err := net.ListenUDP("udp", &net.UDPAddr{IP: append(net.IP(nil), localIP...), Port: port})
		if err != nil {
			continue
		}
		_ = packet.Close()
		return port, nil
	}
	return 0, errors.New("ims: no protected local port is available")
}

func validProtectedPort(port int) bool {
	return port > 1024 && port <= 65535 && port != 5060 && port != 5061
}

type securityMechanism struct {
	raw        string
	name       string
	algorithm  string
	protocol   string
	mode       string
	encryption string
	spiClient  uint32
	spiServer  uint32
	portClient int
	portServer int
	preference int
}

type securityAgreement struct {
	selected    securityMechanism
	verifyValue string
}

func parseSecurityAgreement(values []string, proposal securityProposal) (securityAgreement, error) {
	items := splitHeaderValues(values)
	if len(items) == 0 {
		return securityAgreement{}, ErrIPSecAgreementRequired
	}
	candidates := make([]securityMechanism, 0, len(items))
	for _, item := range items {
		mechanism, err := parseSecurityMechanism(item)
		if err != nil {
			name := strings.ToLower(strings.TrimSpace(strings.SplitN(item, ";", 2)[0]))
			if name == "ipsec-3gpp" {
				return securityAgreement{}, fmt.Errorf(
					"ims: malformed ipsec-3gpp Security-Server: %w",
					err,
				)
			}
			continue
		}
		if !strings.EqualFold(mechanism.name, "ipsec-3gpp") ||
			!strings.EqualFold(mechanism.algorithm, "hmac-sha-1-96") ||
			!strings.EqualFold(mechanism.protocol, "esp") ||
			!strings.EqualFold(mechanism.mode, "trans") ||
			!strings.EqualFold(mechanism.encryption, "aes-cbc") {
			continue
		}
		if mechanism.spiClient == 0 || mechanism.spiServer == 0 ||
			mechanism.spiClient == mechanism.spiServer ||
			mechanism.spiClient == proposal.spiClient ||
			mechanism.spiClient == proposal.spiServer ||
			mechanism.spiServer == proposal.spiClient ||
			mechanism.spiServer == proposal.spiServer ||
			!validProtectedPort(mechanism.portClient) ||
			!validProtectedPort(mechanism.portServer) ||
			mechanism.portClient == mechanism.portServer {
			continue
		}
		candidates = append(candidates, mechanism)
	}
	if len(candidates) == 0 {
		return securityAgreement{}, ErrIPSecAgreementRequired
	}
	sort.SliceStable(candidates, func(left int, right int) bool {
		return candidates[left].preference > candidates[right].preference
	})
	return securityAgreement{
		selected:    candidates[0],
		verifyValue: strings.Join(items, ", "),
	}, nil
}

func parseSecurityMechanism(value string) (securityMechanism, error) {
	parts := strings.Split(value, ";")
	if len(parts) == 0 {
		return securityMechanism{}, errors.New("ims: empty Security-Server mechanism")
	}
	mechanism := securityMechanism{
		raw:        strings.TrimSpace(value),
		name:       strings.ToLower(strings.TrimSpace(parts[0])),
		protocol:   "esp",
		mode:       "trans",
		encryption: "null",
	}
	parameters := make(map[string]string)
	for _, raw := range parts[1:] {
		key, parameterValue, found := strings.Cut(strings.TrimSpace(raw), "=")
		if !found {
			return securityMechanism{}, errors.New("ims: malformed Security-Server parameter")
		}
		key = strings.ToLower(strings.TrimSpace(key))
		parameterValue = strings.Trim(strings.TrimSpace(parameterValue), `"`)
		if key == "" || parameterValue == "" {
			return securityMechanism{}, errors.New("ims: empty Security-Server parameter")
		}
		if _, duplicate := parameters[key]; duplicate {
			return securityMechanism{}, errors.New("ims: duplicate Security-Server parameter")
		}
		parameters[key] = parameterValue
	}
	mechanism.algorithm = strings.ToLower(parameters["alg"])
	if value := parameters["prot"]; value != "" {
		mechanism.protocol = strings.ToLower(value)
	}
	if value := parameters["mod"]; value != "" {
		mechanism.mode = strings.ToLower(value)
	}
	if value := parameters["ealg"]; value != "" {
		mechanism.encryption = strings.ToLower(value)
	}
	var err error
	if mechanism.spiClient, err = decimalUint32(parameters["spi-c"]); err != nil {
		return securityMechanism{}, err
	}
	if mechanism.spiServer, err = decimalUint32(parameters["spi-s"]); err != nil {
		return securityMechanism{}, err
	}
	if mechanism.portClient, err = decimalPort(parameters["port-c"]); err != nil {
		return securityMechanism{}, err
	}
	if mechanism.portServer, err = decimalPort(parameters["port-s"]); err != nil {
		return securityMechanism{}, err
	}
	mechanism.preference, err = preferenceValue(parameters["q"])
	if err != nil {
		return securityMechanism{}, err
	}
	return mechanism, nil
}

func decimalUint32(value string) (uint32, error) {
	if value == "" || len(value) > 10 {
		return 0, errors.New("ims: invalid Security-Server SPI")
	}
	parsed, err := strconv.ParseUint(value, 10, 32)
	if err != nil {
		return 0, errors.New("ims: invalid Security-Server SPI")
	}
	return uint32(parsed), nil
}

func decimalPort(value string) (int, error) {
	parsed, err := strconv.Atoi(value)
	if err != nil || parsed < 1 || parsed > 65535 {
		return 0, errors.New("ims: invalid Security-Server port")
	}
	return parsed, nil
}

func preferenceValue(value string) (int, error) {
	if value == "" {
		return 0, nil
	}
	whole, fraction, found := strings.Cut(value, ".")
	if whole != "0" && whole != "1" {
		return 0, errors.New("ims: invalid Security-Server preference")
	}
	if !found {
		if whole == "1" {
			return 1000, nil
		}
		return 0, nil
	}
	if len(fraction) > 3 {
		return 0, errors.New("ims: invalid Security-Server preference")
	}
	for len(fraction) < 3 {
		fraction += "0"
	}
	numeric, err := strconv.Atoi(fraction)
	if err != nil || (whole == "1" && numeric != 0) {
		return 0, errors.New("ims: invalid Security-Server preference")
	}
	if whole == "1" {
		return 1000, nil
	}
	return numeric, nil
}

func expandIPSecKeys(ck []byte, ik []byte) (encryption []byte, integrity []byte, err error) {
	if len(ck) != 16 || len(ik) != 16 {
		return nil, nil, errors.New("ims: AKA did not return 16-byte CK and IK")
	}
	encryption = append([]byte(nil), ck...)
	integrity = make([]byte, 20)
	copy(integrity, ik)
	return encryption, integrity, nil
}

type xfrmOperation struct {
	description string
	arguments   []string
}

func buildXFRMInstallPlan(config IPSecSAConfig) ([]xfrmOperation, error) {
	if err := validateIPSecSAConfig(config); err != nil {
		return nil, err
	}
	var operations []xfrmOperation
	states := []struct {
		description string
		source      net.IP
		destination net.IP
		spi         uint32
		reqid       uint32
	}{
		{"outbound UE-client to P-CSCF-server state", config.LocalIP, config.RemoteIP, config.PCSCFServerSPI, clientPairReqID(config)},
		{"inbound P-CSCF-server to UE-client state", config.RemoteIP, config.LocalIP, config.UEClientSPI, clientPairReqID(config)},
		{"inbound P-CSCF-client to UE-server state", config.RemoteIP, config.LocalIP, config.UEServerSPI, serverPairReqID(config)},
		{"outbound UE-server to P-CSCF-client state", config.LocalIP, config.RemoteIP, config.PCSCFClientSPI, serverPairReqID(config)},
	}
	for _, state := range states {
		operations = append(operations, xfrmOperation{
			description: state.description,
			arguments: []string{
				"xfrm", "state", "add",
				"src", state.source.String(),
				"dst", state.destination.String(),
				"proto", "esp",
				"spi", fmt.Sprintf("0x%08x", state.spi),
				"reqid", strconv.FormatUint(uint64(state.reqid), 10),
				"mode", "transport",
				"replay-window", "32",
				"auth-trunc", "hmac(sha1)", "0x" + hex.EncodeToString(config.IntegrityKey), "96",
				"enc", "cbc(aes)", "0x" + hex.EncodeToString(config.EncryptionKey),
			},
		})
	}
	for _, flow := range xfrmFlows(config) {
		for _, protocol := range flow.protocols {
			operations = append(operations, xfrmOperation{
				description: flow.description + " " + protocol + " policy",
				arguments: []string{
					flow.family,
					"xfrm", "policy", "add",
					"src", flow.sourcePrefix,
					"dst", flow.destinationPrefix,
					"proto", protocol,
					"sport", strconv.Itoa(flow.sourcePort),
					"dport", strconv.Itoa(flow.destinationPort),
					"dir", flow.direction,
					"priority", "100",
					"tmpl",
					"src", flow.templateSource.String(),
					"dst", flow.templateDestination.String(),
					"proto", "esp",
					"spi", fmt.Sprintf("0x%08x", flow.spi),
					"reqid", strconv.FormatUint(uint64(flow.reqid), 10),
					"mode", "transport",
					"level", "required",
				},
			})
		}
	}
	return operations, nil
}

func buildXFRMCleanupPlan(config IPSecSAConfig) []xfrmOperation {
	var operations []xfrmOperation
	flows := xfrmFlows(config)
	for flowIndex := len(flows) - 1; flowIndex >= 0; flowIndex-- {
		flow := flows[flowIndex]
		for protocolIndex := len(flow.protocols) - 1; protocolIndex >= 0; protocolIndex-- {
			protocol := flow.protocols[protocolIndex]
			operations = append(operations, xfrmOperation{
				description: "delete " + flow.description + " " + protocol + " policy",
				arguments: []string{
					flow.family,
					"xfrm", "policy", "delete",
					"src", flow.sourcePrefix,
					"dst", flow.destinationPrefix,
					"proto", protocol,
					"sport", strconv.Itoa(flow.sourcePort),
					"dport", strconv.Itoa(flow.destinationPort),
					"dir", flow.direction,
				},
			})
		}
	}
	states := []struct {
		source      net.IP
		destination net.IP
		spi         uint32
	}{
		{config.LocalIP, config.RemoteIP, config.PCSCFClientSPI},
		{config.RemoteIP, config.LocalIP, config.UEServerSPI},
		{config.RemoteIP, config.LocalIP, config.UEClientSPI},
		{config.LocalIP, config.RemoteIP, config.PCSCFServerSPI},
	}
	for _, state := range states {
		operations = append(operations, xfrmOperation{
			description: "delete ipsec-3gpp state",
			arguments: []string{
				"xfrm", "state", "delete",
				"src", state.source.String(),
				"dst", state.destination.String(),
				"proto", "esp",
				"spi", fmt.Sprintf("0x%08x", state.spi),
			},
		})
	}
	return operations
}

type xfrmFlow struct {
	description         string
	family              string
	sourcePrefix        string
	destinationPrefix   string
	sourcePort          int
	destinationPort     int
	direction           string
	templateSource      net.IP
	templateDestination net.IP
	spi                 uint32
	reqid               uint32
	protocols           []string
}

func xfrmFlows(config IPSecSAConfig) []xfrmFlow {
	family := "-4"
	prefix := "/32"
	if config.LocalIP.To4() == nil {
		family = "-6"
		prefix = "/128"
	}
	localPrefix := config.LocalIP.String() + prefix
	remotePrefix := config.RemoteIP.String() + prefix
	return []xfrmFlow{
		{
			description: "UE-client to P-CSCF-server", family: family,
			sourcePrefix: localPrefix, destinationPrefix: remotePrefix,
			sourcePort: config.UEClientPort, destinationPort: config.PCSCFServerPort,
			direction: "out", templateSource: config.LocalIP, templateDestination: config.RemoteIP,
			spi: config.PCSCFServerSPI, reqid: clientPairReqID(config),
			protocols: []string{"tcp", "udp"},
		},
		{
			description: "P-CSCF-server to UE-client", family: family,
			sourcePrefix: remotePrefix, destinationPrefix: localPrefix,
			sourcePort: config.PCSCFServerPort, destinationPort: config.UEClientPort,
			direction: "in", templateSource: config.RemoteIP, templateDestination: config.LocalIP,
			spi: config.UEClientSPI, reqid: clientPairReqID(config),
			protocols: []string{"tcp"},
		},
		{
			description: "P-CSCF-client to UE-server", family: family,
			sourcePrefix: remotePrefix, destinationPrefix: localPrefix,
			sourcePort: config.PCSCFClientPort, destinationPort: config.UEServerPort,
			direction: "in", templateSource: config.RemoteIP, templateDestination: config.LocalIP,
			spi: config.UEServerSPI, reqid: serverPairReqID(config),
			protocols: []string{"tcp", "udp"},
		},
		{
			description: "UE-server to P-CSCF-client", family: family,
			sourcePrefix: localPrefix, destinationPrefix: remotePrefix,
			sourcePort: config.UEServerPort, destinationPort: config.PCSCFClientPort,
			direction: "out", templateSource: config.LocalIP, templateDestination: config.RemoteIP,
			spi: config.PCSCFClientSPI, reqid: serverPairReqID(config),
			protocols: []string{"tcp"},
		},
	}
}

func clientPairReqID(config IPSecSAConfig) uint32 {
	reqid := (config.UEClientSPI ^ config.PCSCFServerSPI) & 0x7fffffff
	if reqid == 0 {
		return 1
	}
	return reqid
}

func serverPairReqID(config IPSecSAConfig) uint32 {
	reqid := (config.UEServerSPI ^ config.PCSCFClientSPI) & 0x7fffffff
	if reqid == 0 {
		reqid = 2
	}
	if reqid == clientPairReqID(config) {
		reqid ^= 0x40000000
		if reqid == 0 {
			reqid = 2
		}
	}
	return reqid
}

func validateIPSecSAConfig(config IPSecSAConfig) error {
	local := config.LocalIP
	remote := config.RemoteIP
	if local == nil || remote == nil || local.IsUnspecified() || remote.IsUnspecified() ||
		(local.To4() == nil) != (remote.To4() == nil) {
		return errors.New("ims: ipsec-3gpp endpoints are invalid or use different IP families")
	}
	spis := []uint32{
		config.UEClientSPI, config.UEServerSPI, config.PCSCFClientSPI, config.PCSCFServerSPI,
	}
	seen := make(map[uint32]struct{}, len(spis))
	for _, spi := range spis {
		if spi == 0 {
			return errors.New("ims: ipsec-3gpp SPI is zero")
		}
		if _, duplicate := seen[spi]; duplicate {
			return errors.New("ims: ipsec-3gpp SPIs must be unique")
		}
		seen[spi] = struct{}{}
	}
	ports := []int{
		config.UEClientPort, config.UEServerPort, config.PCSCFClientPort, config.PCSCFServerPort,
	}
	for _, port := range ports {
		if !validProtectedPort(port) {
			return errors.New("ims: ipsec-3gpp protected port is invalid")
		}
	}
	if config.UEClientPort == config.UEServerPort ||
		config.PCSCFClientPort == config.PCSCFServerPort {
		return errors.New("ims: client and server protected ports must differ")
	}
	if len(config.EncryptionKey) != 16 || len(config.IntegrityKey) != 20 {
		return errors.New("ims: ipsec-3gpp key length is invalid")
	}
	return nil
}

func cloneIPSecSAConfig(config IPSecSAConfig) IPSecSAConfig {
	config.LocalIP = append(net.IP(nil), config.LocalIP...)
	config.RemoteIP = append(net.IP(nil), config.RemoteIP...)
	config.EncryptionKey = append([]byte(nil), config.EncryptionKey...)
	config.IntegrityKey = append([]byte(nil), config.IntegrityKey...)
	return config
}

func zeroBytes(value []byte) {
	for index := range value {
		value[index] = 0
	}
}

func (session *Session) securityOffered() bool {
	return session.provider.config.SecurityMode != SecurityDisabled && !session.securityDeclined
}

func (session *Session) securityFromResponse(response *sipResponse) (securityAgreement, bool, error) {
	if !session.securityOffered() {
		return securityAgreement{}, false, nil
	}
	values := response.values("Security-Server")
	if len(splitHeaderValues(values)) == 0 {
		if session.provider.config.SecurityMode == SecurityRequired {
			return securityAgreement{}, false, ErrIPSecAgreementRequired
		}
		session.declineSecurity()
		return securityAgreement{}, false, nil
	}
	agreement, err := parseSecurityAgreement(values, session.securityProposal)
	if err != nil {
		return securityAgreement{}, false, err
	}
	return agreement, true, nil
}

func (session *Session) declineSecurity() {
	session.securityDeclined = true
	session.endpoint = session.initialEndpoint
	if session.protectedTCP != nil {
		_ = session.protectedTCP.Close()
		session.protectedTCP = nil
	}
	if session.protectedUDP != nil {
		_ = session.protectedUDP.Close()
		session.protectedUDP = nil
	}
	session.securityProposal = securityProposal{}
}

func (session *Session) activateIPSec(
	ctx context.Context,
	agreement securityAgreement,
	ck []byte,
	ik []byte,
) error {
	if session.securityActive {
		return errors.New("ims: ipsec-3gpp is already active")
	}
	if !session.securityOffered() {
		return ErrIPSecAgreementRequired
	}
	encryptionKey, integrityKey, err := expandIPSecKeys(ck, ik)
	if err != nil {
		return err
	}
	defer zeroBytes(encryptionKey)
	defer zeroBytes(integrityKey)

	localIP := addressIP(session.conn.LocalAddr())
	remoteIP := addressIP(session.conn.RemoteAddr())
	if localIP == nil || remoteIP == nil {
		return errors.New("ims: protected SIP endpoints are unavailable")
	}
	selected := agreement.selected
	config := IPSecSAConfig{
		LocalIP:         localIP,
		RemoteIP:        remoteIP,
		UEClientSPI:     session.securityProposal.spiClient,
		UEServerSPI:     session.securityProposal.spiServer,
		PCSCFClientSPI:  selected.spiClient,
		PCSCFServerSPI:  selected.spiServer,
		UEClientPort:    session.securityProposal.portClient,
		UEServerPort:    session.securityProposal.portServer,
		PCSCFClientPort: selected.portClient,
		PCSCFServerPort: selected.portServer,
		EncryptionKey:   encryptionKey,
		IntegrityKey:    integrityKey,
	}
	handle, err := session.provider.installer.Install(ctx, config)
	if err != nil {
		return fmt.Errorf("%w: %v", ErrIPSecInstall, err)
	}
	if handle == nil {
		return fmt.Errorf("%w: installer returned no handle", ErrIPSecInstall)
	}

	remoteAddress := net.JoinHostPort(remoteIP.String(), strconv.Itoa(selected.portServer))
	_ = session.conn.Close()
	connection, dialErr := dialSIP(
		ctx,
		session.transport,
		localIP.String(),
		session.securityProposal.portClient,
		remoteAddress,
	)
	if dialErr != nil {
		cleanupErr := handle.Close(context.Background())
		if cleanupErr != nil {
			return errors.Join(
				fmt.Errorf("ims: connect protected P-CSCF: %w", dialErr),
				fmt.Errorf("ims: roll back ipsec-3gpp: %w", cleanupErr),
			)
		}
		return fmt.Errorf("ims: connect protected P-CSCF: %w", dialErr)
	}

	session.conn = connection
	if session.transport == "tcp" {
		session.reader = bufio.NewReader(connection)
	} else {
		session.reader = nil
	}
	session.endpoint.port = selected.portServer
	session.securityAgreement = agreement
	session.securityActive = true
	session.ipsecHandle = handle
	return nil
}

func (session *Session) contactAddress() string {
	if session.securityOffered() {
		host := addressHost(session.conn.LocalAddr())
		return net.JoinHostPort(host, strconv.Itoa(session.securityProposal.portServer))
	}
	return session.conn.LocalAddr().String()
}

func (session *Session) emptyDigestAuthorization() string {
	uri := "sip:" + session.identity.domain
	return "Digest " + strings.Join([]string{
		`username="` + quoteDigest(session.identity.private) + `"`,
		`realm="` + quoteDigest(session.identity.domain) + `"`,
		`nonce=""`,
		`uri="` + quoteDigest(uri) + `"`,
		`response=""`,
		"algorithm=AKAv1-MD5",
		"integrity-protected=no",
	}, ", ")
}

func (session *Session) validProtectedUDPSource(remote *net.UDPAddr) bool {
	if remote == nil || !session.securityActive {
		return false
	}
	expectedIP := addressIP(session.conn.RemoteAddr())
	return expectedIP != nil &&
		expectedIP.Equal(remote.IP) &&
		remote.Port == session.securityAgreement.selected.portClient
}

func (session *Session) effectiveSecurityMode() string {
	if session.securityActive {
		return "ipsec-3gpp"
	}
	return "none"
}
