Full Code of progrium/qmux for AI

main 475935a675d8 cached
64 files
85.7 KB
24.7k tokens
236 symbols
1 requests
Download .txt
Repository: progrium/qmux
Branch: main
Commit: 475935a675d8
Files: 64
Total size: 85.7 KB

Directory structure:
gitextract_r4pm3g2i/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── SPEC.md
├── demos/
│   └── groktunnel/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── golang/
│   ├── README.md
│   ├── codec/
│   │   ├── codec.go
│   │   ├── codec_test.go
│   │   ├── decoder.go
│   │   ├── encoder.go
│   │   ├── message.go
│   │   ├── message_close.go
│   │   ├── message_data.go
│   │   ├── message_eof.go
│   │   ├── message_open.go
│   │   ├── message_openconfirm.go
│   │   ├── message_openfailure.go
│   │   └── message_windowadjust.go
│   ├── go.mod
│   ├── go.sum
│   ├── mux/
│   │   ├── api.go
│   │   ├── doc.go
│   │   └── misc.go
│   ├── session/
│   │   ├── channel.go
│   │   ├── doc.go
│   │   ├── session.go
│   │   ├── session_test.go
│   │   ├── util.go
│   │   ├── util_buffer.go
│   │   ├── util_chanlist.go
│   │   └── util_window.go
│   └── transport/
│       ├── dial_io.go
│       ├── dial_net.go
│       ├── dial_ws.go
│       ├── doc.go
│       ├── listen.go
│       ├── listen_io.go
│       ├── listen_net.go
│       ├── listen_ws.go
│       └── transport_test.go
└── typescript/
    ├── Makefile
    ├── README.md
    ├── api.ts
    ├── channel.ts
    ├── codec/
    │   ├── codec_test.ts
    │   ├── decoder.ts
    │   ├── encoder.ts
    │   ├── index.ts
    │   └── message.ts
    ├── index.ts
    ├── internal.ts
    ├── session.ts
    ├── session_test.ts
    ├── transport/
    │   ├── deno/
    │   │   ├── tcp.ts
    │   │   └── websocket.ts
    │   └── websocket.ts
    ├── tsconfig.json
    └── util.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: progrium


================================================
FILE: .gitignore
================================================
TODO
typescript/dist 
demos/groktunnel/groktunnel

================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "denoland.vscode-deno",
    "golang.go"
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.unstable": true
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 Jeff Lindsay

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# qmux

qmux is a wire protocol for multiplexing connections or streams into a single connection. It is based on the [SSH Connection Protocol](https://tools.ietf.org/html/rfc4254#page-5), which is the simplest, longest running, most widely deployed TCP multiplexing protocol with flow control. 

It is meant as a drop-in layer for any stream capable transport (TCP, WebSocket, stdio, etc) to provide basic multiplexing. This brings any connection API to rough semantic parity with [QUIC](https://en.wikipedia.org/wiki/QUIC) multiplexing, so it can act as a stopgap or fallback when QUIC is not available. You can then design higher level protocols based on multiplexing semantics that sit on top of QUIC or any other streaming transport with qmux.

## Spec

The specification is [here](https://github.com/progrium/qmux/blob/main/SPEC.md). It is a simplified version of the [Channel Mechanism](https://tools.ietf.org/html/rfc4254#page-5) in the SSH Connection Protocol.

## Implementations

- [x] [Golang](https://github.com/progrium/qmux/tree/main/golang) (best reference)
- [x] [TypeScript](https://github.com/progrium/qmux/tree/main/typescript)
- [ ] Python
- [ ] C# (help wanted)

## Demos

- [groktunnel](https://github.com/progrium/qmux/tree/main/demos/groktunnel): Ephemeral localhost public forwarding system for HTTP similar to Ngrok in less than 150 lines of Go.

## About

Licensed MIT


================================================
FILE: SPEC.md
================================================
# qmux

qmux is a wire protocol for multiplexing connections or streams into a single connection.
It is a subset of the [SSH Connection Protocol](https://tools.ietf.org/html/rfc4254#page-5).

Features removed to simplify include channel types, channel requests, and "extended data"
messages that were used for STDERR data.

## Channels

   Either side may open a channel.  Multiple channels are multiplexed
   into a single connection.

   Channels are identified by numbers at each end.  The number referring
   to a channel may be different on each side.  Requests to open a
   channel contain the sender's channel number.  Any other channel-
   related messages contain the recipient's channel number for the
   channel.

   Channels are flow-controlled.  No data may be sent to a channel until
   a message is received to indicate that window space is available.

###  Opening a Channel

   When either side wishes to open a new channel, it allocates a local
   number for the channel.  It then sends the following message to the
   other side, and includes the local channel number and initial window
   size in the message.

      byte      QMUX_MSG_CHANNEL_OPEN
      uint32    sender channel
      uint32    initial window size
      uint32    maximum packet size

   The 'sender channel' is a local identifier for the channel used by the
   sender of this message.  The 'initial window size' specifies how many
   bytes of channel data can be sent to the sender of this message
   without adjusting the window. The 'maximum packet size' specifies the
   maximum size of an individual data packet that can be sent to the
   sender.  For example, one might want to use smaller packets for
   interactive connections to get better interactive response on slow
   links.

   The remote side then decides whether it can open the channel, and
   responds with either `QMUX_MSG_CHANNEL_OPEN_CONFIRMATION` or
   `QMUX_MSG_CHANNEL_OPEN_FAILURE`.

      byte      QMUX_MSG_CHANNEL_OPEN_CONFIRMATION
      uint32    recipient channel
      uint32    sender channel
      uint32    initial window size
      uint32    maximum packet size

   The 'recipient channel' is the channel number given in the original
   open request, and 'sender channel' is the channel number allocated by
   the other side.

      byte      QMUX_MSG_CHANNEL_OPEN_FAILURE
      uint32    recipient channel

###  Data Transfer

   The window size specifies how many bytes the other party can send
   before it must wait for the window to be adjusted.  Both parties use
   the following message to adjust the window.

      byte      QMUX_MSG_CHANNEL_WINDOW_ADJUST
      uint32    recipient channel
      uint32    bytes to add

   After receiving this message, the recipient MAY send the given number
   of bytes more than it was previously allowed to send; the window size
   is incremented.  Implementations MUST correctly handle window sizes
   of up to 2^32 - 1 bytes.  The window MUST NOT be increased above
   2^32 - 1 bytes.

   Data transfer is done with messages of the following type.

      byte      QMUX_MSG_CHANNEL_DATA
      uint32    recipient channel
      string    data

   The maximum amount of data allowed is determined by the maximum
   packet size for the channel, and the current window size, whichever
   is smaller.  The window size is decremented by the amount of data
   sent.  Both parties MAY ignore all extra data sent after the allowed
   window is empty.

   Implementations are expected to have some limit on the transport
   layer packet size.

###  Closing a Channel

   When a party will no longer send more data to a channel, it SHOULD
   send `QMUX_MSG_CHANNEL_EOF`.

      byte      QMUX_MSG_CHANNEL_EOF
      uint32    recipient channel

   No explicit response is sent to this message.  However, the
   application may send EOF to whatever is at the other end of the
   channel.  Note that the channel remains open after this message, and
   more data may still be sent in the other direction.  This message
   does not consume window space and can be sent even if no window space
   is available.

   When either party wishes to terminate the channel, it sends
   `QMUX_MSG_CHANNEL_CLOSE`.  Upon receiving this message, a party MUST
   send back an `QMUX_MSG_CHANNEL_CLOSE` unless it has already sent this
   message for the channel.  The channel is considered closed for a
   party when it has both sent and received `QMUX_MSG_CHANNEL_CLOSE`, and
   the party may then reuse the channel number.  A party MAY send
   `QMUX_MSG_CHANNEL_CLOSE` without having sent or received
   `QMUX_MSG_CHANNEL_EOF`.

      byte      QMUX_MSG_CHANNEL_CLOSE
      uint32    recipient channel

   This message does not consume window space and can be sent even if no
   window space is available.

   It is RECOMMENDED that all data sent before this message be delivered
   to the actual destination, if possible.

## Summary of Message Numbers

   The following is a summary of messages and their associated message
   number byte value.

            QMUX_MSG_CHANNEL_OPEN                    100
            QMUX_MSG_CHANNEL_OPEN_CONFIRMATION       101
            QMUX_MSG_CHANNEL_OPEN_FAILURE            102
            QMUX_MSG_CHANNEL_WINDOW_ADJUST           103
            QMUX_MSG_CHANNEL_DATA                    104
            QMUX_MSG_CHANNEL_EOF                     105
            QMUX_MSG_CHANNEL_CLOSE                   106

## Data Type Representations Used

   byte

      A byte represents an arbitrary 8-bit value (octet).  Fixed length
      data is sometimes represented as an array of bytes, written
      byte[n], where n is the number of bytes in the array.

   uint32

      Represents a 32-bit unsigned integer.  Stored as four bytes in the
      order of decreasing significance (network byte order).  For
      example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4
      aa.

   string

      Arbitrary length binary string.  Strings are allowed to contain
      arbitrary binary data, including null characters and 8-bit
      characters.  They are stored as a uint32 containing its length
      (number of bytes that follow) and zero (= empty string) or more
      bytes that are the value of the string.  Terminating null
      characters are not used.

================================================
FILE: demos/groktunnel/README.md
================================================
# groktunnel

Expose localhost HTTP servers with a public URL

## Build
```
$ go build
```

## Try it out

First we run the groktunnel server. Normally this would be run on a server, but by default uses `vcap.me`
for a hostname which resolves all of its subdomains to localhost.
```
$ ./groktunnel
2021/04/29 16:10:35 groktunnel server [vcap.me] ready!
```

Now run a local web server. Here is how to run a server listing a file directory with Python:
```
$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
```

Then we run groktunnel as a client by giving it the local port to expose.
```
$ ./groktunnel 8000
port 8000 http available at:
http://y8eyshnpol.vcap.me:9999
```

That address should serve the same content as the local web server on 8000. For added effect,
run both client and server with `-p 80`, which will require root to run the server.

## About

This uses qmux between the client and server to tunnel subdomain requests down to the client.
This is done over a hijacked http connection after a tunnel is established and a new subdomain
vhost is setup. 

Not counting dependencies, this whole system is done in under 150 lines. This is the 5th or 6th
implementation of this system since the original [localtunnel](https://github.com/progrium/localtunnel)
in 2010, which was then cloned many times. It was then commercialized by [Ngrok](https://ngrok.com/).

================================================
FILE: demos/groktunnel/go.mod
================================================
module github.com/progrium/qmux/demos/groktunnel

go 1.16

require (
	github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b
	github.com/progrium/qmux/golang v0.0.0-20210428195120-05a36e97c488
)


================================================
FILE: demos/groktunnel/go.sum
================================================
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE=
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=
github.com/progrium/qmux/golang v0.0.0-20210428195120-05a36e97c488 h1:wG/GkKO0L/98gyqxx0Jm8CyVf/bPW3XUY8abCsFc5Yw=
github.com/progrium/qmux/golang v0.0.0-20210428195120-05a36e97c488/go.mod h1:Z2EPtydgPrcZxO50GhkzTGgdWjA5PPHsZkoq6KTxPVE=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=


================================================
FILE: demos/groktunnel/main.go
================================================
package main

import (
	"bufio"
	"context"
	"crypto/rand"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"strings"
	"time"

	vhost "github.com/inconshreveable/go-vhost"
	"github.com/progrium/qmux/golang/session"
)

func main() {
	var port = flag.String("p", "9999", "server port to use")
	var host = flag.String("h", "vcap.me", "server hostname to use")
	var addr = flag.String("b", "127.0.0.1", "ip to bind [server only]")
	flag.Parse()

	// client usage: groktunnel [-h=<server hostname>] <local port>
	if flag.Arg(0) != "" {
		conn, err := net.Dial("tcp", net.JoinHostPort(*host, *port))
		fatal(err)
		client := httputil.NewClientConn(conn, bufio.NewReader(conn))
		req, err := http.NewRequest("GET", "/", nil)
		req.Host = net.JoinHostPort(*host, *port)
		fatal(err)
		client.Write(req)
		resp, _ := client.Read(req)
		fmt.Printf("port %s http available at:\n", flag.Arg(0))
		fmt.Printf("http://%s\n", resp.Header.Get("X-Public-Host"))
		c, _ := client.Hijack()
		sess := session.New(c)
		defer sess.Close()
		for {
			ch, err := sess.Accept()
			fatal(err)
			conn, err := net.Dial("tcp", "127.0.0.1:"+flag.Arg(0))
			fatal(err)
			go join(conn, ch)
		}
		return
	}

	// server usage: groktunnel [-h=<hostname>] [-b=<bind ip>]
	l, err := net.Listen("tcp", net.JoinHostPort(*addr, *port))
	fatal(err)
	defer l.Close()
	vmux, err := vhost.NewHTTPMuxer(l, 1*time.Second)
	fatal(err)

	go serve(vmux, *host, *port)

	log.Printf("groktunnel server [%s] ready!\n", *host)
	for {
		conn, err := vmux.NextError()
		fmt.Println(err)
		if conn != nil {
			conn.Close()
		}
	}
}

func serve(vmux *vhost.HTTPMuxer, host, port string) {
	ml, err := vmux.Listen(net.JoinHostPort(host, port))
	fatal(err)
	srv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		publicHost := strings.TrimSuffix(net.JoinHostPort(newSubdomain()+host, port), ":80")
		pl, err := vmux.Listen(publicHost)
		fatal(err)
		w.Header().Add("X-Public-Host", publicHost)
		w.Header().Add("Connection", "close")
		w.WriteHeader(http.StatusOK)
		conn, _, _ := w.(http.Hijacker).Hijack()
		sess := session.New(conn)
		defer sess.Close()
		log.Printf("%s: start session", publicHost)
		go func() {
			for {
				conn, err := pl.Accept()
				if err != nil {
					log.Println(err)
					return
				}
				ch, err := sess.Open(context.Background())
				if err != nil {
					log.Println(err)
					return
				}
				go join(ch, conn)
			}
		}()
		sess.Wait()
		log.Printf("%s: end session", publicHost)
	})}
	srv.Serve(ml)
}

func join(a io.ReadWriteCloser, b io.ReadWriteCloser) {
	go io.Copy(b, a)
	io.Copy(a, b)
	a.Close()
	b.Close()
}

func newSubdomain() string {
	b := make([]byte, 10)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	letters := []rune("abcdefghijklmnopqrstuvwxyz1234567890")
	r := make([]rune, 10)
	for i := range r {
		r[i] = letters[int(b[i])*len(letters)/256]
	}
	return string(r) + "."
}

func fatal(err error) {
	if err != nil {
		log.Fatal(err)
	}
}


================================================
FILE: golang/README.md
================================================
# qmux for Go

An implementation of qmux for multiplexing any reliable `io.ReadWriteCloser`.

## Using qmux in Go

```
go get github.com/progrium/qmux/golang
```

You can create a qmux session from any `io.ReadWriteCloser` with `session.New`:

```go
package main

import (
    "net"
    "io"
    "context"

    "github.com/progrium/qmux/golang/session"
)

func main() {
    conn, err := net.Dial("tcp", "localhost:9999")
    if err != nil {
        panic(err)
    }
    
    sess := session.New(conn)
    defer sess.Close() // closes underlying conn
    
    ch, err := sess.Open(context.Background())
    if err != nil {
        panic(err)
    }
    defer ch.Close()

    io.WriteString(ch, "Hello world\n")
}

```

However it can be convenient to use the builtin transport dialers and listeners. 

```go
package main

import (
    "io/ioutil"

    "github.com/progrium/qmux/golang/transport"
)

func main() {
    t, err := transport.ListenTCP("localhost:9999")
    if err != nil {
        panic(err)
    }
    defer t.Close()
    
    sess, err := t.Accept()
    if err != nil {
        panic(err)
    }
    defer sess.Close()
    
    ch, err := sess.Accept()
    if err != nil {
        panic(err)
    }
    defer ch.Close()
    
    b, err := ioutil.ReadAll(ch)
    if err != nil {
        panic(err)
    }
    os.Stdout.Write(b) // "Hello world\n" if connected with earlier program
}

```

================================================
FILE: golang/codec/codec.go
================================================
// Package codec implements encoding and decoding of qmux messages.
package codec

import "io"

var (
	DebugMessages io.Writer
	DebugBytes    io.Writer
)


================================================
FILE: golang/codec/codec_test.go
================================================
package codec

import (
	"bytes"
	"testing"
)

func TestMarshalUnmarshal(t *testing.T) {
	tests := []struct {
		in  Message
		out Unmarshaler
	}{
		{
			in: CloseMessage{
				ChannelID: 10,
			},
			out: &CloseMessage{},
		},
		{
			in: DataMessage{
				ChannelID: 10,
				Length:    5,
				Data:      []byte("Hello"),
			},
			out: &DataMessage{},
		},
		{
			in: EOFMessage{
				ChannelID: 10,
			},
			out: &EOFMessage{},
		},
		{
			in: OpenMessage{
				SenderID:      10,
				WindowSize:    1024,
				MaxPacketSize: 1 << 31,
			},
			out: &OpenMessage{},
		},
		{
			in: OpenConfirmMessage{
				ChannelID:     20,
				SenderID:      10,
				WindowSize:    1024,
				MaxPacketSize: 1 << 31,
			},
			out: &OpenConfirmMessage{},
		},
		{
			in: OpenFailureMessage{
				ChannelID: 20,
			},
			out: &OpenFailureMessage{},
		},
		{
			in: WindowAdjustMessage{
				ChannelID:       20,
				AdditionalBytes: 1024,
			},
			out: &WindowAdjustMessage{},
		},
	}
	for _, test := range tests {
		b, err := Marshal(test.in)
		if err != nil {
			t.Fatal(err)
		}
		if err := Unmarshal(b, test.out); err != nil {
			t.Fatal(err)
		}
		bb, err := Marshal(test.out)
		if err != nil {
			t.Fatal(err)
		}
		if !bytes.Equal(b, bb) {
			t.Fatal("bytes not equal")
		}
		if test.in.String() != test.out.(Message).String() {
			t.Fatal("strings not equal")
		}
	}

}

func TestEncodeDecode(t *testing.T) {
	tests := []struct {
		in Message
		id uint32
		ok bool
	}{
		{
			in: CloseMessage{
				ChannelID: 10,
			},
			id: 10,
			ok: true,
		},
		{
			in: DataMessage{
				ChannelID: 10,
				Length:    5,
				Data:      []byte("Hello"),
			},
			id: 10,
			ok: true,
		},
		{
			in: EOFMessage{
				ChannelID: 10,
			},
			id: 10,
			ok: true,
		},
		{
			in: OpenMessage{
				SenderID:      10,
				WindowSize:    1024,
				MaxPacketSize: 1 << 31,
			},
			id: 0,
			ok: false,
		},
		{
			in: OpenConfirmMessage{
				ChannelID:     20,
				SenderID:      10,
				WindowSize:    1024,
				MaxPacketSize: 1 << 31,
			},
			id: 20,
			ok: true,
		},
		{
			in: OpenFailureMessage{
				ChannelID: 20,
			},
			id: 20,
			ok: true,
		},
		{
			in: WindowAdjustMessage{
				ChannelID:       20,
				AdditionalBytes: 1024,
			},
			id: 20,
			ok: true,
		},
	}
	for _, test := range tests {
		var buf bytes.Buffer
		enc := NewEncoder(&buf)
		if err := enc.Encode(test.in); err != nil {
			t.Fatal(err)
		}
		dec := NewDecoder(&buf)
		m, err := dec.Decode()
		if err != nil {
			t.Fatal(err)
		}
		id, ok := m.Channel()
		if id != test.id {
			t.Fatal("id not equal")
		}
		if ok != test.ok {
			t.Fatal("ok not equal")
		}
	}

}


================================================
FILE: golang/codec/decoder.go
================================================
package codec

import (
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"os"
	"sync"
	"syscall"
)

type Decoder struct {
	r io.Reader
	sync.Mutex
}

func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{r: r}
}

func (dec *Decoder) Decode() (Message, error) {
	dec.Lock()
	defer dec.Unlock()

	packet, err := readPacket(dec.r)
	if err != nil {
		return nil, err
	}

	if DebugBytes != nil {
		fmt.Fprintln(DebugBytes, ">>DEC", packet)
	}

	return decode(packet)
}

func readPacket(c io.Reader) ([]byte, error) {
	msgNum := make([]byte, 1)
	_, err := c.Read(msgNum)
	if err != nil {
		var syscallErr *os.SyscallError
		if errors.As(err, &syscallErr) && syscallErr.Err == syscall.ECONNRESET {
			return nil, io.EOF
		}
		return nil, err
	}

	rest := make([]byte, payloadSizes[msgNum[0]])
	_, err = c.Read(rest)
	if err != nil {
		return nil, err
	}

	packet := append(msgNum, rest...)

	if msgNum[0] == msgChannelData {
		dataSize := binary.BigEndian.Uint32(rest[4:8])
		data := make([]byte, dataSize)
		_, err := c.Read(data)
		if err != nil {
			return nil, err
		}

		packet = append(packet, data...)
	}

	return packet, nil
}

func decode(packet []byte) (Message, error) {
	var msg Message
	switch packet[0] {
	case msgChannelOpen:
		msg = new(OpenMessage)
	case msgChannelData:
		msg = new(DataMessage)
	case msgChannelOpenConfirm:
		msg = new(OpenConfirmMessage)
	case msgChannelOpenFailure:
		msg = new(OpenFailureMessage)
	case msgChannelWindowAdjust:
		msg = new(WindowAdjustMessage)
	case msgChannelEOF:
		msg = new(EOFMessage)
	case msgChannelClose:
		msg = new(CloseMessage)
	default:
		return nil, fmt.Errorf("qmux: unexpected message type %d", packet[0])
	}
	if err := Unmarshal(packet, msg); err != nil {
		return nil, err
	}
	if DebugMessages != nil {
		fmt.Fprintln(DebugMessages, ">>DEC", msg)
	}
	return msg, nil
}

type Unmarshaler interface {
	UnmarshalMux([]byte) error
}

func Unmarshal(b []byte, v interface{}) error {
	u, ok := v.(Unmarshaler)
	if !ok {
		return fmt.Errorf("qmux: unmarshal not supported for value %#v", v)
	}
	return u.UnmarshalMux(b)
}


================================================
FILE: golang/codec/encoder.go
================================================
package codec

import (
	"fmt"
	"io"
	"sync"
)

type Encoder struct {
	w io.Writer
	sync.Mutex
}

func NewEncoder(w io.Writer) *Encoder {
	return &Encoder{w: w}
}

func (enc *Encoder) Encode(msg interface{}) error {
	enc.Lock()
	defer enc.Unlock()

	if DebugMessages != nil {
		fmt.Fprintln(DebugMessages, "<<ENC", msg)
	}

	b, err := Marshal(msg)
	if err != nil {
		return err
	}

	if DebugBytes != nil {
		fmt.Fprintln(DebugBytes, "<<ENC", b)
	}

	_, err = enc.w.Write(b)
	return err
}

type Marshaler interface {
	MarshalMux() ([]byte, error)
}

func Marshal(v interface{}) ([]byte, error) {
	m, ok := v.(Marshaler)
	if !ok {
		return []byte{}, fmt.Errorf("qmux: unable to marshal type")
	}
	return m.MarshalMux()
}


================================================
FILE: golang/codec/message.go
================================================
package codec

const (
	msgChannelOpen = iota + 100
	msgChannelOpenConfirm
	msgChannelOpenFailure
	msgChannelWindowAdjust
	msgChannelData
	msgChannelEOF
	msgChannelClose
)

var (
	payloadSizes = map[byte]int{
		msgChannelOpen:         12,
		msgChannelOpenConfirm:  16,
		msgChannelOpenFailure:  4,
		msgChannelWindowAdjust: 8,
		msgChannelData:         8,
		msgChannelEOF:          4,
		msgChannelClose:        4,
	}
)

type Message interface {
	Channel() (uint32, bool)
	String() string
}


================================================
FILE: golang/codec/message_close.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type CloseMessage struct {
	ChannelID uint32
}

func (msg CloseMessage) String() string {
	return fmt.Sprintf("{CloseMessage ChannelID:%d}", msg.ChannelID)
}

func (msg CloseMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg CloseMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelClose]+1)
	packet[0] = msgChannelClose
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	return packet, nil
}

func (msg *CloseMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	return nil
}


================================================
FILE: golang/codec/message_data.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type DataMessage struct {
	ChannelID uint32
	Length    uint32
	Data      []byte
}

func (msg DataMessage) String() string {
	return fmt.Sprintf("{DataMessage ChannelID:%d Length:%d Data: ... }",
		msg.ChannelID, msg.Length)
}

func (msg DataMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg DataMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelData]+1)
	packet[0] = msgChannelData
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	binary.BigEndian.PutUint32(packet[5:9], msg.Length)
	return append(packet, msg.Data...), nil
}

func (msg *DataMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	msg.Length = binary.BigEndian.Uint32(b[5:9])
	msg.Data = b[9:]
	return nil
}


================================================
FILE: golang/codec/message_eof.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type EOFMessage struct {
	ChannelID uint32
}

func (msg EOFMessage) String() string {
	return fmt.Sprintf("{EOFMessage ChannelID:%d}", msg.ChannelID)
}

func (msg EOFMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg EOFMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelEOF]+1)
	packet[0] = msgChannelEOF
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	return packet, nil
}

