// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package server

import (
	
	

	
	
	
	
	
)

const runesAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

// See: https://tools.ietf.org/html/rfc5766#section-6.2
// .
func handleAllocateRequest( Request,  *stun.Message) error { //nolint:cyclop
	.Log.Debugf("Received AllocateRequest from %s", .SrcAddr)

	// 1. The server MUST require that the request be authenticated.  This
	//    authentication MUST be done using the long-term credential
	//    mechanism of [https://tools.ietf.org/html/rfc5389#section-10.2.2]
	//    unless the client and server agree to use another mechanism through
	//    some procedure outside the scope of this document.
	, ,  := authenticateRequest(, , stun.MethodAllocate)
	if ! {
		return 
	}

	 := &allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	}
	 := 0
	 := ""

	 := buildMsg(
		.TransactionID,
		stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse),
		&stun.ErrorCodeAttribute{Code: stun.CodeBadRequest},
	)
	 := buildMsg(
		.TransactionID,
		stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse),
		&stun.ErrorCodeAttribute{Code: stun.CodeInsufficientCapacity},
	)

	// 2. The server checks if the 5-tuple is currently in use by an
	//    existing allocation.  If yes, the server rejects the request with
	//    a 437 (Allocation Mismatch) error.
	if  := .AllocationManager.GetAllocation();  != nil {
		,  := .GetResponseCache()
		if  != .TransactionID {
			 := buildMsg(
				.TransactionID,
				stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse),
				&stun.ErrorCodeAttribute{Code: stun.CodeAllocMismatch},
			)

			return buildAndSendErr(.Conn, .SrcAddr, errRelayAlreadyAllocatedForFiveTuple, ...)
		}
		// A retry allocation
		 := buildMsg(
			.TransactionID,
			stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse),
			append(, )...,
		)

		return buildAndSend(.Conn, .SrcAddr, ...)
	}

	// 3. The server checks if the request contains a REQUESTED-TRANSPORT
	//    attribute.  If the REQUESTED-TRANSPORT attribute is not included
	//    or is malformed, the server rejects the request with a 400 (Bad
	//    Request) error.  Otherwise, if the attribute is included but
	//    specifies a protocol other that UDP/TCP, the server rejects the
	//    request with a 442 (Unsupported Transport Protocol) error.
	var  proto.RequestedTransport
	if  = .GetFrom();  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	} else if .Protocol != proto.ProtoUDP && .Protocol != proto.ProtoTCP {
		 := buildMsg(
			.TransactionID,
			stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse),
			&stun.ErrorCodeAttribute{Code: stun.CodeUnsupportedTransProto},
		)

		return buildAndSendErr(.Conn, .SrcAddr, errUnsupportedTransportProtocol, ...)
	}

	// 4. The request may contain a DONT-FRAGMENT attribute.  If it does,
	//    but the server does not support sending UDP datagrams with the DF
	//    bit set to 1 (see Section 12), then the server treats the DONT-
	//    FRAGMENT attribute in the Allocate request as an unknown
	//    comprehension-required attribute.
	if .Contains(stun.AttrDontFragment) {
		 := buildMsg(
			.TransactionID,
			stun.NewType(stun.MethodAllocate, stun.ClassErrorResponse),
			&stun.ErrorCodeAttribute{Code: stun.CodeUnknownAttribute},
			&stun.UnknownAttributes{stun.AttrDontFragment},
		)

		return buildAndSendErr(.Conn, .SrcAddr, errNoDontFragmentSupport, ...)
	}

	// 5.  The server checks if the request contains a RESERVATION-TOKEN
	//     attribute.  If yes, and the request also contains an EVEN-PORT
	//     attribute, then the server rejects the request with a 400 (Bad
	//     Request) error.  Otherwise, it checks to see if the token is
	//     valid (i.e., the token is in range and has not expired and the
	//     corresponding relayed transport address is still available).  If
	//     the token is not valid for some reason, the server rejects the
	//     request with a 508 (Insufficient Capacity) error.
	var  proto.ReservationToken
	if  = .GetFrom();  == nil {
		var  proto.EvenPort
		if  = .GetFrom();  == nil {
			return buildAndSendErr(.Conn, .SrcAddr, errRequestWithReservationTokenAndEvenPort, ...)
		}
	}

	// 6. The server checks if the request contains an EVEN-PORT attribute.
	//    If yes, then the server checks that it can satisfy the request
	//    (i.e., can allocate a relayed transport address as described
	//    below).  If the server cannot satisfy the request, then the
	//    server rejects the request with a 508 (Insufficient Capacity)
	//    error.
	var  proto.EvenPort
	if  = .GetFrom();  == nil {
		var  int
		,  = .AllocationManager.GetRandomEvenPort()
		if  != nil {
			return buildAndSendErr(.Conn, .SrcAddr, , ...)
		}
		 = 
		,  = randutil.GenerateCryptoRandomString(8, runesAlpha)
		if  != nil {
			return 
		}
	}

	// 7. At any point, the server MAY choose to reject the request with a
	//    486 (Allocation Quota Reached) error if it feels the client is
	//    trying to exceed some locally defined allocation quota.  The
	//    server is free to define this allocation quota any way it wishes,
	//    but SHOULD define it based on the username used to authenticate
	//    the request, and not on the client's transport address.

	// 8. Also at any point, the server MAY choose to reject the request
	//    with a 300 (Try Alternate) error if it wishes to redirect the
	//    client to a different server.  The use of this error code and
	//    attribute follow the specification in [RFC5389].
	 := allocationLifeTime()
	,  := .AllocationManager.CreateAllocation(
		,
		.Conn,
		,
		)
	if  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	// Once the allocation is created, the server replies with a success
	// response.
	// The success response contains:
	//   * An XOR-RELAYED-ADDRESS attribute containing the relayed transport
	//     address.
	//   * A LIFETIME attribute containing the current value of the time-to-
	//     expiry timer.
	//   * A RESERVATION-TOKEN attribute (if a second relayed transport
	//     address was reserved).
	//   * An XOR-MAPPED-ADDRESS attribute containing the client's IP address
	//     and port (from the 5-tuple).

	, ,  := ipnet.AddrIPPort(.SrcAddr)
	if  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	, ,  := ipnet.AddrIPPort(.RelayAddr)
	if  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	 := []stun.Setter{
		&proto.RelayedAddress{
			IP:   ,
			Port: ,
		},
		&proto.Lifetime{
			Duration: ,
		},
		&stun.XORMappedAddress{
			IP:   ,
			Port: ,
		},
	}

	if  != "" {
		.AllocationManager.CreateReservation(, )
		 = append(, proto.ReservationToken([]byte()))
	}

	 := buildMsg(
		.TransactionID,
		stun.NewType(stun.MethodAllocate, stun.ClassSuccessResponse),
		append(, )...,
	)
	.SetResponseCache(.TransactionID, )

	return buildAndSend(.Conn, .SrcAddr, ...)
}

