package webtransport

import (
	
	
	
	
	
	
	
	
	

	
	
	
)

const (
	webTransportDraftOfferHeaderKey = "Sec-Webtransport-Http3-Draft02"
	webTransportDraftHeaderKey      = "Sec-Webtransport-Http3-Draft"
	webTransportDraftHeaderValue    = "draft02"
)

const (
	webTransportFrameType     = 0x41
	webTransportUniStreamType = 0x54
)

type Server struct {
	H3 http3.Server

	// ReorderingTimeout is the maximum time an incoming WebTransport stream that cannot be associated
	// with a session is buffered. It is also the maximum time a WebTransport connection request is
	// blocked waiting for the client's SETTINGS are received.
	// This can happen if the CONNECT request (that creates a new session) is reordered, and arrives
	// after the first WebTransport stream(s) for that session.
	// Defaults to 5 seconds.
	ReorderingTimeout time.Duration

	// CheckOrigin is used to validate the request origin, thereby preventing cross-site request forgery.
	// CheckOrigin returns true if the request Origin header is acceptable.
	// If unset, a safe default is used: If the Origin header is set, it is checked that it
	// matches the request's Host header.
	CheckOrigin func(r *http.Request) bool

	ctx       context.Context // is closed when Close is called
	ctxCancel context.CancelFunc
	refCount  sync.WaitGroup

	initOnce sync.Once
	initErr  error

	conns *sessionManager
}

func ( *Server) () error {
	.initOnce.Do(func() {
		.initErr = .init()
	})
	return .initErr
}

func ( *Server) () time.Duration {
	 := .ReorderingTimeout
	if  == 0 {
		return 5 * time.Second
	}
	return 
}

func ( *Server) () error {
	.ctx, .ctxCancel = context.WithCancel(context.Background())

	.conns = newSessionManager(.timeout())
	if .CheckOrigin == nil {
		.CheckOrigin = checkSameOrigin
	}

	// configure the http3.Server
	if .H3.AdditionalSettings == nil {
		.H3.AdditionalSettings = make(map[uint64]uint64, 1)
	}
	.H3.AdditionalSettings[settingsEnableWebtransport] = 1
	.H3.EnableDatagrams = true
	if .H3.StreamHijacker != nil {
		return errors.New("StreamHijacker already set")
	}
	.H3.StreamHijacker = func( http3.FrameType,  quic.ConnectionTracingID,  quic.Stream,  error) (bool /* hijacked */, error) {
		if isWebTransportError() {
			return true, nil
		}
		if  != webTransportFrameType {
			return false, nil
		}
		// Reading the varint might block if the peer sends really small frames, but this is fine.
		// This function is called from the HTTP/3 request handler, which runs in its own Go routine.
		,  := quicvarint.Read(quicvarint.NewReader())
		if  != nil {
			if isWebTransportError() {
				return true, nil
			}
			return false, 
		}
		.conns.AddStream(, , sessionID())
		return true, nil
	}
	.H3.UniStreamHijacker = func( http3.StreamType,  quic.ConnectionTracingID,  quic.ReceiveStream,  error) ( bool) {
		if  != webTransportUniStreamType && !isWebTransportError() {
			return false
		}
		.conns.AddUniStream(, )
		return true
	}
	return nil
}

func ( *Server) ( net.PacketConn) error {
	if  := .initialize();  != nil {
		return 
	}
	return .H3.Serve()
}

// ServeQUICConn serves a single QUIC connection.
func ( *Server) ( quic.Connection) error {
	if  := .initialize();  != nil {
		return 
	}
	return .H3.ServeQUICConn()
}

func ( *Server) () error {
	if  := .initialize();  != nil {
		return 
	}
	return .H3.ListenAndServe()
}

func ( *Server) (,  string) error {
	if  := .initialize();  != nil {
		return 
	}
	return .H3.ListenAndServeTLS(, )
}

func ( *Server) () error {
	// Make sure that ctxCancel is defined.
	// This is expected to be uncommon.
	// It only happens if the server is closed without Serve / ListenAndServe having been called.
	.initOnce.Do(func() {})

	if .ctxCancel != nil {
		.ctxCancel()
	}
	if .conns != nil {
		.conns.Close()
	}
	 := .H3.Close()
	.refCount.Wait()
	return 
}

func ( *Server) ( http.ResponseWriter,  *http.Request) (*Session, error) {
	if .Method != http.MethodConnect {
		return nil, fmt.Errorf("expected CONNECT request, got %s", .Method)
	}
	if .Proto != protocolHeader {
		return nil, fmt.Errorf("unexpected protocol: %s", .Proto)
	}
	if ,  := .Header[webTransportDraftOfferHeaderKey]; ! || len() != 1 || [0] != "1" {
		return nil, fmt.Errorf("missing or invalid %s header", webTransportDraftOfferHeaderKey)
	}
	if !.CheckOrigin() {
		return nil, errors.New("webtransport: request origin not allowed")
	}

	// Wait for SETTINGS
	 := .(http3.Hijacker).Connection()
	 := time.NewTimer(.timeout())
	defer .Stop()
	select {
	case <-.ReceivedSettings():
	case <-.C:
		return nil, errors.New("webtransport: didn't receive the client's SETTINGS on time")
	}
	 := .Settings()
	if !.EnableDatagrams {
		return nil, errors.New("webtransport: missing datagram support")
	}

	.Header().Add(webTransportDraftHeaderKey, webTransportDraftHeaderValue)
	.WriteHeader(http.StatusOK)
	.(http.Flusher).Flush()

	 := .(http3.HTTPStreamer).HTTPStream()
	 := sessionID(.StreamID())
	return .conns.AddSession(, , ), nil
}

// copied from https://github.com/gorilla/websocket
func checkSameOrigin( *http.Request) bool {
	 := .Header.Get("Origin")
	if  == "" {
		return true
	}
	,  := url.Parse()
	if  != nil {
		return false
	}
	return equalASCIIFold(.Host, .Host)
}

// copied from https://github.com/gorilla/websocket
func equalASCIIFold(,  string) bool {
	for  != "" &&  != "" {
		,  := utf8.DecodeRuneInString()
		 = [:]
		,  := utf8.DecodeRuneInString()
		 = [:]
		if  ==  {
			continue
		}
		if 'A' <=  &&  <= 'Z' {
			 =  + 'a' - 'A'
		}
		if 'A' <=  &&  <= 'Z' {
			 =  + 'a' - 'A'
		}
		if  !=  {
			return false
		}
	}
	return  == 
}