func (msg *EOFMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	return nil
}


================================================
FILE: golang/codec/message_open.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type OpenMessage struct {
	SenderID      uint32
	WindowSize    uint32
	MaxPacketSize uint32
}

func (msg OpenMessage) String() string {
	return fmt.Sprintf("{OpenMessage SenderID:%d WindowSize:%d MaxPacketSize:%d}",
		msg.SenderID, msg.WindowSize, msg.MaxPacketSize)
}

func (msg OpenMessage) Channel() (uint32, bool) {
	return 0, false
}

func (msg OpenMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelOpen]+1)
	packet[0] = msgChannelOpen
	binary.BigEndian.PutUint32(packet[1:5], msg.SenderID)
	binary.BigEndian.PutUint32(packet[5:9], msg.WindowSize)
	binary.BigEndian.PutUint32(packet[9:13], msg.MaxPacketSize)
	return packet, nil
}

func (msg *OpenMessage) UnmarshalMux(b []byte) error {
	msg.SenderID = binary.BigEndian.Uint32(b[1:5])
	msg.WindowSize = binary.BigEndian.Uint32(b[5:9])
	msg.MaxPacketSize = binary.BigEndian.Uint32(b[9:13])
	return nil
}


================================================
FILE: golang/codec/message_openconfirm.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type OpenConfirmMessage struct {
	ChannelID     uint32
	SenderID      uint32
	WindowSize    uint32
	MaxPacketSize uint32
}

func (msg OpenConfirmMessage) String() string {
	return fmt.Sprintf("{OpenConfirmMessage ChannelID:%d SenderID:%d WindowSize:%d MaxPacketSize:%d}",
		msg.ChannelID, msg.SenderID, msg.WindowSize, msg.MaxPacketSize)
}

func (msg OpenConfirmMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg OpenConfirmMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelOpenConfirm]+1)
	packet[0] = msgChannelOpenConfirm
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	binary.BigEndian.PutUint32(packet[5:9], msg.SenderID)
	binary.BigEndian.PutUint32(packet[9:13], msg.WindowSize)
	binary.BigEndian.PutUint32(packet[13:17], msg.MaxPacketSize)
	return packet, nil
}

func (msg *OpenConfirmMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	msg.SenderID = binary.BigEndian.Uint32(b[5:9])
	msg.WindowSize = binary.BigEndian.Uint32(b[9:13])
	msg.MaxPacketSize = binary.BigEndian.Uint32(b[13:17])
	return nil
}


================================================
FILE: golang/codec/message_openfailure.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type OpenFailureMessage struct {
	ChannelID uint32
}

func (msg OpenFailureMessage) String() string {
	return fmt.Sprintf("{OpenFailureMessage ChannelID:%d}", msg.ChannelID)
}

func (msg OpenFailureMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg OpenFailureMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelOpenFailure]+1)
	packet[0] = msgChannelOpenFailure
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	return packet, nil
}

func (msg *OpenFailureMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	return nil
}


================================================
FILE: golang/codec/message_windowadjust.go
================================================
package codec

import (
	"encoding/binary"
	"fmt"
)

type WindowAdjustMessage struct {
	ChannelID       uint32
	AdditionalBytes uint32
}

func (msg WindowAdjustMessage) String() string {
	return fmt.Sprintf("{WindowAdjustMessage ChannelID:%d AdditionalBytes:%d}",
		msg.ChannelID, msg.AdditionalBytes)
}

func (msg WindowAdjustMessage) Channel() (uint32, bool) {
	return msg.ChannelID, true
}

func (msg WindowAdjustMessage) MarshalMux() ([]byte, error) {
	packet := make([]byte, payloadSizes[msgChannelWindowAdjust]+1)
	packet[0] = msgChannelWindowAdjust
	binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID)
	binary.BigEndian.PutUint32(packet[5:9], msg.AdditionalBytes)
	return packet, nil
}

func (msg *WindowAdjustMessage) UnmarshalMux(b []byte) error {
	msg.ChannelID = binary.BigEndian.Uint32(b[1:5])
	msg.AdditionalBytes = binary.BigEndian.Uint32(b[5:9])
	return nil
}


================================================
FILE: golang/go.mod
================================================
module github.com/progrium/qmux/golang

go 1.16

require golang.org/x/net v0.0.0-20210420210106-798c2154c571 // indirect


================================================
FILE: golang/go.sum
================================================
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=


================================================
FILE: golang/mux/api.go
================================================
package mux

import (
	"context"
	"io"
)

// Session is a bi-directional channel muxing session on a given transport.
type Session interface {
	// Close closes the underlying transport.
	// Any blocked Accept operations will be unblocked and return errors.
	Close() error

	// Open establishes a new channel with the other end.
	Open(ctx context.Context) (Channel, error)

	// Accept waits for and returns the next incoming channel.
	Accept() (Channel, error)
}

// Channel is an ordered, reliable, flow-controlled, duplex stream
// that is multiplexed over a qmux session.
type Channel interface {
	// Read reads up to len(data) bytes from the channel.
	Read(data []byte) (int, error)

	// Write writes len(data) bytes to the channel.
	Write(data []byte) (int, error)

	// Close signals end of channel use. No data may be sent after this
	// call.
	Close() error

	// CloseWrite signals the end of sending data.
	// The other side may still send data
	CloseWrite() error

	// ID returns the unique identifier of this channel
	// within the session
	ID() uint32
}

// Transport is an interface describing what is needed for a session
type Transport interface {
	io.Reader
	io.Writer
	io.Closer
}


================================================
FILE: golang/mux/doc.go
================================================
// Package mux provides a generic muxing API.
package mux