func handleRefreshRequest( Request,  *stun.Message) error {
	.Log.Debugf("Received RefreshRequest from %s", .SrcAddr)

	, ,  := authenticateRequest(, , stun.MethodRefresh)
	if ! {
		return 
	}

	 := allocationLifeTime()
	 := &allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	}

	if  != 0 {
		 := .AllocationManager.GetAllocation()

		if  == nil {
			return fmt.Errorf("%w %v:%v", errNoAllocationFound, .SrcAddr, .Conn.LocalAddr())
		}
		.Refresh()
	} else {
		.AllocationManager.DeleteAllocation()
	}

	return buildAndSend(
		.Conn,
		.SrcAddr,
		buildMsg(
			.TransactionID,
			stun.NewType(stun.MethodRefresh, stun.ClassSuccessResponse),
			[]stun.Setter{
				&proto.Lifetime{
					Duration: ,
				},
				,
			}...,
		)...,
	)
}

func handleCreatePermissionRequest( Request,  *stun.Message) error {
	.Log.Debugf("Received CreatePermission from %s", .SrcAddr)

	 := .AllocationManager.GetAllocation(&allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	})
	if  == nil {
		return fmt.Errorf("%w %v:%v", errNoAllocationFound, .SrcAddr, .Conn.LocalAddr())
	}

	, ,  := authenticateRequest(, , stun.MethodCreatePermission)
	if ! {
		return 
	}

	 := 0

	if  := .ForEach(stun.AttrXORPeerAddress, func( *stun.Message) error {
		var  proto.PeerAddress
		if  := .GetFrom();  != nil {
			return 
		}

		if  := .AllocationManager.GrantPermission(.SrcAddr, .IP);  != nil {
			.Log.Infof("permission denied for client %s to peer %s", .SrcAddr, .IP)

			return 
		}

		.Log.Debugf("Adding permission for %s", fmt.Sprintf("%s:%d",
			.IP, .Port))

		.AddPermission(allocation.NewPermission(
			&net.UDPAddr{
				IP:   .IP,
				Port: .Port,
			},
			.Log,
		))
		++

		return nil
	});  != nil {
		 = 0
	}

	 := stun.ClassSuccessResponse
	if  == 0 {
		 = stun.ClassErrorResponse
	}

	return buildAndSend(
		.Conn,
		.SrcAddr,
		buildMsg(.TransactionID, stun.NewType(stun.MethodCreatePermission, ),
			[]stun.Setter{}...)...,
	)
}

