 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			228 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			228 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package webtransport
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"net"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"github.com/quic-go/quic-go"
 | |
| 	"github.com/quic-go/quic-go/http3"
 | |
| 	"github.com/quic-go/quic-go/quicvarint"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	webTransportDraftOfferHeaderKey = "Sec-Webtransport-Http3-Draft02"
 | |
| 	webTransportDraftHeaderKey      = "Sec-Webtransport-Http3-Draft"
 | |
| 	webTransportDraftHeaderValue    = "draft02"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	webTransportFrameType     = 0x41
 | |
| 	webTransportUniStreamType = 0x54
 | |
| )
 | |
| 
 | |
| type Server struct {
 | |
| 	H3 http3.Server
 | |
| 
 | |
| 	// StreamReorderingTime is the time an incoming WebTransport stream that cannot be associated
 | |
| 	// with a session is buffered.
 | |
| 	// 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.
 | |
| 	StreamReorderingTimeout 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 (s *Server) initialize() error {
 | |
| 	s.initOnce.Do(func() {
 | |
| 		s.initErr = s.init()
 | |
| 	})
 | |
| 	return s.initErr
 | |
| }
 | |
| 
 | |
| func (s *Server) init() error {
 | |
| 	s.ctx, s.ctxCancel = context.WithCancel(context.Background())
 | |
| 	timeout := s.StreamReorderingTimeout
 | |
| 	if timeout == 0 {
 | |
| 		timeout = 5 * time.Second
 | |
| 	}
 | |
| 	s.conns = newSessionManager(timeout)
 | |
| 	if s.CheckOrigin == nil {
 | |
| 		s.CheckOrigin = checkSameOrigin
 | |
| 	}
 | |
| 
 | |
| 	// configure the http3.Server
 | |
| 	if s.H3.AdditionalSettings == nil {
 | |
| 		s.H3.AdditionalSettings = make(map[uint64]uint64)
 | |
| 	}
 | |
| 	s.H3.AdditionalSettings[settingsEnableWebtransport] = 1
 | |
| 	s.H3.EnableDatagrams = true
 | |
| 	if s.H3.StreamHijacker != nil {
 | |
| 		return errors.New("StreamHijacker already set")
 | |
| 	}
 | |
| 	s.H3.StreamHijacker = func(ft http3.FrameType, qconn quic.Connection, str quic.Stream, err error) (bool /* hijacked */, error) {
 | |
| 		if isWebTransportError(err) {
 | |
| 			return true, nil
 | |
| 		}
 | |
| 		if ft != 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.
 | |
| 		id, err := quicvarint.Read(quicvarint.NewReader(str))
 | |
| 		if err != nil {
 | |
| 			if isWebTransportError(err) {
 | |
| 				return true, nil
 | |
| 			}
 | |
| 			return false, err
 | |
| 		}
 | |
| 		s.conns.AddStream(qconn, str, sessionID(id))
 | |
| 		return true, nil
 | |
| 	}
 | |
| 	s.H3.UniStreamHijacker = func(st http3.StreamType, qconn quic.Connection, str quic.ReceiveStream, err error) (hijacked bool) {
 | |
| 		if st != webTransportUniStreamType && !isWebTransportError(err) {
 | |
| 			return false
 | |
| 		}
 | |
| 		s.conns.AddUniStream(qconn, str)
 | |
| 		return true
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (s *Server) Serve(conn net.PacketConn) error {
 | |
| 	if err := s.initialize(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.H3.Serve(conn)
 | |
| }
 | |
| 
 | |
| // ServeQUICConn serves a single QUIC connection.
 | |
| func (s *Server) ServeQUICConn(conn quic.Connection) error {
 | |
| 	if err := s.initialize(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.H3.ServeQUICConn(conn)
 | |
| }
 | |
| 
 | |
| func (s *Server) ListenAndServe() error {
 | |
| 	if err := s.initialize(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.H3.ListenAndServe()
 | |
| }
 | |
| 
 | |
| func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
 | |
| 	if err := s.initialize(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return s.H3.ListenAndServeTLS(certFile, keyFile)
 | |
| }
 | |
| 
 | |
| func (s *Server) Close() 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.
 | |
| 	s.initOnce.Do(func() {})
 | |
| 
 | |
| 	if s.ctxCancel != nil {
 | |
| 		s.ctxCancel()
 | |
| 	}
 | |
| 	if s.conns != nil {
 | |
| 		s.conns.Close()
 | |
| 	}
 | |
| 	err := s.H3.Close()
 | |
| 	s.refCount.Wait()
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| func (s *Server) Upgrade(w http.ResponseWriter, r *http.Request) (*Session, error) {
 | |
| 	if r.Method != http.MethodConnect {
 | |
| 		return nil, fmt.Errorf("expected CONNECT request, got %s", r.Method)
 | |
| 	}
 | |
| 	if r.Proto != protocolHeader {
 | |
| 		return nil, fmt.Errorf("unexpected protocol: %s", r.Proto)
 | |
| 	}
 | |
| 	if v, ok := r.Header[webTransportDraftOfferHeaderKey]; !ok || len(v) != 1 || v[0] != "1" {
 | |
| 		return nil, fmt.Errorf("missing or invalid %s header", webTransportDraftOfferHeaderKey)
 | |
| 	}
 | |
| 	if !s.CheckOrigin(r) {
 | |
| 		return nil, errors.New("webtransport: request origin not allowed")
 | |
| 	}
 | |
| 	w.Header().Add(webTransportDraftHeaderKey, webTransportDraftHeaderValue)
 | |
| 	w.WriteHeader(http.StatusOK)
 | |
| 	w.(http.Flusher).Flush()
 | |
| 
 | |
| 	httpStreamer, ok := r.Body.(http3.HTTPStreamer)
 | |
| 	if !ok { // should never happen, unless quic-go changed the API
 | |
| 		return nil, errors.New("failed to take over HTTP stream")
 | |
| 	}
 | |
| 	str := httpStreamer.HTTPStream()
 | |
| 	sID := sessionID(str.StreamID())
 | |
| 
 | |
| 	hijacker, ok := w.(http3.Hijacker)
 | |
| 	if !ok { // should never happen, unless quic-go changed the API
 | |
| 		return nil, errors.New("failed to hijack")
 | |
| 	}
 | |
| 	return s.conns.AddSession(
 | |
| 		hijacker.StreamCreator(),
 | |
| 		sID,
 | |
| 		r.Body.(http3.HTTPStreamer).HTTPStream(),
 | |
| 	), nil
 | |
| }
 | |
| 
 | |
| // copied from https://github.com/gorilla/websocket
 | |
| func checkSameOrigin(r *http.Request) bool {
 | |
| 	origin := r.Header.Get("Origin")
 | |
| 	if origin == "" {
 | |
| 		return true
 | |
| 	}
 | |
| 	u, err := url.Parse(origin)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	return equalASCIIFold(u.Host, r.Host)
 | |
| }
 | |
| 
 | |
| // copied from https://github.com/gorilla/websocket
 | |
| func equalASCIIFold(s, t string) bool {
 | |
| 	for s != "" && t != "" {
 | |
| 		sr, size := utf8.DecodeRuneInString(s)
 | |
| 		s = s[size:]
 | |
| 		tr, size := utf8.DecodeRuneInString(t)
 | |
| 		t = t[size:]
 | |
| 		if sr == tr {
 | |
| 			continue
 | |
| 		}
 | |
| 		if 'A' <= sr && sr <= 'Z' {
 | |
| 			sr = sr + 'a' - 'A'
 | |
| 		}
 | |
| 		if 'A' <= tr && tr <= 'Z' {
 | |
| 			tr = tr + 'a' - 'A'
 | |
| 		}
 | |
| 		if sr != tr {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return s == t
 | |
| }
 |