================================================
FILE: golang/mux/misc.go
================================================
package mux

import "fmt"

type waiter interface {
	Wait() error
}

// Wait blocks until the session transport has shut down, and returns the
// error causing the shutdown.
func Wait(sess Session) error {
	w, ok := sess.(waiter)
	if !ok {
		return fmt.Errorf("Session does not support waiting")
	}
	return w.Wait()
}


================================================
FILE: golang/session/channel.go
================================================
package session

import (
	"errors"
	"fmt"
	"io"
	"sync"

	"github.com/progrium/qmux/golang/codec"
)

type channelDirection uint8

const (
	channelInbound channelDirection = iota
	channelOutbound
)

// Channel is an implementation of the Channel interface that works
// with the Session class.
type Channel struct {

	// R/O after creation
	localId, remoteId uint32

	// maxIncomingPayload and maxRemotePayload are the maximum
	// payload sizes of normal and extended data packets for
	// receiving and sending, respectively. The wire packet will
	// be 9 or 13 bytes larger (excluding encryption overhead).
	maxIncomingPayload uint32
	maxRemotePayload   uint32

	session *Session

	// direction contains either channelOutbound, for channels created
	// locally, or channelInbound, for channels created by the peer.
	direction channelDirection

	// Pending internal channel messages.
	msg chan codec.Message

	sentEOF bool

	// thread-safe data
	remoteWin window
	pending   *buffer

	// windowMu protects myWindow, the flow-control window.
	windowMu sync.Mutex
	myWindow uint32

	// writeMu serializes calls to session.conn.Write() and
	// protects sentClose and packetPool. This mutex must be
	// different from windowMu, as writePacket can block if there
	// is a key exchange pending.
	writeMu   sync.Mutex
	sentClose bool

	// packet buffer for writing
	packetBuf []byte
}

// ID returns the unique identifier of this channel
// within the session
func (ch *Channel) ID() uint32 {
	return ch.localId
}

// CloseWrite signals the end of sending data.
// The other side may still send data
func (ch *Channel) CloseWrite() error {
	ch.sentEOF = true
	return ch.send(codec.EOFMessage{
		ChannelID: ch.remoteId})
}

// Close signals end of channel use. No data may be sent after this
// call.
func (ch *Channel) Close() error {
	return ch.send(codec.CloseMessage{
		ChannelID: ch.remoteId})
}

// Write writes len(data) bytes to the channel.
func (ch *Channel) Write(data []byte) (n int, err error) {
	if ch.sentEOF {
		return 0, io.EOF
	}

	for len(data) > 0 {
		space := min(ch.maxRemotePayload, len(data))
		if space, err = ch.remoteWin.reserve(space); err != nil {
			return n, err
		}

		toSend := data[:space]

		if err = ch.session.enc.Encode(codec.DataMessage{
			ChannelID: ch.remoteId,
			Length:    uint32(len(toSend)),
			Data:      toSend,
		}); err != nil {
			return n, err
		}

		n += len(toSend)
		data = data[len(toSend):]
	}

	return n, err
}

// Read reads up to len(data) bytes from the channel.
func (c *Channel) Read(data []byte) (n int, err error) {
	n, err = c.pending.Read(data)

	if n > 0 {
		err = c.adjustWindow(uint32(n))
		// sendWindowAdjust can return io.EOF if the remote
		// peer has closed the connection, however we want to
		// defer forwarding io.EOF to the caller of Read until
		// the buffer has been drained.
		if n > 0 && err == io.EOF {
			err = nil
		}
	}
	return n, err
}

// writePacket sends a packet. If the packet is a channel close, it updates
// sentClose. This method takes the lock c.writeMu.
func (ch *Channel) send(msg interface{}) error {
	ch.writeMu.Lock()
	defer ch.writeMu.Unlock()

	if ch.sentClose {
		return io.EOF
	}

	if _, ok := msg.(codec.CloseMessage); ok {
		ch.sentClose = true
	}

	return ch.session.enc.Encode(msg)
}

func (c *Channel) adjustWindow(n uint32) error {
	c.windowMu.Lock()
	// Since myWindow is managed on our side, and can never exceed
	// the initial window setting, we don't worry about overflow.
	c.myWindow += uint32(n)
	c.windowMu.Unlock()
	return c.send(codec.WindowAdjustMessage{
		ChannelID:       c.remoteId,
		AdditionalBytes: uint32(n),
	})
}

func (c *Channel) close() {
	c.pending.eof()
	close(c.msg)
	c.writeMu.Lock()
	// This is not necessary for a normal channel teardown, but if
	// there was another error, it is.
	c.sentClose = true
	c.writeMu.Unlock()
	// Unblock writers.
	c.remoteWin.close()
}

// responseMessageReceived is called when a success or failure message is
// received on a channel to check that such a message is reasonable for the
// given channel.
func (ch *Channel) responseMessageReceived() error {
	if ch.direction == channelInbound {
		return errors.New("qmux: channel response message received on inbound channel")
	}
	return nil
}

func (ch *Channel) handle(msg codec.Message) error {
	switch m := msg.(type) {
	case *codec.DataMessage:
		return ch.handleData(m)

	case *codec.CloseMessage:
		ch.send(codec.CloseMessage{
			ChannelID: ch.remoteId,
		})
		ch.session.chans.remove(ch.localId)
		ch.close()
		return nil

	case *codec.EOFMessage:
		ch.pending.eof()
		return nil

	case *codec.WindowAdjustMessage:
		if !ch.remoteWin.add(m.AdditionalBytes) {
			return fmt.Errorf("qmux: invalid window update for %d bytes", m.AdditionalBytes)
		}
		return nil

	case *codec.OpenConfirmMessage:
		if err := ch.responseMessageReceived(); err != nil {
			return err
		}
		if m.MaxPacketSize < minPacketLength || m.MaxPacketSize > maxPacketLength {
			return fmt.Errorf("qmux: invalid MaxPacketSize %d from peer", m.MaxPacketSize)
		}
		ch.remoteId = m.SenderID
		ch.maxRemotePayload = m.MaxPacketSize
		ch.remoteWin.add(m.WindowSize)
		ch.msg <- m
		return nil

	case *codec.OpenFailureMessage:
		if err := ch.responseMessageReceived(); err != nil {
			return err
		}
		ch.session.chans.remove(m.ChannelID)
		ch.msg <- m
		return nil

	default:
		return fmt.Errorf("qmux: invalid channel message %v", msg)
	}
}

func (ch *Channel) handleData(msg *codec.DataMessage) error {
	if msg.Length > ch.maxIncomingPayload {
		// TODO(hanwen): should send Disconnect?
		return errors.New("qmux: incoming packet exceeds maximum payload size")
	}

	if msg.Length != uint32(len(msg.Data)) {
		return errors.New("qmux: wrong packet length")
	}

	ch.windowMu.Lock()
	if ch.myWindow < msg.Length {
		ch.windowMu.Unlock()
		// TODO(hanwen): should send Disconnect with reason?
		return errors.New("qmux: remote side wrote too much")
	}
	ch.myWindow -= msg.Length
	ch.windowMu.Unlock()

	ch.pending.write(msg.Data)
	return nil
}


================================================
FILE: golang/session/doc.go
================================================
// Package session implements a qmux session and channel API.
package session


================================================
FILE: golang/session/session.go
================================================
package session

import (
	"context"
	"fmt"
	"io"
	"sync"

	"github.com/progrium/qmux/golang/codec"
	"github.com/progrium/qmux/golang/mux"
)

const (
	minPacketLength = 9
	maxPacketLength = 1 << 31

	// channelMaxPacket contains the maximum number of bytes that will be
	// sent in a single packet. As per RFC 4253, section 6.1, 32k is also
	// the minimum.
	channelMaxPacket = 1 << 15
	// We follow OpenSSH here.
	channelWindowSize = 64 * channelMaxPacket

	// chanSize sets the amount of buffering qmux connections. This is
	// primarily for testing: setting chanSize=0 uncovers deadlocks more
	// quickly.
	chanSize = 16
)

// Session is a bi-directional channel muxing session on a given transport.
type Session struct {
	t     mux.Transport
	chans chanList

	enc *codec.Encoder
	dec *codec.Decoder

	inbox chan mux.Channel

	errCond *sync.Cond
	err     error
	closeCh chan bool
}

// NewSession returns a session that runs over the given transport.
func New(t mux.Transport) *Session {
	if t == nil {
		return nil
	}
	s := &Session{
		t:       t,
		enc:     codec.NewEncoder(t),
		dec:     codec.NewDecoder(t),
		inbox:   make(chan mux.Channel, chanSize),
		errCond: sync.NewCond(new(sync.Mutex)),
		closeCh: make(chan bool, 1),
	}
	go s.loop()
	return s
}

// Close closes the underlying transport.
func (s *Session) Close() error {
	s.t.Close()
	return nil
}

// Wait blocks until the transport has shut down, and returns the
// error causing the shutdown.
func (s *Session) Wait() error {
	s.errCond.L.Lock()
	defer s.errCond.L.Unlock()
	for s.err == nil {
		s.errCond.Wait()
	}
	return s.err
}

// Accept waits for and returns the next incoming channel.
func (s *Session) Accept() (mux.Channel, error) {
	select {
	case ch := <-s.inbox:
		return ch, nil
	case <-s.closeCh:
		return nil, io.EOF
	}
}

// Open establishes a new channel with the other end.
func (s *Session) Open(ctx context.Context) (mux.Channel, error) {
	ch := s.newChannel(channelOutbound)
	ch.maxIncomingPayload = channelMaxPacket

	if err := s.enc.Encode(codec.OpenMessage{
		WindowSize:    ch.myWindow,
		MaxPacketSize: ch.maxIncomingPayload,
		SenderID:      ch.localId,
	}); err != nil {
		return nil, err
	}

	var m codec.Message

	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	case m = <-ch.msg:
		if m == nil {
			return nil, fmt.Errorf("qmux: channel closed early during open")
		}
	}

	switch msg := m.(type) {
	case *codec.OpenConfirmMessage:
		return ch, nil
	case *codec.OpenFailureMessage:
		return nil, fmt.Errorf("qmux: channel open failed on remote side")
	default:
		return nil, fmt.Errorf("qmux: unexpected packet in response to channel open: %v", msg)
	}
}

func (s *Session) newChannel(direction channelDirection) *Channel {
	ch := &Channel{
		remoteWin: window{Cond: sync.NewCond(new(sync.Mutex))},
		myWindow:  channelWindowSize,
		pending:   newBuffer(),
		direction: direction,
		msg:       make(chan codec.Message, chanSize),
		session:   s,
		packetBuf: make([]byte, 0),
	}
	ch.localId = s.chans.add(ch)
	return ch
}

// loop runs the connection machine. It will process packets until an
// error is encountered. To synchronize on loop exit, use session.Wait.
func (s *Session) loop() {
	var err error
	for err == nil {
		err = s.onePacket()
	}

	for _, ch := range s.chans.dropAll() {
		ch.close()
	}

	s.t.Close()
	s.closeCh <- true

	s.errCond.L.Lock()
	s.err = err
	s.errCond.Broadcast()
	s.errCond.L.Unlock()
}

// onePacket reads and processes one packet.
func (s *Session) onePacket() error {
	var err error
	var msg codec.Message

	msg, err = s.dec.Decode()
	if err != nil {
		return err
	}

	id, isChan := msg.Channel()
	if !isChan {
		return s.handleOpen(msg.(*codec.OpenMessage))
	}

	ch := s.chans.getChan(id)
	if ch == nil {
		return fmt.Errorf("qmux: invalid channel %d", id)
	}

	return ch.handle(msg)
}

// handleChannelOpen schedules a channel to be Accept()ed.
func (s *Session) handleOpen(msg *codec.OpenMessage) error {
	if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > maxPacketLength {
		return s.enc.Encode(codec.OpenFailureMessage{
			ChannelID: msg.SenderID,
		})
	}

	c := s.newChannel(channelInbound)
	c.remoteId = msg.SenderID
	c.maxRemotePayload = msg.MaxPacketSize
	c.remoteWin.add(msg.WindowSize)
	c.maxIncomingPayload = channelMaxPacket
	s.inbox <- c

	return s.enc.Encode(codec.OpenConfirmMessage{
		ChannelID:     c.remoteId,
		SenderID:      c.localId,
		WindowSize:    c.myWindow,
		MaxPacketSize: c.maxIncomingPayload,
	})
}


================================================
FILE: golang/session/session_test.go
================================================
package session

import (
	"bytes"
	"context"
	"errors"
	"io/ioutil"
	"net"
	"testing"
	"time"

	"github.com/progrium/qmux/golang/mux"
)

func fatal(err error, t *testing.T) {
	t.Helper()
	if err != nil {
		t.Fatal(err)
	}
}

func TestQmux(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	fatal(err, t)
	defer l.Close()

	go func() {
		conn, err := l.Accept()
		fatal(err, t)
		defer conn.Close()

		sess := New(conn)

		ch, err := sess.Open(context.Background())
		fatal(err, t)
		b, err := ioutil.ReadAll(ch)
		fatal(err, t)
		ch.Close() // should already be closed by other end

		ch, err = sess.Accept()
		_, err = ch.Write(b)
		fatal(err, t)
		err = ch.CloseWrite()
		fatal(err, t)

		err = sess.Close()
		fatal(err, t)
	}()

	conn, err := net.Dial("tcp", l.Addr().String())
	fatal(err, t)
	defer conn.Close()

	sess := New(conn)

	var ch mux.Channel
	t.Run("session accept", func(t *testing.T) {
		ch, err = sess.Accept()
		fatal(err, t)
	})

	t.Run("channel write", func(t *testing.T) {
		_, err = ch.Write([]byte("Hello world"))
		fatal(err, t)
		err = ch.Close()
		fatal(err, t)
	})

	t.Run("session open", func(t *testing.T) {
		ch, err = sess.Open(context.Background())
		fatal(err, t)
	})

	var b []byte
	t.Run("channel read", func(t *testing.T) {
		b, err = ioutil.ReadAll(ch)
		fatal(err, t)
		ch.Close() // should already be closed by other end
	})

	if !bytes.Equal(b, []byte("Hello world")) {
		t.Fatalf("unexpected bytes: %s", b)
	}
}

func TestSessionOpenTimeout(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	fatal(err, t)
	defer l.Close()

	conn, err := net.Dial("tcp", l.Addr().String())
	fatal(err, t)
	defer conn.Close()

	sess := New(conn)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
	defer cancel()

	ch, err := sess.Open(ctx)
	if err != context.DeadlineExceeded {
		t.Fatalf("expected DeadlineExceeded, but got: %v", err)
	}
	if ch != nil {
		ch.Close()
	}
}

func TestSessionWait(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	fatal(err, t)
	defer l.Close()

	conn, err := net.Dial("tcp", l.Addr().String())
	fatal(err, t)
	defer conn.Close()

	sess := New(conn)
	fatal(sess.Close(), t)
	// wait should return immediately since the connection was closed
	err = mux.Wait(sess)
	var netErr net.Error
	if !errors.As(err, &netErr) {
		t.Fatalf("expected a network error, but got: %v", err)
	}
}


================================================
FILE: golang/session/util.go
================================================
package session

func min(a uint32, b int) uint32 {
	if a < uint32(b) {
		return a
	}
	return uint32(b)
}


================================================
FILE: golang/session/util_buffer.go
================================================
package session

import (
	"io"
	"sync"
)