func handleSendIndication( Request,  *stun.Message) error {
	.Log.Debugf("Received SendIndication from %s", .SrcAddr)
	 := .AllocationManager.GetAllocation(&allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	})
	if  == nil {
		return fmt.Errorf("%w %v:%v", errNoAllocationFound, .SrcAddr, .Conn.LocalAddr())
	}

	 := proto.Data{}
	if  := .GetFrom();  != nil {
		return 
	}

	 := proto.PeerAddress{}
	if  := .GetFrom();  != nil {
		return 
	}

	 := &net.UDPAddr{IP: .IP, Port: .Port}
	if  := .GetPermission();  == nil {
		return fmt.Errorf("%w: %v", errNoPermission, )
	}

	,  := .RelaySocket.WriteTo(, )
	if  != len() {
		return fmt.Errorf("%w %d != %d (expected) err: %v", errShortWrite, , len(), ) //nolint:errorlint
	}

	return 
}

func handleChannelBindRequest( Request,  *stun.Message) error {
	.Log.Debugf("Received ChannelBindRequest from %s", .SrcAddr)

	 := .AllocationManager.GetAllocation(&allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	})
	if  == nil {
		return fmt.Errorf("%w %v:%v", errNoAllocationFound, .SrcAddr, .Conn.LocalAddr())
	}

	 := buildMsg(
		.TransactionID,
		stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse),
		&stun.ErrorCodeAttribute{Code: stun.CodeBadRequest},
	)

	, ,  := authenticateRequest(, , stun.MethodChannelBind)
	if ! {
		return 
	}

	var  proto.ChannelNumber
	if  = .GetFrom();  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	 := proto.PeerAddress{}
	if  = .GetFrom();  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	if  = .AllocationManager.GrantPermission(.SrcAddr, .IP);  != nil {
		.Log.Infof("permission denied for client %s to peer %s", .SrcAddr, .IP)

		 := buildMsg(.TransactionID,
			stun.NewType(stun.MethodChannelBind, stun.ClassErrorResponse),
			&stun.ErrorCodeAttribute{Code: stun.CodeUnauthorized})

		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	.Log.Debugf("Binding channel %d to %s", , )
	 = .AddChannelBind(allocation.NewChannelBind(
		,
		&net.UDPAddr{IP: .IP, Port: .Port},
		.Log,
	), .ChannelBindTimeout)
	if  != nil {
		return buildAndSendErr(.Conn, .SrcAddr, , ...)
	}

	return buildAndSend(
		.Conn,
		.SrcAddr,
		buildMsg(.TransactionID, stun.NewType(stun.MethodChannelBind, stun.ClassSuccessResponse),
			[]stun.Setter{}...)...,
	)
}

func handleChannelData( Request,  *proto.ChannelData) error {
	.Log.Debugf("Received ChannelData from %s", .SrcAddr)

	 := .AllocationManager.GetAllocation(&allocation.FiveTuple{
		SrcAddr:  .SrcAddr,
		DstAddr:  .Conn.LocalAddr(),
		Protocol: allocation.UDP,
	})
	if  == nil {
		return fmt.Errorf("%w %v:%v", errNoAllocationFound, .SrcAddr, .Conn.LocalAddr())
	}

	 := .GetChannelByNumber(.Number)
	if  == nil {
		return fmt.Errorf("%w %x", errNoSuchChannelBind, uint16(.Number))
	}

	,  := .RelaySocket.WriteTo(.Data, .Peer)
	if  != nil {
		return fmt.Errorf("%w: %s", errFailedWriteSocket, .Error())
	} else if  != len(.Data) {
		return fmt.Errorf("%w %d != %d (expected)", errShortWrite, , len(.Data))
	}

	return nil
}