package websocket

import (
	
	
	
	
	
	
	
)

// NetConn converts a *websocket.Conn into a net.Conn.
//
// It's for tunneling arbitrary protocols over WebSockets.
// Few users of the library will need this but it's tricky to implement
// correctly and so provided in the library.
// See https://github.com/nhooyr/websocket/issues/100.
//
// Every Write to the net.Conn will correspond to a message write of
// the given type on *websocket.Conn.
//
// The passed ctx bounds the lifetime of the net.Conn. If cancelled,
// all reads and writes on the net.Conn will be cancelled.
//
// If a message is read that is not of the correct type, the connection
// will be closed with StatusUnsupportedData and an error will be returned.
//
// Close will close the *websocket.Conn with StatusNormalClosure.
//
// When a deadline is hit and there is an active read or write goroutine, the
// connection will be closed. This is different from most net.Conn implementations
// where only the reading/writing goroutines are interrupted but the connection
// is kept alive.
//
// The Addr methods will return the real addresses for connections obtained
// from websocket.Accept. But for connections obtained from websocket.Dial, a mock net.Addr
// will be returned that gives "websocket" for Network() and "websocket/unknown-addr" for
// String(). This is because websocket.Dial only exposes a io.ReadWriteCloser instead of the
// full net.Conn to us.
//
// When running as WASM, the Addr methods will always return the mock address described above.
//
// A received StatusNormalClosure or StatusGoingAway close frame will be translated to
// io.EOF when reading.
//
// Furthermore, the ReadLimit is set to -1 to disable it.
func ( context.Context,  *Conn,  MessageType) net.Conn {
	.SetReadLimit(-1)

	 := &netConn{
		c:       ,
		msgType: ,
		readMu:  newMu(),
		writeMu: newMu(),
	}

	.writeCtx, .writeCancel = context.WithCancel()
	.readCtx, .readCancel = context.WithCancel()

	.writeTimer = time.AfterFunc(math.MaxInt64, func() {
		if !.writeMu.tryLock() {
			// If the lock cannot be acquired, then there is an
			// active write goroutine and so we should cancel the context.
			.writeCancel()
			return
		}
		defer .writeMu.unlock()

		// Prevents future writes from writing until the deadline is reset.
		atomic.StoreInt64(&.writeExpired, 1)
	})
	if !.writeTimer.Stop() {
		<-.writeTimer.C
	}

	.readTimer = time.AfterFunc(math.MaxInt64, func() {
		if !.readMu.tryLock() {
			// If the lock cannot be acquired, then there is an
			// active read goroutine and so we should cancel the context.
			.readCancel()
			return
		}
		defer .readMu.unlock()

		// Prevents future reads from reading until the deadline is reset.
		atomic.StoreInt64(&.readExpired, 1)
	})
	if !.readTimer.Stop() {
		<-.readTimer.C
	}

	return 
}

type netConn struct {
	c       *Conn
	msgType MessageType

	writeTimer   *time.Timer
	writeMu      *mu
	writeExpired int64
	writeCtx     context.Context
	writeCancel  context.CancelFunc

	readTimer   *time.Timer
	readMu      *mu
	readExpired int64
	readCtx     context.Context
	readCancel  context.CancelFunc
	readEOFed   bool
	reader      io.Reader
}

var _ net.Conn = &netConn{}

func ( *netConn) () error {
	.writeTimer.Stop()
	.writeCancel()
	.readTimer.Stop()
	.readCancel()
	return .c.Close(StatusNormalClosure, "")
}

func ( *netConn) ( []byte) (int, error) {
	.writeMu.forceLock()
	defer .writeMu.unlock()

	if atomic.LoadInt64(&.writeExpired) == 1 {
		return 0, fmt.Errorf("failed to write: %w", context.DeadlineExceeded)
	}

	 := .c.Write(.writeCtx, .msgType, )
	if  != nil {
		return 0, 
	}
	return len(), nil
}

func ( *netConn) ( []byte) (int, error) {
	.readMu.forceLock()
	defer .readMu.unlock()

	for {
		,  := .read()
		if  != nil {
			return , 
		}
		if  == 0 {
			continue
		}
		return , nil
	}
}

func ( *netConn) ( []byte) (int, error) {
	if atomic.LoadInt64(&.readExpired) == 1 {
		return 0, fmt.Errorf("failed to read: %w", context.DeadlineExceeded)
	}

	if .readEOFed {
		return 0, io.EOF
	}

	if .reader == nil {
		, ,  := .c.Reader(.readCtx)
		if  != nil {
			switch CloseStatus() {
			case StatusNormalClosure, StatusGoingAway:
				.readEOFed = true
				return 0, io.EOF
			}
			return 0, 
		}
		if  != .msgType {
			 := fmt.Errorf("unexpected frame type read (expected %v): %v", .msgType, )
			.c.Close(StatusUnsupportedData, .Error())
			return 0, 
		}
		.reader = 
	}

	,  := .reader.Read()
	if  == io.EOF {
		.reader = nil
		 = nil
	}
	return , 
}

type websocketAddr struct {
}

func ( websocketAddr) () string {
	return "websocket"
}

func ( websocketAddr) () string {
	return "websocket/unknown-addr"
}

func ( *netConn) ( time.Time) error {
	.SetWriteDeadline()
	.SetReadDeadline()
	return nil
}

func ( *netConn) ( time.Time) error {
	atomic.StoreInt64(&.writeExpired, 0)
	if .IsZero() {
		.writeTimer.Stop()
	} else {
		 := time.Until()
		if  <= 0 {
			 = 1
		}
		.writeTimer.Reset()
	}
	return nil
}

func ( *netConn) ( time.Time) error {
	atomic.StoreInt64(&.readExpired, 0)
	if .IsZero() {
		.readTimer.Stop()
	} else {
		 := time.Until()
		if  <= 0 {
			 = 1
		}
		.readTimer.Reset()
	}
	return nil
}