// buffer provides a linked list buffer for data exchange
// between producer and consumer. Theoretically the buffer is
// of unlimited capacity as it does no allocation of its own.
type buffer struct {
	// protects concurrent access to head, tail and closed
	*sync.Cond

	head *element // the buffer that will be read first
	tail *element // the buffer that will be read last

	closed bool
}

// An element represents a single link in a linked list.
type element struct {
	buf  []byte
	next *element
}

// newBuffer returns an empty buffer that is not closed.
func newBuffer() *buffer {
	e := new(element)
	b := &buffer{
		Cond: sync.NewCond(new(sync.Mutex)),
		head: e,
		tail: e,
	}
	return b
}

// write makes buf available for Read to receive.
// buf must not be modified after the call to write.
func (b *buffer) write(buf []byte) {
	b.Cond.L.Lock()
	e := &element{buf: buf}
	b.tail.next = e
	b.tail = e
	b.Cond.Signal()
	b.Cond.L.Unlock()
}

// eof closes the buffer. Reads from the buffer once all
// the data has been consumed will receive io.EOF.
func (b *buffer) eof() {
	b.Cond.L.Lock()
	b.closed = true
	b.Cond.Signal()
	b.Cond.L.Unlock()
}

// Read reads data from the internal buffer in buf.  Reads will block
// if no data is available, or until the buffer is closed.
func (b *buffer) Read(buf []byte) (n int, err error) {
	b.Cond.L.Lock()
	defer b.Cond.L.Unlock()

	for len(buf) > 0 {
		// if there is data in b.head, copy it
		if len(b.head.buf) > 0 {
			r := copy(buf, b.head.buf)
			buf, b.head.buf = buf[r:], b.head.buf[r:]
			n += r
			continue
		}
		// if there is a next buffer, make it the head
		if len(b.head.buf) == 0 && b.head != b.tail {
			b.head = b.head.next
			continue
		}

		// if at least one byte has been copied, return
		if n > 0 {
			break
		}

		// if nothing was read, and there is nothing outstanding
		// check to see if the buffer is closed.
		if b.closed {
			err = io.EOF
			break
		}
		// out of buffers, wait for producer
		b.Cond.Wait()
	}
	return
}


================================================
FILE: golang/session/util_chanlist.go
================================================
package session

import "sync"

// chanList is a thread safe channel list.
type chanList struct {
	// protects concurrent access to chans
	sync.Mutex

	// chans are indexed by the local id of the channel, which the
	// other side should send in the PeersId field.
	chans []*Channel
}

// Assigns a channel ID to the given channel.
func (c *chanList) add(ch *Channel) uint32 {
	c.Lock()
	defer c.Unlock()
	for i := range c.chans {
		if c.chans[i] == nil {
			c.chans[i] = ch
			return uint32(i)
		}
	}
	c.chans = append(c.chans, ch)
	return uint32(len(c.chans) - 1)
}

// getChan returns the channel for the given ID.
func (c *chanList) getChan(id uint32) *Channel {
	c.Lock()
	defer c.Unlock()
	if id < uint32(len(c.chans)) {
		return c.chans[id]
	}
	return nil
}

func (c *chanList) remove(id uint32) {
	c.Lock()
	if id < uint32(len(c.chans)) {
		c.chans[id] = nil
	}
	c.Unlock()
}

// dropAll forgets all channels it knows, returning them in a slice.
func (c *chanList) dropAll() []*Channel {
	c.Lock()
	defer c.Unlock()
	var r []*Channel

	for _, ch := range c.chans {
		if ch == nil {
			continue
		}
		r = append(r, ch)
	}
	c.chans = nil
	return r
}


================================================
FILE: golang/session/util_window.go
================================================
package session

import (
	"io"
	"sync"
)

// window represents the buffer available to clients
// wishing to write to a channel.
type window struct {
	*sync.Cond
	win          uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
	writeWaiters int
	closed       bool
}

// add adds win to the amount of window available
// for consumers.
func (w *window) add(win uint32) bool {
	// a zero sized window adjust is a noop.
	if win == 0 {
		return true
	}
	w.L.Lock()
	if w.win+win < win {
		w.L.Unlock()
		return false
	}
	w.win += win
	// It is unusual that multiple goroutines would be attempting to reserve
	// window space, but not guaranteed. Use broadcast to notify all waiters
	// that additional window is available.
	w.Broadcast()
	w.L.Unlock()
	return true
}

// close sets the window to closed, so all reservations fail
// immediately.
func (w *window) close() {
	w.L.Lock()
	w.closed = true
	w.Broadcast()
	w.L.Unlock()
}

// reserve reserves win from the available window capacity.
// If no capacity remains, reserve will block. reserve may
// return less than requested.
func (w *window) reserve(win uint32) (uint32, error) {
	var err error
	w.L.Lock()
	w.writeWaiters++
	w.Broadcast()
	for w.win == 0 && !w.closed {
		w.Wait()
	}
	w.writeWaiters--
	if w.win < win {
		win = w.win
	}
	w.win -= win
	if w.closed {
		err = io.EOF
	}
	w.L.Unlock()
	return win, err
}

// waitWriterBlocked waits until some goroutine is blocked for further
// writes. It is used in tests only.
func (w *window) waitWriterBlocked() {
	w.Cond.L.Lock()
	for w.writeWaiters == 0 {
		w.Cond.Wait()
	}
	w.Cond.L.Unlock()
}


================================================
FILE: golang/transport/dial_io.go
================================================
package transport

import (
	"io"
	"os"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
)

func DialIO(out io.WriteCloser, in io.ReadCloser) (mux.Session, error) {
	return session.New(&ioduplex{out, in}), nil
}

func DialStdio() (mux.Session, error) {
	return DialIO(os.Stdout, os.Stdin)
}


================================================
FILE: golang/transport/dial_net.go
================================================
package transport

import (
	"net"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
)

func dialNet(proto, addr string) (mux.Session, error) {
	conn, err := net.Dial(proto, addr)
	if err != nil {
		return nil, err
	}
	return session.New(conn), nil
}

func DialTCP(addr string) (mux.Session, error) {
	return dialNet("tcp", addr)
}

func DialUnix(addr string) (mux.Session, error) {
	return dialNet("unix", addr)
}


================================================
FILE: golang/transport/dial_ws.go
================================================
package transport

import (
	"fmt"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
	"golang.org/x/net/websocket"
)

func DialWS(addr string) (mux.Session, error) {
	ws, err := websocket.Dial(fmt.Sprintf("ws://%s/", addr), "", fmt.Sprintf("http://%s/", addr))
	if err != nil {
		return nil, err
	}
	ws.PayloadType = websocket.BinaryFrame
	return session.New(ws), nil
}


================================================
FILE: golang/transport/doc.go
================================================
// Package transport provides several dialers and listeners for getting qmux sessions over TCP, Unix sockets, WebSocket, and stdio.
package transport


================================================
FILE: golang/transport/listen.go
================================================
package transport

import "github.com/progrium/qmux/golang/mux"

type Listener interface {
	// Close closes the listener.
	// Any blocked Accept operations will be unblocked and return errors.
	Close() error

	// Accept waits for and returns the next incoming session.
	Accept() (mux.Session, error)
}


================================================
FILE: golang/transport/listen_io.go
================================================
package transport

import (
	"io"
	"os"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
)

type IOListener struct {
	io.ReadWriteCloser
}

func (l *IOListener) Accept() (mux.Session, error) {
	return session.New(l.ReadWriteCloser), nil
}

type ioduplex struct {
	io.WriteCloser
	io.ReadCloser
}

func (d *ioduplex) Close() error {
	if err := d.WriteCloser.Close(); err != nil {
		return err
	}
	if err := d.ReadCloser.Close(); err != nil {
		return err
	}
	return nil
}

func ListenIO(out io.WriteCloser, in io.ReadCloser) (*IOListener, error) {
	return &IOListener{
		&ioduplex{out, in},
	}, nil
}

func ListenStdio() (*IOListener, error) {
	return ListenIO(os.Stdout, os.Stdin)
}


================================================
FILE: golang/transport/listen_net.go
================================================
package transport

import (
	"io"
	"net"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
)

type NetListener struct {
	net.Listener
	accepted chan mux.Session
	closer   chan bool
	errs     chan error
}

func (l *NetListener) Accept() (mux.Session, error) {
	select {
	case <-l.closer:
		return nil, io.EOF
	case err := <-l.errs:
		return nil, err
	case sess := <-l.accepted:
		return sess, nil
	}
}

// func (l *NetListener) Addr() net.Addr {
// 	return l.Addr()
// }

func (l *NetListener) Close() error {
	if l.closer != nil {
		l.closer <- true
	}
	return l.Listener.Close()
}

func listenNet(proto, addr string) (*NetListener, error) {
	l, err := net.Listen(proto, addr)
	if err != nil {
		return nil, err
	}
	closer := make(chan bool, 1)
	errs := make(chan error, 1)
	accepted := make(chan mux.Session)
	go func(l net.Listener) {
		for {
			conn, err := l.Accept()
			if err != nil {
				errs <- err
				return
			}
			accepted <- session.New(conn)
		}
	}(l)
	return &NetListener{
		Listener: l,
		errs:     errs,
		accepted: accepted,
		closer:   closer,
	}, nil
}

func ListenTCP(addr string) (*NetListener, error) {
	return listenNet("tcp", addr)
}

func ListenUnix(addr string) (*NetListener, error) {
	return listenNet("unix", addr)
}


================================================
FILE: golang/transport/listen_ws.go
================================================
package transport

import (
	"net"
	"net/http"

	"github.com/progrium/qmux/golang/mux"
	"github.com/progrium/qmux/golang/session"
	"golang.org/x/net/websocket"
)

func HandleWS(l *NetListener, ws *websocket.Conn) {
	ws.PayloadType = websocket.BinaryFrame
	sess := session.New(ws)
	defer sess.Close()
	l.accepted <- sess
	l.errs <- mux.Wait(sess)
}

func ListenWS(addr string) (*NetListener, error) {
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return nil, err
	}
	nl := &NetListener{
		Listener: l,
		accepted: make(chan mux.Session),
		errs:     make(chan error, 2),
		closer:   make(chan bool, 1),
	}
	s := &http.Server{
		Addr: addr,
		Handler: websocket.Handler(func(ws *websocket.Conn) {
			HandleWS(nl, ws)
		}),
	}
	go func() {
		nl.errs <- s.Serve(l)
	}()
	return nl, nil
}


================================================
FILE: golang/transport/transport_test.go
================================================
package transport

import (
	"bytes"
	"context"
	"io"
	"io/ioutil"
	"path"
	"testing"

	"github.com/progrium/qmux/golang/mux"
)

func fatal(err error, t *testing.T) {
	t.Helper()
	if err != nil {
		t.Fatal(err)
	}
}

func testExchange(t *testing.T, sess mux.Session) {
	var err error
	var ch mux.Channel
	t.Run("session accept", func(t *testing.T) {
		ch, err = sess.Accept()
		fatal(err, t)
	})

	t.Run("channel write", func(t *testing.T) {
		_, err = ch.Write([]byte("Hello world"))
		fatal(err, t)
		err = ch.Close()
		fatal(err, t)
	})

	t.Run("session open", func(t *testing.T) {
		ch, err = sess.Open(context.Background())
		fatal(err, t)
	})

	var b []byte
	t.Run("channel read", func(t *testing.T) {
		b, err = ioutil.ReadAll(ch)
		fatal(err, t)
		err = ch.Close()
		fatal(err, t)
	})

	if !bytes.Equal(b, []byte("Hello world")) {
		t.Fatalf("unexpected bytes: %s", b)
	}

	t.Run("session close", func(t *testing.T) {
		err = sess.Close()
		fatal(err, t)
	})
}

func startListener(t *testing.T, l Listener) {
	t.Helper()

	t.Cleanup(func() {
		fatal(l.Close(), t)
	})

	go func() {
		sess, err := l.Accept()
		fatal(err, t)
		t.Cleanup(func() {
			// Synchronizes cleanup, waiting for the client to disconnect before
			// closing the stream. This prevents errors in the Pipe-based test with
			// closing one end of the pipe before the other has read the data.
			// Registering as a test cleanup function also avoids a race condition
			// with the test exiting before closing the session.
			if err := mux.Wait(sess); err != io.EOF {
				t.Errorf("Wait returned unexpected error: %v", err)
			}
			err = sess.Close()
			fatal(err, t)
		})

		ch, err := sess.Open(context.Background())
		fatal(err, t)
		b, err := ioutil.ReadAll(ch)
		fatal(err, t)
		ch.Close()

		ch, err = sess.Accept()
		_, err = ch.Write(b)
		fatal(err, t)
		err = ch.CloseWrite()
		fatal(err, t)
	}()
}

func TestTCP(t *testing.T) {
	l, err := ListenTCP("127.0.0.1:0")
	fatal(err, t)
	startListener(t, l)

	sess, err := DialTCP(l.Addr().String())
	fatal(err, t)
	testExchange(t, sess)
}

func TestUnix(t *testing.T) {
	tmp := t.TempDir()
	sockPath := path.Join(tmp, "qmux.sock")
	l, err := ListenUnix(sockPath)
	fatal(err, t)
	startListener(t, l)

	sess, err := DialUnix(sockPath)
	fatal(err, t)
	testExchange(t, sess)
}

func TestIO(t *testing.T) {
	pr1, pw1 := io.Pipe()
	pr2, pw2 := io.Pipe()

	l, err := ListenIO(pw1, pr2)
	fatal(err, t)
	startListener(t, l)

	sess, err := DialIO(pw2, pr1)
	fatal(err, t)
	testExchange(t, sess)
}

func TestWS(t *testing.T) {
	l, err := ListenWS("127.0.0.1:0")
	fatal(err, t)
	startListener(t, l)

	sess, err := DialWS(l.Addr().String())
	fatal(err, t)
	testExchange(t, sess)
}


================================================
FILE: typescript/Makefile
================================================

build: dist/qmux.js

dist/qmux.js: **.ts
	mkdir -p dist
	deno bundle -c tsconfig.json index.ts dist/qmux.js

================================================
FILE: typescript/README.md
================================================
# qmux for TypeScript

================================================
FILE: typescript/api.ts
================================================

export interface IConn {
    read(len: number): Promise<Uint8Array | undefined>;
    write(buffer: Uint8Array): Promise<number>;
    close(): Promise<void>;
}

export interface ISession {
    open(): Promise<IChannel>;
    accept(): Promise<IChannel | undefined>;
    close(): Promise<void>;
}

export interface IChannel extends IConn {
    ident(): number
    closeWrite(): Promise<void>
}

export interface IConnListener {
    accept(): Promise<IConn | undefined>;
    close(): Promise<void>;
}


================================================
FILE: typescript/channel.ts
================================================
// @ts-ignore
import * as util from "./util.ts";
// @ts-ignore
import * as codec from "./codec/index.ts";
// @ts-ignore
import * as internal from "./internal.ts";

export const channelMaxPacket = 1 << 15;
export const channelWindowSize = 64 * channelMaxPacket;

// channel represents a virtual muxed connection
export class Channel {
    localId: number;
    remoteId: number;
    maxIncomingPayload: number;
    maxRemotePayload: number;
    session: internal.Session;
    ready: util.queue<boolean>;
    sentEOF: boolean;
    sentClose: boolean;
    remoteWin: number;
    myWindow: number;
    readBuf: util.ReadBuffer;
    writers: Array<() => void>;

    constructor(sess: internal.Session) {
        this.localId = 0;
        this.remoteId = 0;
        this.maxIncomingPayload = 0;
        this.maxRemotePayload = 0;
        this.sentEOF = false;
        this.sentClose = false;
        this.remoteWin = 0;
        this.myWindow = 0;
        this.ready = new util.queue();
        this.session = sess;
        this.writers = [];
        this.readBuf = new util.ReadBuffer();
    }

