//go:build !js
// +build !js

package websocket

import (
	
	
	
	
	
	

	
)

// StatusCode represents a WebSocket status code.
// https://tools.ietf.org/html/rfc6455#section-7.4
type StatusCode int

// https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
//
// These are only the status codes defined by the protocol.
//
// You can define custom codes in the 3000-4999 range.
// The 3000-3999 range is reserved for use by libraries, frameworks and applications.
// The 4000-4999 range is reserved for private use.
const (
	StatusNormalClosure   StatusCode = 1000
	StatusGoingAway       StatusCode = 1001
	StatusProtocolError   StatusCode = 1002
	StatusUnsupportedData StatusCode = 1003

	// 1004 is reserved and so unexported.
	statusReserved StatusCode = 1004

	// StatusNoStatusRcvd cannot be sent in a close message.
	// It is reserved for when a close message is received without
	// a status code.
	StatusNoStatusRcvd StatusCode = 1005

	// StatusAbnormalClosure is exported for use only with Wasm.
	// In non Wasm Go, the returned error will indicate whether the
	// connection was closed abnormally.
	StatusAbnormalClosure StatusCode = 1006

	StatusInvalidFramePayloadData StatusCode = 1007
	StatusPolicyViolation         StatusCode = 1008
	StatusMessageTooBig           StatusCode = 1009
	StatusMandatoryExtension      StatusCode = 1010
	StatusInternalError           StatusCode = 1011
	StatusServiceRestart          StatusCode = 1012
	StatusTryAgainLater           StatusCode = 1013
	StatusBadGateway              StatusCode = 1014

	// StatusTLSHandshake is only exported for use with Wasm.
	// In non Wasm Go, the returned error will indicate whether there was
	// a TLS handshake failure.
	StatusTLSHandshake StatusCode = 1015
)

// CloseError is returned when the connection is closed with a status and reason.
//
// Use Go 1.13's errors.As to check for this error.
// Also see the CloseStatus helper.
type CloseError struct {
	Code   StatusCode
	Reason string
}

func ( CloseError) () string {
	return fmt.Sprintf("status = %v and reason = %q", .Code, .Reason)
}

// CloseStatus is a convenience wrapper around Go 1.13's errors.As to grab
// the status code from a CloseError.
//
// -1 will be returned if the passed error is nil or not a CloseError.
func ( error) StatusCode {
	var  CloseError
	if errors.As(, &) {
		return .Code
	}
	return -1
}

// Close performs the WebSocket close handshake with the given status code and reason.
//
// It will write a WebSocket close frame with a timeout of 5s and then wait 5s for
// the peer to send a close frame.
// All data messages received from the peer during the close handshake will be discarded.
//
// The connection can only be closed once. Additional calls to Close
// are no-ops.
//
// The maximum length of reason must be 125 bytes. Avoid
// sending a dynamic reason.
//
// Close will unblock all goroutines interacting with the connection once
// complete.
func ( *Conn) ( StatusCode,  string) error {
	defer .wg.Wait()
	return .closeHandshake(, )
}

// CloseNow closes the WebSocket connection without attempting a close handshake.
// Use when you do not want the overhead of the close handshake.
func ( *Conn) () ( error) {
	defer .wg.Wait()
	defer errd.Wrap(&, "failed to close WebSocket")

	if .isClosed() {
		return net.ErrClosed
	}

	.close(nil)
	return .closeErr
}

func ( *Conn) ( StatusCode,  string) ( error) {
	defer errd.Wrap(&, "failed to close WebSocket")

	 := .writeClose(, )
	 := .waitCloseHandshake()

	if  != nil {
		return 
	}

	if CloseStatus() == -1 && !errors.Is(net.ErrClosed, ) {
		return 
	}

	return nil
}

func ( *Conn) ( StatusCode,  string) error {
	.closeMu.Lock()
	 := .wroteClose
	.wroteClose = true
	.closeMu.Unlock()
	if  {
		return net.ErrClosed
	}

	 := CloseError{
		Code:   ,
		Reason: ,
	}

	var  []byte
	var  error
	if .Code != StatusNoStatusRcvd {
		,  = .bytes()
	}

	 := .writeControl(context.Background(), opClose, )
	if CloseStatus() != -1 {
		// Not a real error if it's due to a close frame being received.
		 = nil
	}

	// We do this after in case there was an error writing the close frame.
	.setCloseErr(fmt.Errorf("sent close frame: %w", ))

	if  != nil {
		return 
	}
	return 
}

func ( *Conn) () error {
	defer .close(nil)

	,  := context.WithTimeout(context.Background(), time.Second*5)
	defer ()

	 := .readMu.lock()
	if  != nil {
		return 
	}
	defer .readMu.unlock()

	if .readCloseFrameErr != nil {
		return .readCloseFrameErr
	}

	for  := int64(0);  < .msgReader.payloadLength; ++ {
		,  := .br.ReadByte()
		if  != nil {
			return 
		}
	}

	for {
		,  := .readLoop()
		if  != nil {
			return 
		}

		for  := int64(0);  < .payloadLength; ++ {
			,  := .br.ReadByte()
			if  != nil {
				return 
			}
		}
	}
}

func ( []byte) (CloseError, error) {
	if len() == 0 {
		return CloseError{
			Code: StatusNoStatusRcvd,
		}, nil
	}

	if len() < 2 {
		return CloseError{}, fmt.Errorf("close payload %q too small, cannot even contain the 2 byte status code", )
	}

	 := CloseError{
		Code:   StatusCode(binary.BigEndian.Uint16()),
		Reason: string([2:]),
	}

	if !validWireCloseCode(.Code) {
		return CloseError{}, fmt.Errorf("invalid status code %v", .Code)
	}

	return , nil
}

// See http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
// and https://tools.ietf.org/html/rfc6455#section-7.4.1
func ( StatusCode) bool {
	switch  {
	case statusReserved, StatusNoStatusRcvd, StatusAbnormalClosure, StatusTLSHandshake:
		return false
	}

	if  >= StatusNormalClosure &&  <= StatusBadGateway {
		return true
	}
	if  >= 3000 &&  <= 4999 {
		return true
	}

	return false
}

func ( CloseError) () ([]byte, error) {
	,  := .bytesErr()
	if  != nil {
		 = fmt.Errorf("failed to marshal close frame: %w", )
		 = CloseError{
			Code: StatusInternalError,
		}
		, _ = .bytesErr()
	}
	return , 
}

const maxCloseReason = maxControlPayload - 2

func ( CloseError) () ([]byte, error) {
	if len(.Reason) > maxCloseReason {
		return nil, fmt.Errorf("reason string max is %v but got %q with length %v", maxCloseReason, .Reason, len(.Reason))
	}

	if !validWireCloseCode(.Code) {
		return nil, fmt.Errorf("status code %v cannot be set", .Code)
	}

	 := make([]byte, 2+len(.Reason))
	binary.BigEndian.PutUint16(, uint16(.Code))
	copy([2:], .Reason)
	return , nil
}

func ( *Conn) ( error) {
	.closeMu.Lock()
	.setCloseErrLocked()
	.closeMu.Unlock()
}

func ( *Conn) ( error) {
	if .closeErr == nil &&  != nil {
		.closeErr = fmt.Errorf("WebSocket closed: %w", )
	}
}

func ( *Conn) () bool {
	select {
	case <-.closed:
		return true
	default:
		return false
	}
}