package transport import ( "bytes" "io" "net" "net/http" "nhooyr.io/websocket" "github.com/gotd/td/internal/mtproxy/obfuscated2" "github.com/gotd/td/internal/proto/codec" "github.com/gotd/td/internal/tdsync" "github.com/gotd/td/internal/wsutil" ) type wsListener struct { addr net.Addr ch chan *wsServerConn closed *tdsync.Ready } // WebsocketListener creates new MTProto Websocket listener. func WebsocketListener(addr net.Addr) (net.Listener, http.Handler) { l := wsListener{ addr: addr, ch: make(chan *wsServerConn, 1), closed: tdsync.NewReady(), } return l, l } func (l wsListener) ServeHTTP(w http.ResponseWriter, r *http.Request) { wsConn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ Subprotocols: []string{"binary"}, }) if err != nil { w.WriteHeader(400) return } defer func() { _ = wsConn.Close(websocket.StatusNormalClosure, "Close") }() conn := wsutil.NetConn(wsConn) rw, md, err := obfuscated2.Accept(conn, nil) if err != nil { w.WriteHeader(400) return } var tag *bytes.Reader if md.Protocol[0] == codec.AbridgedClientStart[0] { // Abridged sends only byte for tag. tag = bytes.NewReader(md.Protocol[:1]) } else { tag = bytes.NewReader(md.Protocol[:]) } accepted := &wsServerConn{ closed: *tdsync.NewReady(), // Add codec tag in the begin of stream to emulate TCP fully. // MTProto sends codec tag in plain TCP connections, but not in obfuscated2 (Websocket/MTProxy). reader: io.MultiReader(tag, rw), writer: rw, Conn: conn, } reqCtx := r.Context().Done() closed := l.closed.Ready() // Pass connection to the Accept(). select { case <-reqCtx: return case <-closed: return case l.ch <- accepted: } // Await close or shutdown. select { case <-reqCtx: return case <-closed: return case <-accepted.closed.Ready(): } } func (l wsListener) Accept() (net.Conn, error) { r := l.closed.Ready() for { select { case <-r: return nil, net.ErrClosed case conn := <-l.ch: return conn, nil } } } func (l wsListener) Close() error { l.closed.Signal() return nil } func (l wsListener) Addr() net.Addr { return l.addr } type wsServerConn struct { closed tdsync.Ready reader io.Reader writer io.Writer net.Conn } func (c *wsServerConn) Read(p []byte) (int, error) { return c.reader.Read(p) } func (c *wsServerConn) Write(p []byte) (int, error) { return c.writer.Write(p) } func (c *wsServerConn) Close() error { c.closed.Signal() return nil }