    ident(): number {
        return this.localId;
    }

    async read(len: number): Promise<Uint8Array | undefined> {
        let data = await this.readBuf.read(len);
        if (data !== undefined) {
            try {
                await this.adjustWindow(data.byteLength)
            } catch (e) {
                if (e !== "EOF") {
                    throw e;
                }
            }
        }
        return data;
    }

    reserveWindow(win: number): number {
        if (this.remoteWin < win) {
            win = this.remoteWin;
        }
        this.remoteWin -= win;
        return win;
    }

    addWindow(win: number) {
        this.remoteWin += win;
        while (this.remoteWin > 0) {
            let writer = this.writers.shift();
            if (!writer) break;
            writer();
        }
    }

    write(buffer: Uint8Array): Promise<number> {
        if (this.sentEOF) {
            return Promise.reject("EOF");
        }

        return new Promise((resolve, reject) => {
            let n = 0;
            let tryWrite = () => {
                if (this.sentEOF || this.sentClose) {
                    reject("EOF");
                    return;
                }
                if (buffer.byteLength == 0) {
                    resolve(n);
                    return;
                }
                let space = Math.min(this.maxRemotePayload, buffer.byteLength);
                let reserved = this.reserveWindow(space);
                if (reserved == 0) {
                    this.writers.push(tryWrite);
                    return;
                }

                let toSend = buffer.slice(0, reserved);

                this.send({
                    ID: codec.DataID,
                    channelID: this.remoteId,
                    length: toSend.byteLength,
                    data: toSend,
                }).then(() => {
                    n += toSend.byteLength;
                    buffer = buffer.slice(toSend.byteLength);
                    if (buffer.byteLength == 0) {
                        resolve(n);
                        return;
                    }
                    this.writers.push(tryWrite);
                })
            }
            tryWrite();
        })
    }

    async closeWrite() {
        this.sentEOF = true;
        await this.send({
            ID: codec.EofID,
            channelID: this.remoteId
        });
        this.writers.forEach(writer => writer());
        this.writers = [];
    }

    async close(): Promise<void> {
        if (!this.sentClose) {
            await this.send({
                ID: codec.CloseID,
                channelID: this.remoteId
            });
            this.sentClose = true;
            while (await this.ready.shift() !== undefined) { }
            return;
        }
        this.shutdown();
    }

    shutdown(): void {
        this.readBuf.close();
        this.writers.forEach(writer => writer());
        this.ready.close();
        this.session.rmCh(this.localId);
    }

    async adjustWindow(n: number) {
        // Since myWindow is managed on our side, and can never exceed
        // the initial window setting, we don't worry about overflow.
        this.myWindow += n;
        await this.send({
            ID: codec.WindowAdjustID,
            channelID: this.remoteId,
            additionalBytes: n,
        })
    }

    send(msg: codec.ChannelMessage): Promise<number> {
        if (this.sentClose) {
            throw "EOF";
        }

        this.sentClose = (msg.ID === codec.CloseID);

        return this.session.enc.encode(msg);
    }

    handle(msg: codec.ChannelMessage): void {
        if (msg.ID === codec.DataID) {
            this.handleData(msg as codec.DataMessage);
            return;
        }
        if (msg.ID === codec.CloseID) {
            this.close(); // is this right?
            return;
        }
        if (msg.ID === codec.EofID) {
            this.readBuf.eof();
        }
        if (msg.ID === codec.OpenFailureID) {
            this.session.rmCh(msg.channelID);
            this.ready.push(false);
            return;
        }
        if (msg.ID === codec.OpenConfirmID) {
            if (msg.maxPacketSize < internal.minPacketLength || msg.maxPacketSize > internal.maxPacketLength) {
                throw "invalid max packet size";
            }
            this.remoteId = msg.senderID;
            this.maxRemotePayload = msg.maxPacketSize;
            this.addWindow(msg.windowSize);
            this.ready.push(true);
            return;
        }
        if (msg.ID === codec.WindowAdjustID) {
            this.addWindow(msg.additionalBytes);
        }
    }

    handleData(msg: codec.DataMessage) {
        if (msg.length > this.maxIncomingPayload) {
            throw "incoming packet exceeds maximum payload size";
        }

        // TODO: check packet length
        if (this.myWindow < msg.length) {
            throw "remote side wrote too much";
        }

        this.myWindow -= msg.length;

        this.readBuf.write(msg.data)
    }

}



================================================
FILE: typescript/codec/codec_test.ts
================================================
import {
    assertEquals,
} from "https://deno.land/std/testing/asserts.ts";

// @ts-ignore
import * as codec from "./index.ts";
import * as msg from "./message.ts";

Deno.test("hello world #1", () => {
    let packet = new Uint8Array(5);
    packet.set([105, 0, 0, 0, 0]);
    let obj = codec.Unmarshal(packet) as msg.AnyMessage;
    let buf = codec.Marshal(obj);
    console.log("Hello", obj, buf);
});


================================================
FILE: typescript/codec/decoder.ts
================================================
// @ts-ignore
import * as msg from "./message.ts";
// @ts-ignore
import * as api from "../api.ts";
// @ts-ignore
import * as util from "../util.ts";

export class Decoder {
    conn: api.IConn;
    debug: boolean;

    constructor(conn: api.IConn, debug: boolean = false) {
        this.conn = conn;
        this.debug = debug;
    }

    async decode(): Promise<msg.Message | undefined> {
        let packet = await readPacket(this.conn);
        if (packet === undefined) {
            return Promise.resolve(undefined);
        }
        let msg = Unmarshal(packet);
        if (this.debug) {
            console.log(">>", msg);
        }
        return msg;
    }
}

async function readPacket(conn: api.IConn): Promise<Uint8Array | undefined> {
    let head = await conn.read(1);
    if (head === undefined) {
        return Promise.resolve(undefined);
    }
    let msgID = head[0];

    let size = msg.payloadSizes.get(msgID);
    if (size === undefined || msgID < msg.OpenID || msgID > msg.CloseID) {
        return Promise.reject(`bad packet: ${msgID}`);
    }

    let rest = await conn.read(size);
    if (rest === undefined) {
        return Promise.reject("unexpected EOF");
    }

    if (msgID === msg.DataID) {
        let view = new DataView(rest.buffer);
        let length = view.getUint32(4);
        let data = await conn.read(length);
        if (data === undefined) {
            return Promise.reject("unexpected EOF");
        }
        return util.concat([head, rest, data], length + rest.length + 1);
    }

    return util.concat([head, rest], rest.length + 1);
}

export function Unmarshal(packet: Uint8Array): msg.Message {
    let data = new DataView(packet.buffer);
    switch (packet[0]) {
        case msg.CloseID:
            return {
                ID: packet[0],
                channelID: data.getUint32(1)
            } as msg.CloseMessage;
        case msg.DataID:
            let dataLength = data.getUint32(5);
            let rest = new Uint8Array(packet.buffer.slice(9));
            return {
                ID: packet[0],
                channelID: data.getUint32(1),
                length: dataLength,
                data: rest,
            } as msg.DataMessage;
        case msg.EofID:
            return {
                ID: packet[0],
                channelID: data.getUint32(1)
            } as msg.EOFMessage;
        case msg.OpenID:
            return {
                ID: packet[0],
                senderID: data.getUint32(1),
                windowSize: data.getUint32(5),
                maxPacketSize: data.getUint32(9),
            } as msg.OpenMessage;
        case msg.OpenConfirmID:
            return {
                ID: packet[0],
                channelID: data.getUint32(1),
                senderID: data.getUint32(5),
                windowSize: data.getUint32(9),
                maxPacketSize: data.getUint32(13),
            } as msg.OpenConfirmMessage;
        case msg.OpenFailureID:
            return {
                ID: packet[0],
                channelID: data.getUint32(1),
            } as msg.OpenFailureMessage;
        case msg.WindowAdjustID:
            return {
                ID: packet[0],
                channelID: data.getUint32(1),
                additionalBytes: data.getUint32(5),
            } as msg.WindowAdjustMessage;
        default:
            throw `unmarshal of unknown type: ${packet[0]}`;
    }
}


================================================
FILE: typescript/codec/encoder.ts
================================================
// @ts-ignore
import * as api from "../api.ts";
// @ts-ignore
import * as msg from "./message.ts";

export class Encoder {
    conn: api.IConn;
    debug: boolean;

    constructor(conn: api.IConn, debug: boolean = false) {
        this.conn = conn;
        this.debug = debug;
    }

    async encode(m: msg.AnyMessage): Promise<number> {
        if (this.debug) {
            console.log("<<", m);
        }
        return this.conn.write(Marshal(m));
    }
}

export function Marshal(obj: msg.AnyMessage): Uint8Array {
    if (obj.ID === msg.CloseID) {
        let m = obj as msg.CloseMessage;
        let data = new DataView(new ArrayBuffer(5));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        return new Uint8Array(data.buffer);
    }
    if (obj.ID === msg.DataID) {
        let m = obj as msg.DataMessage;
        let data = new DataView(new ArrayBuffer(9));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        data.setUint32(5, m.length);
        let buf = new Uint8Array(9 + m.length);
        buf.set(new Uint8Array(data.buffer), 0);
        buf.set(m.data, 9);
        return buf;
    }
    if (obj.ID === msg.EofID) {
        let m = obj as msg.EOFMessage;
        let data = new DataView(new ArrayBuffer(5));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        return new Uint8Array(data.buffer);
    }
    if (obj.ID === msg.OpenID) {
        let m = obj as msg.OpenMessage;
        let data = new DataView(new ArrayBuffer(13));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.senderID);
        data.setUint32(5, m.windowSize);
        data.setUint32(9, m.maxPacketSize);
        return new Uint8Array(data.buffer);
    }
    if (obj.ID === msg.OpenConfirmID) {
        let m = obj as msg.OpenConfirmMessage;
        let data = new DataView(new ArrayBuffer(17));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        data.setUint32(5, m.senderID);
        data.setUint32(9, m.windowSize);
        data.setUint32(13, m.maxPacketSize);
        return new Uint8Array(data.buffer);
    }
    if (obj.ID === msg.OpenFailureID) {
        let m = obj as msg.OpenFailureMessage;
        let data = new DataView(new ArrayBuffer(5));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        return new Uint8Array(data.buffer);
    }
    if (obj.ID === msg.WindowAdjustID) {
        let m = obj as msg.WindowAdjustMessage;
        let data = new DataView(new ArrayBuffer(9));
        data.setUint8(0, m.ID);
        data.setUint32(1, m.channelID);
        data.setUint32(5, m.additionalBytes);
        return new Uint8Array(data.buffer);
    }
    throw `marshal of unknown type: ${obj}`;
}


================================================
FILE: typescript/codec/index.ts
================================================
// @ts-ignore
export * from "./message.ts";
// @ts-ignore
export * from "./encoder.ts";
// @ts-ignore
export * from "./decoder.ts";


================================================
FILE: typescript/codec/message.ts
================================================

export const OpenID = 100;
export const OpenConfirmID = 101;
export const OpenFailureID = 102;
export const WindowAdjustID = 103;
export const DataID = 104;
export const EofID = 105;
export const CloseID = 106;

export var payloadSizes = new Map([
    [OpenID, 12],
    [OpenConfirmID, 16],
    [OpenFailureID, 4],
    [WindowAdjustID, 8],
    [DataID, 8],
    [EofID, 4],
    [CloseID, 4],
]);

export interface Message {
    ID: number;
}

export interface OpenMessage {
    ID: 100;
    senderID: number;
    windowSize: number;
    maxPacketSize: number;
}

export interface OpenConfirmMessage {
    ID: 101;
    channelID: number;
    senderID: number;
    windowSize: number;
    maxPacketSize: number;
}

export interface OpenFailureMessage {
    ID: 102;
    channelID: number;
}

export interface WindowAdjustMessage {
    ID: 103;
    channelID: number;
    additionalBytes: number;
}

export interface DataMessage {
    ID: 104;
    channelID: number;
    length: number;
    data: Uint8Array;
}

export interface EOFMessage {
    ID: 105;
    channelID: number;
}

export interface CloseMessage {
    ID: 106;
    channelID: number;
}

export type ChannelMessage = (
    OpenConfirmMessage |
    OpenFailureMessage |
    WindowAdjustMessage |
    DataMessage |
    EOFMessage |
    CloseMessage);

export type AnyMessage = ChannelMessage | OpenMessage;


================================================
FILE: typescript/index.ts
================================================
// https://github.com/Microsoft/TypeScript/issues/27481
// @ts-ignore
export * from "./internal.ts";
// @ts-ignore
export * from "./transport/websocket.ts";


================================================
FILE: typescript/internal.ts
================================================
// @ts-ignore
export * from "./session.ts";
// @ts-ignore
export * from "./channel.ts";


================================================
FILE: typescript/session.ts
================================================
// @ts-ignore
import * as api from "./api.ts";
// @ts-ignore
import * as codec from "./codec/index.ts";
// @ts-ignore
import * as util from "./util.ts";
// @ts-ignore
import * as internal from "./internal.ts";

export const minPacketLength = 9;
export const maxPacketLength = Number.MAX_VALUE;


export class Session implements api.ISession {
    conn: api.IConn;
    channels: Array<internal.Channel>;
    incoming: util.queue<api.IChannel>;
    enc: codec.Encoder;
    dec: codec.Decoder;
    done: Promise<void>;

    constructor(conn: api.IConn, debug: boolean = false) {
        this.conn = conn;
        this.enc = new codec.Encoder(conn, debug);
        this.dec = new codec.Decoder(conn, debug);
        this.channels = [];
        this.incoming = new util.queue();
        this.done = this.loop();
    }

    async open(): Promise<api.IChannel> {
        let ch = this.newChannel();
        ch.maxIncomingPayload = internal.channelMaxPacket;
        await this.enc.encode({
            ID: codec.OpenID,
            windowSize: ch.myWindow,
            maxPacketSize: ch.maxIncomingPayload,
            senderID: ch.localId
        });
        if (await ch.ready.shift()) {
            return ch;
        }
        throw "failed to open";
    }

    accept(): Promise<api.IChannel | undefined> {
        return this.incoming.shift();
    }

    async close(): Promise<void> {
        for (const ids of Object.keys(this.channels)) {
            let id = parseInt(ids);
            if (this.channels[id] !== undefined) {
                this.channels[id].shutdown();
            }
        }
        await this.conn.close();
        await this.done;
    }

    async loop() {
        try {
            while (true) {
                let msg = await this.dec.decode();
                if (msg === undefined) {
                    this.close();
                    return;
                }
                if (msg.ID === codec.OpenID) {
                    await this.handleOpen(msg as codec.OpenMessage);
                    continue;
                }

                let cmsg: codec.ChannelMessage = msg as codec.ChannelMessage;

                let ch = this.getCh(cmsg.channelID);
                if (ch === undefined) {
                    throw `invalid channel (${cmsg.channelID}) on op ${cmsg.ID}`;
                }
                await ch.handle(cmsg);
            }
        } catch (e) {
            throw new Error(`session readloop: ${e}`);
        }
        // catch {
        // 	this.channels.forEach(async (ch) => {
        // 		await ch.close();
        // 	})
        // 	this.channels = [];
        // 	await this.conn.close();
        // }
    }

    async handleOpen(msg: codec.OpenMessage) {
        if (msg.maxPacketSize < minPacketLength || msg.maxPacketSize > maxPacketLength) {
            await this.enc.encode({
                ID: codec.OpenFailureID,
                channelID: msg.senderID
            });
            return;
        }
        let c = this.newChannel();
        c.remoteId = msg.senderID;
        c.maxRemotePayload = msg.maxPacketSize;
        c.remoteWin = msg.windowSize;
        c.maxIncomingPayload = internal.channelMaxPacket;
        this.incoming.push(c);
        await this.enc.encode({
            ID: codec.OpenConfirmID,
            channelID: c.remoteId,
            senderID: c.localId,
            windowSize: c.myWindow,
            maxPacketSize: c.maxIncomingPayload
        });
    }

    newChannel(): internal.Channel {
        let ch = new internal.Channel(this);
        ch.remoteWin = 0;
        ch.myWindow = internal.channelWindowSize;
        ch.localId = this.addCh(ch);
        return ch;
    }

    getCh(id: number): internal.Channel {
        let ch = this.channels[id];
        if (ch && ch.localId !== id) {
            console.log("bad ids:", id, ch.localId, ch.remoteId);
        }
        return ch;
    }

    addCh(ch: internal.Channel): number {
        this.channels.forEach((v, i) => {
            if (v === undefined) {
                this.channels[i] = ch;
                return i;
            }
        });
        this.channels.push(ch);
        return this.channels.length - 1;
    }

    rmCh(id: number): void {
        delete this.channels[id];
    }

}



================================================
FILE: typescript/session_test.ts
================================================
import {
    assertEquals,
} from "https://deno.land/std/testing/asserts.ts";

import * as session from "./session.ts";
import * as api from "./api.ts";
import * as util from "./util.ts";
import * as tcp from "./transport/deno/tcp.ts";
import * as websocket from "./transport/deno/websocket.ts";

async function readAll(conn: api.IConn): Promise<Uint8Array> {
    let buff = new Uint8Array();
    while (true) {
        let next = await conn.read(100);
        if (next === undefined) {
            return buff;
        }
        buff = util.concat([buff, next], buff.byteLength + next.byteLength);
    }
}

async function startListener(listener: api.IConnListener) {
    let conn = await listener.accept();
    if (!conn) {
        throw new Error("accept failed")
    }
    let sess = new session.Session(conn);
    let ch = await sess.open();
    let b = await readAll(ch);
    await ch.close();

    let ch2 = await sess.accept();
    if (ch2 === undefined) {
        throw new Error("accept failed")
    }
    await ch2.write(b);
    await ch2.close();
    try {
        await sess.close();
        await listener.close();
    } catch (e) {
        console.log(e);
    }
}

async function testExchange(conn: api.IConn) {
    let sess = new session.Session(conn);
    let ch = await sess.accept();
    if (ch === undefined) {
        throw new Error("accept failed")
    }

    await ch.write(new TextEncoder().encode("Hello world"));
    await ch.closeWrite();
    await ch.close();

    let ch2 = await sess.open();
    let b = await readAll(ch2);
    await ch2.close();

    assertEquals(new TextEncoder().encode("Hello world"), b);
    try {
        await sess.close();
    } catch (e) {
        console.log(e);
    }
}

Deno.test("tcp", async () => {
    let listener = new tcp.Listener({ port: 0 });
    let port = (listener.listener.addr as Deno.NetAddr).port;
    await Promise.all([
        startListener(listener),
        tcp.Dial({ port }).then(conn => {
            return testExchange(conn);
        }),
    ]);
});

Deno.test("websocket", async () => {
    let endpoint = "ws://127.0.0.1:9999";
    let listener = new websocket.Listener(9999);
    await Promise.all([
        startListener(listener),
        websocket.Dial(endpoint).then(conn => {
            return testExchange(conn);
        }),
    ]);
});


Deno.test("multiple pending reads", async () => {
    let listener = Deno.listen({ port: 0 });

    let port = (listener.addr as Deno.NetAddr).port;

    let lConn = listener.accept();

    let sess1 = new session.Session(new tcp.Conn(await Deno.connect({ port })));
    let sess2 = new session.Session(new tcp.Conn(await lConn));

    let ch1p = sess1.accept();
    let ch2 = await sess2.open();
    let ch1 = await ch1p;
    if (ch1 === undefined) {
        throw new Error("accept failed");
    }

    let a = ch1.read(1);
    let bc = ch1.read(2);

    await ch2.write(new TextEncoder().encode("abc"));

    assertEquals(await a, new TextEncoder().encode("a"))
    assertEquals(await bc, new TextEncoder().encode("bc"))

    await ch2.closeWrite();
    await ch2.close();
    await sess2.close();

    await ch1.close();
    await sess1.close();

    listener.close();
});


================================================
FILE: typescript/transport/deno/tcp.ts
================================================
// @ts-ignore
import * as api from "../../api.ts";


export class Listener implements api.IConnListener {
    listener: Deno.Listener;

    constructor(opts: Deno.ListenOptions) {
        this.listener = Deno.listen(opts);
    }

    async accept(): Promise<Conn | undefined> {
        return new Conn(await this.listener.accept());
    }

    close(): Promise<void> {
        this.listener.close();
        return Promise.resolve();
    }
}

export async function Dial(opts: Deno.ConnectOptions): Promise<Conn> {
    return new Conn(await Deno.connect(opts));
}

export class Conn implements api.IConn {
    conn: Deno.Conn;

    constructor(conn: Deno.Conn) {
        this.conn = conn;
    }

    async read(len: number): Promise<Uint8Array | undefined> {
        let buff = new Uint8Array(len);
        let n: number | null;
        try {
            n = await this.conn.read(buff);
        } catch (e) {
            if (e instanceof Deno.errors.Interrupted || e instanceof Deno.errors.BadResource) {
                return undefined;
            }
            throw e;
        }
        if (n == null) {
            return undefined;
        }
        if (buff.byteLength > n) {
            buff = buff.slice(0, n);
        }
        return buff;
    }

    write(buffer: Uint8Array): Promise<number> {
        return this.conn.write(buffer)
    }

    close(): Promise<void> {
        try {
            this.conn.close();
        } catch (e) {
            if (!(e instanceof Deno.errors.BadResource)) {
                throw e;
            }
        }
        return Promise.resolve();
    }
}


================================================
FILE: typescript/transport/deno/websocket.ts
================================================
import { StandardWebSocketClient, WebSocketClient, WebSocketServer } from "https://deno.land/x/websocket@v0.1.2/mod.ts";

// @ts-ignore
import * as api from "./../../api.ts";
// @ts-ignore
import * as internal from "./../../internal.ts";
// @ts-ignore
import * as util from "./../../util.ts";

export class Listener implements api.IConnListener {
    wss: WebSocketServer
    q: util.queue<Conn>

    constructor(port: number) {
        this.q = new util.queue();
        this.wss = new WebSocketServer(port);
        this.wss.on("connection", (ws: WebSocketClient) => {
            this.q.push(new Conn(ws));
        })
    }

    accept(): Promise<Conn | undefined> {
        return this.q.shift();
    }

    async close(): Promise<void> {
        await this.wss.close();
        this.q.close();
    }
}

export function Dial(endpoint: string): Promise<Conn> {
    let ws = new StandardWebSocketClient(endpoint);
    return new Promise<Conn>((resolve) => {
        // TODO errors?
        ws.on("open", function () {
            resolve(new Conn(ws));
        });
    })
}

export class Conn implements api.IConn {
    socket: WebSocketClient
    buf: util.ReadBuffer
    isClosed: boolean

    constructor(socket: WebSocketClient) {
        this.isClosed = false;
        this.socket = socket;
        this.buf = new util.ReadBuffer();
        this.socket.on("message", (event: MessageEvent<Blob> | Uint8Array) => {
            if (event instanceof Uint8Array) {
                this.buf.write(event);
                return;
            }
            event.data.arrayBuffer().then((data) => {
                let buf = new Uint8Array(data);
                this.buf.write(buf);
            });
        });
        this.socket.on("close", () => {
            this.close();
        });
        //this.socket.onerror = (err) => console.error("qtalk", err);
    }

    read(len: number): Promise<Uint8Array | undefined> {
        return this.buf.read(len);
    }

    write(buffer: Uint8Array): Promise<number> {
        this.socket.send(buffer);
        return Promise.resolve(buffer.byteLength);
    }

    async close(): Promise<void> {
        if (this.isClosed) {
            return;
        }
        this.isClosed = true;
        this.buf.close();
        await this.socket.close(1000); // Code 1000: Normal Closure
    }
}


================================================
FILE: typescript/transport/websocket.ts
================================================
// @ts-ignore
import * as api from "./../api.ts";
// @ts-ignore
import * as internal from "./../internal.ts";
// @ts-ignore
import * as util from "./../util.ts";

export function Dial(addr: string, debug: boolean = false, onclose?: () => void): Promise<api.ISession> {
    return new Promise((resolve) => {
        var socket = new WebSocket(addr);
        socket.onopen = () => resolve(new internal.Session(new Conn(socket), debug));
        //socket.onerror = (err) => console.error("qtalk", err);
        if (onclose) socket.onclose = onclose;
    })
}

export class Conn implements api.IConn {
    socket: WebSocket
    error: any
    waiters: Array<() => void>
    buf: Uint8Array;
    isClosed: boolean

    constructor(socket: WebSocket) {
        this.isClosed = false;
        this.buf = new Uint8Array(0);
        this.waiters = [];
        this.socket = socket;
        this.socket.binaryType = "arraybuffer";
        this.socket.onmessage = (event) => {
            var buf = new Uint8Array(event.data);
            this.buf = util.concat([this.buf, buf], this.buf.length + buf.length);
            if (this.waiters.length > 0) {
                let waiter = this.waiters.shift();
                if (waiter) waiter();
            }
        };
        let onclose = this.socket.onclose;
        this.socket.onclose = (e: CloseEvent) => {
            if (onclose) onclose.bind(this.socket)(e);
            this.close();
        }
        //this.socket.onerror = (err) => console.error("qtalk", err);
    }

    read(len: number): Promise<Uint8Array | undefined> {
        return new Promise((resolve) => {
            var tryRead = () => {
                if (this.isClosed) {
                    resolve(undefined);
                    return;
                }
                if (this.buf.length >= len) {
                    var data = this.buf.slice(0, len);
                    this.buf = this.buf.slice(len);
                    resolve(data);
                    return;
                }
                this.waiters.push(tryRead);
            }
            tryRead();
        })
    }

    write(buffer: Uint8Array): Promise<number> {
        this.socket.send(buffer);
        return Promise.resolve(buffer.byteLength);
    }

    close(): Promise<void> {
        if (this.isClosed) return Promise.resolve();
        return new Promise((resolve) => {
            this.isClosed = true;
            this.waiters.forEach(waiter => waiter());
            this.socket.close();
            resolve();
        });
    }
}


================================================
FILE: typescript/tsconfig.json
================================================
{
    "compilerOptions": {
        "lib": ["es2016", "dom", "es5"],
        "noImplicitAny": true,
        "allowJs": true,
    }
}


================================================
FILE: typescript/util.ts
================================================

export function concat(list: Uint8Array[], totalLength: number): Uint8Array {
    let buf = new Uint8Array(totalLength);
    let offset = 0;
    list.forEach((el) => {
        buf.set(el, offset);
        offset += el.length;
    });
    return buf;
}

// queue primitive for incoming connections and
// signaling channel ready state
export class queue<ValueType> {
    q: Array<ValueType>
    waiters: Array<(a: ValueType | undefined) => void>
    closed: boolean

    constructor() {
        this.q = [];
        this.waiters = [];
        this.closed = false;
    }

    push(obj: ValueType) {
        if (this.closed) throw "closed queue";
        if (this.waiters.length > 0) {
            let waiter = this.waiters.shift()
            if (waiter) waiter(obj);
            return;
        }
        this.q.push(obj);
    }

    shift(): Promise<ValueType | undefined> {
        if (this.closed) return Promise.resolve(undefined);
        return new Promise(resolve => {
            if (this.q.length > 0) {
                resolve(this.q.shift());
                return;
            }
            this.waiters.push(resolve);
        })
    }

    close() {
        if (this.closed) return;
        this.closed = true;
        this.waiters.forEach(waiter => {
            waiter(undefined);
        });
    }
}

export class ReadBuffer {
    gotEOF: boolean;
    readBuf: Uint8Array | undefined;
    readers: Array<() => void>;

    constructor() {
        this.readBuf = new Uint8Array(0);
        this.gotEOF = false;
        this.readers = [];
    }

    read(len: number): Promise<Uint8Array | undefined> {
        return new Promise(resolve => {
            let tryRead = () => {
                if (this.readBuf === undefined) {
                    resolve(undefined);
                    return;
                }
                if (this.readBuf.length == 0) {
                    if (this.gotEOF) {
                        this.readBuf = undefined;
                        resolve(undefined);
                        return;
                    }
                    this.readers.push(tryRead);
                    return;
                }
                let data = this.readBuf.slice(0, len);
                this.readBuf = this.readBuf.slice(data.byteLength);
                if (this.readBuf.length == 0 && this.gotEOF) {
                    this.readBuf = undefined;
                }
                resolve(data);
            }
            tryRead();
        });
    }

    write(data: Uint8Array) {
        if (this.readBuf) {
            this.readBuf = concat([this.readBuf, data], this.readBuf.length + data.length);
        }

        while (!this.readBuf || this.readBuf.length > 0) {
            let reader = this.readers.shift();
            if (!reader) break
            reader();
        }
    }

    eof() {
        this.gotEOF = true;
        this.flushReaders();
    }

    close() {
        this.readBuf = undefined;
        this.flushReaders();
    }

    protected flushReaders() {
        while (true) {
            let reader = this.readers.shift();
            if (reader === undefined) {
                return;
            }
            reader();
        }
    }
}
Download .txt
gitextract_r4pm3g2i/

├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── LICENSE
├── README.md
├── SPEC.md
├── demos/
│   └── groktunnel/
│       ├── README.md
│       ├── go.mod
│       ├── go.sum
│       └── main.go
├── golang/
│   ├── README.md
│   ├── codec/
│   │   ├── codec.go
│   │   ├── codec_test.go
│   │   ├── decoder.go
│   │   ├── encoder.go
│   │   ├── message.go
│   │   ├── message_close.go
│   │   ├── message_data.go
│   │   ├── message_eof.go
│   │   ├── message_open.go
│   │   ├── message_openconfirm.go
│   │   ├── message_openfailure.go
│   │   └── message_windowadjust.go
│   ├── go.mod
│   ├── go.sum
│   ├── mux/
│   │   ├── api.go
│   │   ├── doc.go
│   │   └── misc.go
│   ├── session/
│   │   ├── channel.go
│   │   ├── doc.go
│   │   ├── session.go
│   │   ├── session_test.go
│   │   ├── util.go
│   │   ├── util_buffer.go
│   │   ├── util_chanlist.go
│   │   └── util_window.go
│   └── transport/
│       ├── dial_io.go
│       ├── dial_net.go
│       ├── dial_ws.go
│       ├── doc.go
│       ├── listen.go
│       ├── listen_io.go
│       ├── listen_net.go
│       ├── listen_ws.go
│       └── transport_test.go
└── typescript/
    ├── Makefile
    ├── README.md
    ├── api.ts
    ├── channel.ts
    ├── codec/
    │   ├── codec_test.ts
    │   ├── decoder.ts
    │   ├── encoder.ts
    │   ├── index.ts
    │   └── message.ts
    ├── index.ts
    ├── internal.ts
    ├── session.ts
    ├── session_test.ts
    ├── transport/
    │   ├── deno/
    │   │   ├── tcp.ts
    │   │   └── websocket.ts
    │   └── websocket.ts
    ├── tsconfig.json
    └── util.ts
Download .txt
SYMBOL INDEX (236 symbols across 40 files)

FILE: demos/groktunnel/main.go
  function main (line 21) | func main() {
  function serve (line 71) | func serve(vmux *vhost.HTTPMuxer, host, port string) {
  function join (line 106) | func join(a io.ReadWriteCloser, b io.ReadWriteCloser) {
  function newSubdomain (line 113) | func newSubdomain() string {
  function fatal (line 126) | func fatal(err error) {

FILE: golang/codec/codec_test.go
  function TestMarshalUnmarshal (line 8) | func TestMarshalUnmarshal(t *testing.T) {
  function TestEncodeDecode (line 86) | func TestEncodeDecode(t *testing.T) {

FILE: golang/codec/decoder.go
  type Decoder (line 13) | type Decoder struct
    method Decode (line 22) | func (dec *Decoder) Decode() (Message, error) {
  function NewDecoder (line 18) | func NewDecoder(r io.Reader) *Decoder {
  function readPacket (line 38) | func readPacket(c io.Reader) ([]byte, error) {
  function decode (line 71) | func decode(packet []byte) (Message, error) {
  type Unmarshaler (line 100) | type Unmarshaler interface
  function Unmarshal (line 104) | func Unmarshal(b []byte, v interface{}) error {

FILE: golang/codec/encoder.go
  type Encoder (line 9) | type Encoder struct
    method Encode (line 18) | func (enc *Encoder) Encode(msg interface{}) error {
  function NewEncoder (line 14) | func NewEncoder(w io.Writer) *Encoder {
  type Marshaler (line 39) | type Marshaler interface
  function Marshal (line 43) | func Marshal(v interface{}) ([]byte, error) {

FILE: golang/codec/message.go
  constant msgChannelOpen (line 4) | msgChannelOpen = iota + 100
  constant msgChannelOpenConfirm (line 5) | msgChannelOpenConfirm
  constant msgChannelOpenFailure (line 6) | msgChannelOpenFailure
  constant msgChannelWindowAdjust (line 7) | msgChannelWindowAdjust
  constant msgChannelData (line 8) | msgChannelData
  constant msgChannelEOF (line 9) | msgChannelEOF
  constant msgChannelClose (line 10) | msgChannelClose
  type Message (line 25) | type Message interface

FILE: golang/codec/message_close.go
  type CloseMessage (line 8) | type CloseMessage struct
    method String (line 12) | func (msg CloseMessage) String() string {
    method Channel (line 16) | func (msg CloseMessage) Channel() (uint32, bool) {
    method MarshalMux (line 20) | func (msg CloseMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 27) | func (msg *CloseMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_data.go
  type DataMessage (line 8) | type DataMessage struct
    method String (line 14) | func (msg DataMessage) String() string {
    method Channel (line 19) | func (msg DataMessage) Channel() (uint32, bool) {
    method MarshalMux (line 23) | func (msg DataMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 31) | func (msg *DataMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_eof.go
  type EOFMessage (line 8) | type EOFMessage struct
    method String (line 12) | func (msg EOFMessage) String() string {
    method Channel (line 16) | func (msg EOFMessage) Channel() (uint32, bool) {
    method MarshalMux (line 20) | func (msg EOFMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 27) | func (msg *EOFMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_open.go
  type OpenMessage (line 8) | type OpenMessage struct
    method String (line 14) | func (msg OpenMessage) String() string {
    method Channel (line 19) | func (msg OpenMessage) Channel() (uint32, bool) {
    method MarshalMux (line 23) | func (msg OpenMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 32) | func (msg *OpenMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_openconfirm.go
  type OpenConfirmMessage (line 8) | type OpenConfirmMessage struct
    method String (line 15) | func (msg OpenConfirmMessage) String() string {
    method Channel (line 20) | func (msg OpenConfirmMessage) Channel() (uint32, bool) {
    method MarshalMux (line 24) | func (msg OpenConfirmMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 34) | func (msg *OpenConfirmMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_openfailure.go
  type OpenFailureMessage (line 8) | type OpenFailureMessage struct
    method String (line 12) | func (msg OpenFailureMessage) String() string {
    method Channel (line 16) | func (msg OpenFailureMessage) Channel() (uint32, bool) {
    method MarshalMux (line 20) | func (msg OpenFailureMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 27) | func (msg *OpenFailureMessage) UnmarshalMux(b []byte) error {

FILE: golang/codec/message_windowadjust.go
  type WindowAdjustMessage (line 8) | type WindowAdjustMessage struct
    method String (line 13) | func (msg WindowAdjustMessage) String() string {
    method Channel (line 18) | func (msg WindowAdjustMessage) Channel() (uint32, bool) {
    method MarshalMux (line 22) | func (msg WindowAdjustMessage) MarshalMux() ([]byte, error) {
    method UnmarshalMux (line 30) | func (msg *WindowAdjustMessage) UnmarshalMux(b []byte) error {

FILE: golang/mux/api.go
  type Session (line 9) | type Session interface
  type Channel (line 23) | type Channel interface
  type Transport (line 44) | type Transport interface

FILE: golang/mux/misc.go
  type waiter (line 5) | type waiter interface
  function Wait (line 11) | func Wait(sess Session) error {

FILE: golang/session/channel.go
  type channelDirection (line 12) | type channelDirection
  constant channelInbound (line 15) | channelInbound channelDirection = iota
  constant channelOutbound (line 16) | channelOutbound
  type Channel (line 21) | type Channel struct
    method ID (line 65) | func (ch *Channel) ID() uint32 {
    method CloseWrite (line 71) | func (ch *Channel) CloseWrite() error {
    method Close (line 79) | func (ch *Channel) Close() error {
    method Write (line 85) | func (ch *Channel) Write(data []byte) (n int, err error) {
    method Read (line 114) | func (c *Channel) Read(data []byte) (n int, err error) {
    method send (line 132) | func (ch *Channel) send(msg interface{}) error {
    method adjustWindow (line 147) | func (c *Channel) adjustWindow(n uint32) error {
    method close (line 159) | func (c *Channel) close() {
    method responseMessageReceived (line 174) | func (ch *Channel) responseMessageReceived() error {
    method handle (line 181) | func (ch *Channel) handle(msg codec.Message) error {
    method handleData (line 230) | func (ch *Channel) handleData(msg *codec.DataMessage) error {

FILE: golang/session/session.go
  constant minPacketLength (line 14) | minPacketLength = 9
  constant maxPacketLength (line 15) | maxPacketLength = 1 << 31
  constant channelMaxPacket (line 20) | channelMaxPacket = 1 << 15
  constant channelWindowSize (line 22) | channelWindowSize = 64 * channelMaxPacket
  constant chanSize (line 27) | chanSize = 16
  type Session (line 31) | type Session struct
    method Close (line 63) | func (s *Session) Close() error {
    method Wait (line 70) | func (s *Session) Wait() error {
    method Accept (line 80) | func (s *Session) Accept() (mux.Channel, error) {
    method Open (line 90) | func (s *Session) Open(ctx context.Context) (mux.Channel, error) {
    method newChannel (line 123) | func (s *Session) newChannel(direction channelDirection) *Channel {
    method loop (line 139) | func (s *Session) loop() {
    method onePacket (line 159) | func (s *Session) onePacket() error {
    method handleOpen (line 182) | func (s *Session) handleOpen(msg *codec.OpenMessage) error {
  function New (line 46) | func New(t mux.Transport) *Session {

FILE: golang/session/session_test.go
  function fatal (line 15) | func fatal(err error, t *testing.T) {
  function TestQmux (line 22) | func TestQmux(t *testing.T) {
  function TestSessionOpenTimeout (line 86) | func TestSessionOpenTimeout(t *testing.T) {
  function TestSessionWait (line 109) | func TestSessionWait(t *testing.T) {

FILE: golang/session/util.go
  function min (line 3) | func min(a uint32, b int) uint32 {

FILE: golang/session/util_buffer.go
  type buffer (line 11) | type buffer struct
    method write (line 40) | func (b *buffer) write(buf []byte) {
    method eof (line 51) | func (b *buffer) eof() {
    method Read (line 60) | func (b *buffer) Read(buf []byte) (n int, err error) {
  type element (line 22) | type element struct
  function newBuffer (line 28) | func newBuffer() *buffer {

FILE: golang/session/util_chanlist.go
  type chanList (line 6) | type chanList struct
    method add (line 16) | func (c *chanList) add(ch *Channel) uint32 {
    method getChan (line 30) | func (c *chanList) getChan(id uint32) *Channel {
    method remove (line 39) | func (c *chanList) remove(id uint32) {
    method dropAll (line 48) | func (c *chanList) dropAll() []*Channel {

FILE: golang/session/util_window.go
  type window (line 10) | type window struct
    method add (line 19) | func (w *window) add(win uint32) bool {
    method close (line 40) | func (w *window) close() {
    method reserve (line 50) | func (w *window) reserve(win uint32) (uint32, error) {
    method waitWriterBlocked (line 72) | func (w *window) waitWriterBlocked() {

FILE: golang/transport/dial_io.go
  function DialIO (line 11) | func DialIO(out io.WriteCloser, in io.ReadCloser) (mux.Session, error) {
  function DialStdio (line 15) | func DialStdio() (mux.Session, error) {

FILE: golang/transport/dial_net.go
  function dialNet (line 10) | func dialNet(proto, addr string) (mux.Session, error) {
  function DialTCP (line 18) | func DialTCP(addr string) (mux.Session, error) {
  function DialUnix (line 22) | func DialUnix(addr string) (mux.Session, error) {

FILE: golang/transport/dial_ws.go
  function DialWS (line 11) | func DialWS(addr string) (mux.Session, error) {

FILE: golang/transport/listen.go
  type Listener (line 5) | type Listener interface

FILE: golang/transport/listen_io.go
  type IOListener (line 11) | type IOListener struct
    method Accept (line 15) | func (l *IOListener) Accept() (mux.Session, error) {
  type ioduplex (line 19) | type ioduplex struct
    method Close (line 24) | func (d *ioduplex) Close() error {
  function ListenIO (line 34) | func ListenIO(out io.WriteCloser, in io.ReadCloser) (*IOListener, error) {
  function ListenStdio (line 40) | func ListenStdio() (*IOListener, error) {

FILE: golang/transport/listen_net.go
  type NetListener (line 11) | type NetListener struct
    method Accept (line 18) | func (l *NetListener) Accept() (mux.Session, error) {
    method Close (line 33) | func (l *NetListener) Close() error {
  function listenNet (line 40) | func listenNet(proto, addr string) (*NetListener, error) {
  function ListenTCP (line 66) | func ListenTCP(addr string) (*NetListener, error) {
  function ListenUnix (line 70) | func ListenUnix(addr string) (*NetListener, error) {

FILE: golang/transport/listen_ws.go
  function HandleWS (line 12) | func HandleWS(l *NetListener, ws *websocket.Conn) {
  function ListenWS (line 20) | func ListenWS(addr string) (*NetListener, error) {

FILE: golang/transport/transport_test.go
  function fatal (line 14) | func fatal(err error, t *testing.T) {
  function testExchange (line 21) | func testExchange(t *testing.T, sess mux.Session) {
  function startListener (line 59) | func startListener(t *testing.T, l Listener) {
  function TestTCP (line 96) | func TestTCP(t *testing.T) {
  function TestUnix (line 106) | func TestUnix(t *testing.T) {
  function TestIO (line 118) | func TestIO(t *testing.T) {
  function TestWS (line 131) | func TestWS(t *testing.T) {

FILE: typescript/api.ts
  type IConn (line 2) | interface IConn {
  type ISession (line 8) | interface ISession {
  type IChannel (line 14) | interface IChannel extends IConn {
  type IConnListener (line 19) | interface IConnListener {

FILE: typescript/channel.ts
  class Channel (line 12) | class Channel {
    method constructor (line 26) | constructor(sess: internal.Session) {
    method ident (line 41) | ident(): number {
    method read (line 45) | async read(len: number): Promise<Uint8Array | undefined> {
    method reserveWindow (line 59) | reserveWindow(win: number): number {
    method addWindow (line 67) | addWindow(win: number) {
    method write (line 76) | write(buffer: Uint8Array): Promise<number> {
    method closeWrite (line 120) | async closeWrite() {
    method close (line 130) | async close(): Promise<void> {
    method shutdown (line 143) | shutdown(): void {
    method adjustWindow (line 150) | async adjustWindow(n: number) {
    method send (line 161) | send(msg: codec.ChannelMessage): Promise<number> {
    method handle (line 171) | handle(msg: codec.ChannelMessage): void {
    method handleData (line 203) | handleData(msg: codec.DataMessage) {

FILE: typescript/codec/decoder.ts
  class Decoder (line 8) | class Decoder {
    method constructor (line 12) | constructor(conn: api.IConn, debug: boolean = false) {
    method decode (line 17) | async decode(): Promise<msg.Message | undefined> {
  function readPacket (line 30) | async function readPacket(conn: api.IConn): Promise<Uint8Array | undefin...
  function Unmarshal (line 60) | function Unmarshal(packet: Uint8Array): msg.Message {

FILE: typescript/codec/encoder.ts
  class Encoder (line 6) | class Encoder {
    method constructor (line 10) | constructor(conn: api.IConn, debug: boolean = false) {
    method encode (line 15) | async encode(m: msg.AnyMessage): Promise<number> {
  function Marshal (line 23) | function Marshal(obj: msg.AnyMessage): Uint8Array {

FILE: typescript/codec/message.ts
  type Message (line 20) | interface Message {
  type OpenMessage (line 24) | interface OpenMessage {
  type OpenConfirmMessage (line 31) | interface OpenConfirmMessage {
  type OpenFailureMessage (line 39) | interface OpenFailureMessage {
  type WindowAdjustMessage (line 44) | interface WindowAdjustMessage {
  type DataMessage (line 50) | interface DataMessage {
  type EOFMessage (line 57) | interface EOFMessage {
  type CloseMessage (line 62) | interface CloseMessage {
  type ChannelMessage (line 67) | type ChannelMessage = (
  type AnyMessage (line 75) | type AnyMessage = ChannelMessage | OpenMessage;

FILE: typescript/session.ts
  class Session (line 14) | class Session implements api.ISession {
    method constructor (line 22) | constructor(conn: api.IConn, debug: boolean = false) {
    method open (line 31) | async open(): Promise<api.IChannel> {
    method accept (line 46) | accept(): Promise<api.IChannel | undefined> {
    method close (line 50) | async close(): Promise<void> {
    method loop (line 61) | async loop() {
    method handleOpen (line 94) | async handleOpen(msg: codec.OpenMessage) {
    method newChannel (line 117) | newChannel(): internal.Channel {
    method getCh (line 125) | getCh(id: number): internal.Channel {
    method addCh (line 133) | addCh(ch: internal.Channel): number {
    method rmCh (line 144) | rmCh(id: number): void {

FILE: typescript/session_test.ts
  function readAll (line 11) | async function readAll(conn: api.IConn): Promise<Uint8Array> {
  function startListener (line 22) | async function startListener(listener: api.IConnListener) {
  function testExchange (line 46) | async function testExchange(conn: api.IConn) {

FILE: typescript/transport/deno/tcp.ts
  class Listener (line 5) | class Listener implements api.IConnListener {
    method constructor (line 8) | constructor(opts: Deno.ListenOptions) {
    method accept (line 12) | async accept(): Promise<Conn | undefined> {
    method close (line 16) | close(): Promise<void> {
  function Dial (line 22) | async function Dial(opts: Deno.ConnectOptions): Promise<Conn> {
  class Conn (line 26) | class Conn implements api.IConn {
    method constructor (line 29) | constructor(conn: Deno.Conn) {
    method read (line 33) | async read(len: number): Promise<Uint8Array | undefined> {
    method write (line 53) | write(buffer: Uint8Array): Promise<number> {
    method close (line 57) | close(): Promise<void> {

FILE: typescript/transport/deno/websocket.ts
  class Listener (line 10) | class Listener implements api.IConnListener {
    method constructor (line 14) | constructor(port: number) {
    method accept (line 22) | accept(): Promise<Conn | undefined> {
    method close (line 26) | async close(): Promise<void> {
  function Dial (line 32) | function Dial(endpoint: string): Promise<Conn> {
  class Conn (line 42) | class Conn implements api.IConn {
    method constructor (line 47) | constructor(socket: WebSocketClient) {
    method read (line 67) | read(len: number): Promise<Uint8Array | undefined> {
    method write (line 71) | write(buffer: Uint8Array): Promise<number> {
    method close (line 76) | async close(): Promise<void> {

FILE: typescript/transport/websocket.ts
  function Dial (line 8) | function Dial(addr: string, debug: boolean = false, onclose?: () => void...
  class Conn (line 17) | class Conn implements api.IConn {
    method constructor (line 24) | constructor(socket: WebSocket) {
    method read (line 46) | read(len: number): Promise<Uint8Array | undefined> {
    method write (line 65) | write(buffer: Uint8Array): Promise<number> {
    method close (line 70) | close(): Promise<void> {

FILE: typescript/util.ts
  function concat (line 2) | function concat(list: Uint8Array[], totalLength: number): Uint8Array {
  class queue (line 14) | class queue<ValueType> {
    method constructor (line 19) | constructor() {
    method push (line 25) | push(obj: ValueType) {
    method shift (line 35) | shift(): Promise<ValueType | undefined> {
    method close (line 46) | close() {
  class ReadBuffer (line 55) | class ReadBuffer {
    method constructor (line 60) | constructor() {
    method read (line 66) | read(len: number): Promise<Uint8Array | undefined> {
    method write (line 93) | write(data: Uint8Array) {
    method eof (line 105) | eof() {
    method close (line 110) | close() {
    method flushReaders (line 115) | protected flushReaders() {
Condensed preview — 64 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (97K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 64,
    "preview": "# These are supported funding model platforms\n\ngithub: progrium\n"
  },
  {
    "path": ".gitignore",
    "chars": 49,
    "preview": "TODO\ntypescript/dist \ndemos/groktunnel/groktunnel"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 75,
    "preview": "{\n  \"recommendations\": [\n    \"denoland.vscode-deno\",\n    \"golang.go\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 71,
    "preview": "{\n  \"deno.enable\": true,\n  \"deno.lint\": true,\n  \"deno.unstable\": true\n}"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2021 Jeff Lindsay\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "README.md",
    "chars": 1396,
    "preview": "# qmux\n\nqmux is a wire protocol for multiplexing connections or streams into a single connection. It is based on the [SS"
  },
  {
    "path": "SPEC.md",
    "chars": 6292,
    "preview": "# qmux\n\nqmux is a wire protocol for multiplexing connections or streams into a single connection.\nIt is a subset of the "
  },
  {
    "path": "demos/groktunnel/README.md",
    "chars": 1388,
    "preview": "# groktunnel\n\nExpose localhost HTTP servers with a public URL\n\n## Build\n```\n$ go build\n```\n\n## Try it out\n\nFirst we run "
  },
  {
    "path": "demos/groktunnel/go.mod",
    "chars": 211,
    "preview": "module github.com/progrium/qmux/demos/groktunnel\n\ngo 1.16\n\nrequire (\n\tgithub.com/inconshreveable/go-vhost v0.0.0-2016062"
  },
  {
    "path": "demos/groktunnel/go.sum",
    "chars": 1100,
    "preview": "github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE=\ng"
  },
  {
    "path": "demos/groktunnel/main.go",
    "chars": 3001,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/http"
  },
  {
    "path": "golang/README.md",
    "chars": 1394,
    "preview": "# qmux for Go\n\nAn implementation of qmux for multiplexing any reliable `io.ReadWriteCloser`.\n\n## Using qmux in Go\n\n```\ng"
  },
  {
    "path": "golang/codec/codec.go",
    "chars": 154,
    "preview": "// Package codec implements encoding and decoding of qmux messages.\npackage codec\n\nimport \"io\"\n\nvar (\n\tDebugMessages io."
  },
  {
    "path": "golang/codec/codec_test.go",
    "chars": 2610,
    "preview": "package codec\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n)\n\nfunc TestMarshalUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tin  Mess"
  },
  {
    "path": "golang/codec/decoder.go",
    "chars": 2075,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sync\"\n\t\"syscall\"\n)\n\ntype Decoder struct {\n\tr i"
  },
  {
    "path": "golang/codec/encoder.go",
    "chars": 719,
    "preview": "package codec\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n)\n\ntype Encoder struct {\n\tw io.Writer\n\tsync.Mutex\n}\n\nfunc NewEncoder(w io.W"
  },
  {
    "path": "golang/codec/message.go",
    "chars": 490,
    "preview": "package codec\n\nconst (\n\tmsgChannelOpen = iota + 100\n\tmsgChannelOpenConfirm\n\tmsgChannelOpenFailure\n\tmsgChannelWindowAdjus"
  },
  {
    "path": "golang/codec/message_close.go",
    "chars": 633,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype CloseMessage struct {\n\tChannelID uint32\n}\n\nfunc (msg CloseMess"
  },
  {
    "path": "golang/codec/message_data.go",
    "chars": 834,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype DataMessage struct {\n\tChannelID uint32\n\tLength    uint32\n\tData"
  },
  {
    "path": "golang/codec/message_eof.go",
    "chars": 617,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype EOFMessage struct {\n\tChannelID uint32\n}\n\nfunc (msg EOFMessage)"
  },
  {
    "path": "golang/codec/message_open.go",
    "chars": 948,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype OpenMessage struct {\n\tSenderID      uint32\n\tWindowSize    uint"
  },
  {
    "path": "golang/codec/message_openconfirm.go",
    "chars": 1174,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype OpenConfirmMessage struct {\n\tChannelID     uint32\n\tSenderID   "
  },
  {
    "path": "golang/codec/message_openfailure.go",
    "chars": 681,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype OpenFailureMessage struct {\n\tChannelID uint32\n}\n\nfunc (msg Ope"
  },
  {
    "path": "golang/codec/message_windowadjust.go",
    "chars": 878,
    "preview": "package codec\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype WindowAdjustMessage struct {\n\tChannelID       uint32\n\tAddition"
  },
  {
    "path": "golang/go.mod",
    "chars": 121,
    "preview": "module github.com/progrium/qmux/golang\n\ngo 1.16\n\nrequire golang.org/x/net v0.0.0-20210420210106-798c2154c571 // indirect"
  },
  {
    "path": "golang/go.sum",
    "chars": 718,
    "preview": "golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=\ngolang.org/x/net v0."
  },
  {
    "path": "golang/mux/api.go",
    "chars": 1196,
    "preview": "package mux\n\nimport (\n\t\"context\"\n\t\"io\"\n)\n\n// Session is a bi-directional channel muxing session on a given transport.\nty"
  },
  {
    "path": "golang/mux/doc.go",
    "chars": 58,
    "preview": "// Package mux provides a generic muxing API.\npackage mux\n"
  },
  {
    "path": "golang/mux/misc.go",
    "chars": 317,
    "preview": "package mux\n\nimport \"fmt\"\n\ntype waiter interface {\n\tWait() error\n}\n\n// Wait blocks until the session transport has shut "
  },
  {
    "path": "golang/session/channel.go",
    "chars": 6033,
    "preview": "package session\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/progrium/qmux/golang/codec\"\n)\n\ntype channelDirect"
  },
  {
    "path": "golang/session/doc.go",
    "chars": 78,
    "preview": "// Package session implements a qmux session and channel API.\npackage session\n"
  },
  {
    "path": "golang/session/session.go",
    "chars": 4487,
    "preview": "package session\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/progrium/qmux/golang/codec\"\n\t\"github.com/progriu"
  },
  {
    "path": "golang/session/session_test.go",
    "chars": 2395,
    "preview": "package session\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/progrium/qm"
  },
  {
    "path": "golang/session/util.go",
    "chars": 106,
    "preview": "package session\n\nfunc min(a uint32, b int) uint32 {\n\tif a < uint32(b) {\n\t\treturn a\n\t}\n\treturn uint32(b)\n}\n"
  },
  {
    "path": "golang/session/util_buffer.go",
    "chars": 2044,
    "preview": "package session\n\nimport (\n\t\"io\"\n\t\"sync\"\n)\n\n// buffer provides a linked list buffer for data exchange\n// between producer"
  },
  {
    "path": "golang/session/util_chanlist.go",
    "chars": 1155,
    "preview": "package session\n\nimport \"sync\"\n\n// chanList is a thread safe channel list.\ntype chanList struct {\n\t// protects concurren"
  },
  {
    "path": "golang/session/util_window.go",
    "chars": 1617,
    "preview": "package session\n\nimport (\n\t\"io\"\n\t\"sync\"\n)\n\n// window represents the buffer available to clients\n// wishing to write to a"
  },
  {
    "path": "golang/transport/dial_io.go",
    "chars": 325,
    "preview": "package transport\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang/sessio"
  },
  {
    "path": "golang/transport/dial_net.go",
    "chars": 448,
    "preview": "package transport\n\nimport (\n\t\"net\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang/session\"\n)\n"
  },
  {
    "path": "golang/transport/dial_ws.go",
    "chars": 403,
    "preview": "package transport\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang/session\"\n\t\""
  },
  {
    "path": "golang/transport/doc.go",
    "chars": 150,
    "preview": "// Package transport provides several dialers and listeners for getting qmux sessions over TCP, Unix sockets, WebSocket,"
  },
  {
    "path": "golang/transport/listen.go",
    "chars": 302,
    "preview": "package transport\n\nimport \"github.com/progrium/qmux/golang/mux\"\n\ntype Listener interface {\n\t// Close closes the listener"
  },
  {
    "path": "golang/transport/listen_io.go",
    "chars": 717,
    "preview": "package transport\n\nimport (\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang/sessio"
  },
  {
    "path": "golang/transport/listen_net.go",
    "chars": 1280,
    "preview": "package transport\n\nimport (\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang/sessi"
  },
  {
    "path": "golang/transport/listen_ws.go",
    "chars": 792,
    "preview": "package transport\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/progrium/qmux/golang/mux\"\n\t\"github.com/progrium/qmux/golang"
  },
  {
    "path": "golang/transport/transport_test.go",
    "chars": 2698,
    "preview": "package transport\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"path\"\n\t\"testing\"\n\n\t\"github.com/progrium/qmux/golang"
  },
  {
    "path": "typescript/Makefile",
    "chars": 108,
    "preview": "\nbuild: dist/qmux.js\n\ndist/qmux.js: **.ts\n\tmkdir -p dist\n\tdeno bundle -c tsconfig.json index.ts dist/qmux.js"
  },
  {
    "path": "typescript/README.md",
    "chars": 21,
    "preview": "# qmux for TypeScript"
  },
  {
    "path": "typescript/api.ts",
    "chars": 498,
    "preview": "\nexport interface IConn {\n    read(len: number): Promise<Uint8Array | undefined>;\n    write(buffer: Uint8Array): Promise"
  },
  {
    "path": "typescript/channel.ts",
    "chars": 6149,
    "preview": "// @ts-ignore\nimport * as util from \"./util.ts\";\n// @ts-ignore\nimport * as codec from \"./codec/index.ts\";\n// @ts-ignore\n"
  },
  {
    "path": "typescript/codec/codec_test.ts",
    "chars": 406,
    "preview": "import {\n    assertEquals,\n} from \"https://deno.land/std/testing/asserts.ts\";\n\n// @ts-ignore\nimport * as codec from \"./i"
  },
  {
    "path": "typescript/codec/decoder.ts",
    "chars": 3415,
    "preview": "// @ts-ignore\nimport * as msg from \"./message.ts\";\n// @ts-ignore\nimport * as api from \"../api.ts\";\n// @ts-ignore\nimport "
  },
  {
    "path": "typescript/codec/encoder.ts",
    "chars": 2744,
    "preview": "// @ts-ignore\nimport * as api from \"../api.ts\";\n// @ts-ignore\nimport * as msg from \"./message.ts\";\n\nexport class Encoder"
  },
  {
    "path": "typescript/codec/index.ts",
    "chars": 132,
    "preview": "// @ts-ignore\nexport * from \"./message.ts\";\n// @ts-ignore\nexport * from \"./encoder.ts\";\n// @ts-ignore\nexport * from \"./d"
  },
  {
    "path": "typescript/codec/message.ts",
    "chars": 1366,
    "preview": "\nexport const OpenID = 100;\nexport const OpenConfirmID = 101;\nexport const OpenFailureID = 102;\nexport const WindowAdjus"
  },
  {
    "path": "typescript/index.ts",
    "chars": 157,
    "preview": "// https://github.com/Microsoft/TypeScript/issues/27481\n// @ts-ignore\nexport * from \"./internal.ts\";\n// @ts-ignore\nexpor"
  },
  {
    "path": "typescript/internal.ts",
    "chars": 88,
    "preview": "// @ts-ignore\nexport * from \"./session.ts\";\n// @ts-ignore\nexport * from \"./channel.ts\";\n"
  },
  {
    "path": "typescript/session.ts",
    "chars": 4264,
    "preview": "// @ts-ignore\nimport * as api from \"./api.ts\";\n// @ts-ignore\nimport * as codec from \"./codec/index.ts\";\n// @ts-ignore\nim"
  },
  {
    "path": "typescript/session_test.ts",
    "chars": 3210,
    "preview": "import {\n    assertEquals,\n} from \"https://deno.land/std/testing/asserts.ts\";\n\nimport * as session from \"./session.ts\";\n"
  },
  {
    "path": "typescript/transport/deno/tcp.ts",
    "chars": 1599,
    "preview": "// @ts-ignore\nimport * as api from \"../../api.ts\";\n\n\nexport class Listener implements api.IConnListener {\n    listener: "
  },
  {
    "path": "typescript/transport/deno/websocket.ts",
    "chars": 2332,
    "preview": "import { StandardWebSocketClient, WebSocketClient, WebSocketServer } from \"https://deno.land/x/websocket@v0.1.2/mod.ts\";"
  },
  {
    "path": "typescript/transport/websocket.ts",
    "chars": 2536,
    "preview": "// @ts-ignore\nimport * as api from \"./../api.ts\";\n// @ts-ignore\nimport * as internal from \"./../internal.ts\";\n// @ts-ign"
  },
  {
    "path": "typescript/tsconfig.json",
    "chars": 132,
    "preview": "{\n    \"compilerOptions\": {\n        \"lib\": [\"es2016\", \"dom\", \"es5\"],\n        \"noImplicitAny\": true,\n        \"allowJs\": tr"
  },
  {
    "path": "typescript/util.ts",
    "chars": 3209,
    "preview": "\nexport function concat(list: Uint8Array[], totalLength: number): Uint8Array {\n    let buf = new Uint8Array(totalLength)"
  }
]

About this extraction

This page contains the full source code of the progrium/qmux GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 64 files (85.7 KB), approximately 24.7k tokens, and a symbol index with 236 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!