├── javascript ├── rpc │ ├── index.ts │ ├── internal.ts │ ├── peer.ts │ ├── caller.ts │ ├── responder.ts │ └── codec.ts ├── qtalk.ts ├── mux │ ├── internal.ts │ ├── index.ts │ ├── codec │ │ ├── index.ts │ │ ├── codec_test.ts │ │ ├── message.ts │ │ ├── encoder.ts │ │ └── decoder.ts │ ├── api.ts │ ├── util.ts │ ├── transport │ │ └── websocket.ts │ ├── session.ts │ └── channel.ts ├── tsconfig.json ├── legacy │ ├── qrpc │ │ ├── tsconfig.node.json │ │ ├── tsconfig.browser.json │ │ ├── Makefile │ │ ├── package.json │ │ ├── demo │ │ │ ├── browser-server.js │ │ │ ├── _demo_libmux.js │ │ │ ├── _demo_qmux.js │ │ │ ├── _dev.js │ │ │ └── _demo_mock.js │ │ ├── server.go │ │ └── src │ │ │ └── qrpc.ts │ └── qmux │ │ ├── tsconfig.node.json │ │ ├── tsconfig.browser.json │ │ ├── Makefile │ │ ├── package.json │ │ ├── demos │ │ ├── browser-client.html │ │ ├── browser-server.js │ │ ├── node-ws.js │ │ └── node-tcp.js │ │ ├── tester │ │ └── tester_client.js │ │ ├── src │ │ ├── qmux_browser.ts │ │ ├── qmux_node.ts │ │ └── qmux.ts │ │ └── dist │ │ └── qmux_node.js └── Makefile ├── exp ├── README.md ├── bus │ └── bus.go └── python │ └── qmux.py ├── spec ├── README.md └── qmux.md ├── .gitignore ├── golang ├── mux │ ├── codec │ │ ├── codec_test.go │ │ ├── encoder.go │ │ ├── messages.go │ │ └── decoder.go │ ├── transport │ │ ├── dialers.go │ │ └── listeners.go │ ├── api.go │ ├── qmux_test.go │ ├── session.go │ ├── util.go │ └── channel.go └── rpc │ ├── server.go │ ├── peer.go │ ├── codec.go │ ├── object │ └── objects.go │ ├── caller.go │ ├── export.go │ └── responder.go ├── go.mod ├── _examples ├── webview │ ├── rpc-test.html │ ├── main.go │ ├── xterm.min.css │ └── main.html └── simplerpc │ └── main.go ├── go.sum └── README.md /javascript/rpc/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./internal.ts"; -------------------------------------------------------------------------------- /javascript/qtalk.ts: -------------------------------------------------------------------------------- 1 | export * from "./mux/index.ts"; 2 | export * from "./rpc/index.ts"; -------------------------------------------------------------------------------- /javascript/mux/internal.ts: -------------------------------------------------------------------------------- 1 | export * from "./session.ts"; 2 | export * from "./channel.ts"; -------------------------------------------------------------------------------- /exp/README.md: -------------------------------------------------------------------------------- 1 | # Experimental 2 | 3 | These are experiments and early language implementations. -------------------------------------------------------------------------------- /javascript/mux/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./internal.ts"; 2 | export * from "./transport/websocket.ts"; 3 | -------------------------------------------------------------------------------- /javascript/mux/codec/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./message.ts"; 2 | export * from "./encoder.ts"; 3 | export * from "./decoder.ts"; -------------------------------------------------------------------------------- /spec/README.md: -------------------------------------------------------------------------------- 1 | # Specifications 2 | 3 | Here are specifications for protocols like qmux and eventually 4 | API specifications for language bindings. -------------------------------------------------------------------------------- /javascript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2016", "dom", "es5"], 4 | "noImplicitAny": true, 5 | "allowJs": true, 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | node_modules/ 10 | libmux/build 11 | .mypy_cache 12 | __pycache__ -------------------------------------------------------------------------------- /golang/mux/codec/codec_test.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMessage(t *testing.T) { 9 | msg := DataMessage{ 10 | ChannelID: 10, 11 | Length: 100, 12 | Data: []byte("He"), 13 | } 14 | fmt.Println(*msg.Channel()) 15 | } 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/manifold/qtalk 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gorilla/websocket v1.4.2 // indirect 7 | github.com/webview/webview v0.0.0-20200724072439-e0c01595b361 // indirect 8 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 9 | gopkg.in/vmihailenco/msgpack.v2 v2.9.1 10 | ) 11 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./dist", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "lib": ["es2016", "es5"] 9 | }, 10 | "include": [ 11 | "./src/qrpc.ts" 12 | ] 13 | } -------------------------------------------------------------------------------- /javascript/rpc/internal.ts: -------------------------------------------------------------------------------- 1 | export * from "../mux/api.ts"; 2 | export * from "./codec.ts"; 3 | export * from "./responder.ts"; 4 | export * from "./caller.ts"; 5 | export * from "./peer.ts"; 6 | 7 | export function errable(p: Promise): Promise { 8 | return p 9 | .then(ret => [ret, null]) 10 | .catch(err => [null, err]); 11 | } -------------------------------------------------------------------------------- /javascript/legacy/qmux/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./dist", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "lib": ["es2016", "es5"] 9 | }, 10 | "include": [ 11 | "./src/qmux.ts", 12 | "./src/qmux_node.ts" 13 | ] 14 | } -------------------------------------------------------------------------------- /javascript/Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: qtalk.bundle.js 3 | 4 | qtalk.bundle.js: 5 | deno bundle -c tsconfig.json ./qtalk.ts qtalk.bundle.js 6 | cp qtalk.bundle.js ../_examples/webview/qtalk.bundle.js 7 | # for tractor, when qtalk is checked out under tractor 8 | @test -f ../../vnd/qtalk/qtalk.bundle.js && cp qtalk.bundle.js ../../vnd/qtalk/qtalk.bundle.js || true 9 | 10 | clean: 11 | rm qtalk.bundle.js || true -------------------------------------------------------------------------------- /javascript/legacy/qrpc/tsconfig.browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./dist/browser", 5 | "target": "es5", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "lib": ["es2016", "dom", "es5"] 11 | }, 12 | "include": [ 13 | "./src/qrpc.ts" 14 | ] 15 | } -------------------------------------------------------------------------------- /javascript/mux/api.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IConn { 3 | read(len: number): Promise; 4 | write(buffer: Uint8Array): Promise; 5 | close(): Promise; 6 | } 7 | 8 | export interface ISession { 9 | open(): Promise; 10 | accept(): Promise; 11 | close(): Promise; 12 | 13 | } 14 | 15 | export interface IChannel extends IConn { 16 | ident(): number 17 | } 18 | -------------------------------------------------------------------------------- /javascript/mux/codec/codec_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assertEquals, 3 | } from "https://deno.land/std/testing/asserts.ts"; 4 | 5 | import * as codec from "./index.ts"; 6 | 7 | Deno.test("hello world #1", () => { 8 | let packet = new Uint8Array(5); 9 | packet.set([105, 0, 0, 0, 0]); 10 | let obj = codec.Decode(packet); 11 | let buf = codec.Encode(obj); 12 | console.log("Hello", obj, buf); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/tsconfig.browser.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "./dist/browser", 5 | "target": "es5", 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "lib": ["es2016", "dom", "es5"] 11 | }, 12 | "include": [ 13 | "./src/qmux.ts", 14 | "./src/qmux_browser.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /javascript/legacy/qrpc/Makefile: -------------------------------------------------------------------------------- 1 | TSC := ./node_modules/.bin/tsc 2 | BROWSERIFY := ./node_modules/.bin/browserify 3 | UGLIFY := ./node_modules/.bin/uglifyjs 4 | 5 | build: browser node 6 | 7 | node_modules: 8 | yarn 9 | 10 | browser: node_modules 11 | $(TSC) -p tsconfig.browser.json 12 | $(BROWSERIFY) --s qrpc dist/browser/qrpc.js | $(UGLIFY) > ./dist/browser/qrpc.min.js 13 | #rm dist/browser/qmux.js dist/browser/qmux_browser.js 14 | 15 | node: node_modules 16 | $(TSC) -p tsconfig.node.json 17 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/Makefile: -------------------------------------------------------------------------------- 1 | TSC := ./node_modules/.bin/tsc 2 | BROWSERIFY := ./node_modules/.bin/browserify 3 | UGLIFY := ./node_modules/.bin/uglifyjs 4 | 5 | build: browser node 6 | 7 | node_modules: 8 | yarn 9 | 10 | browser: node_modules 11 | $(TSC) -p tsconfig.browser.json 12 | $(BROWSERIFY) --s qmux dist/browser/qmux.js dist/browser/qmux_browser.js > ./dist/browser/qmux.min.js 13 | #$(UGLIFY) > ./dist/browser/qmux.min.js 14 | rm dist/browser/qmux.js dist/browser/qmux_browser.js 15 | 16 | node: node_modules 17 | $(TSC) -p tsconfig.node.json -------------------------------------------------------------------------------- /javascript/legacy/qrpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qrpc", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "./dist/qrpc.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "msgpack-lite": "^0.1.26" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^10.5.2", 16 | "typescript": "^2.9.2", 17 | "browserify": "^16.2.3", 18 | "typescript": "^3.0.3", 19 | "uglify-js": "^3.4.9" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qmux", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/qmux_node.js", 6 | "dependencies": { 7 | "ts-node": "^8.5.2", 8 | "ws": "^6.0.0" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^10.9.4", 12 | "@types/ws": "^6.0.1", 13 | "browserify": "^16.2.3", 14 | "typescript": "^3.7.2", 15 | "uglify-js": "^3.4.9" 16 | }, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /_examples/webview/rpc-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | open js console .. 4 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /golang/rpc/server.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/manifold/qtalk/golang/mux" 7 | ) 8 | 9 | type Server struct { 10 | Mux *RespondMux 11 | } 12 | 13 | func (s *Server) Respond(sess mux.Session) { 14 | for { 15 | ch, err := sess.Accept() 16 | if err != nil { 17 | if err == io.EOF { 18 | return 19 | } 20 | panic(err) 21 | } 22 | go Respond(sess, ch, s.Mux) 23 | } 24 | } 25 | 26 | func (s *Server) Serve(l mux.Listener) error { 27 | for { 28 | sess, err := l.Accept() 29 | if err != nil { 30 | return err 31 | } 32 | go s.Respond(sess) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/demo/browser-server.js: -------------------------------------------------------------------------------- 1 | var qmux = require("../../../qmux/node/dist/qmux_node.js"); 2 | var qrpc = require("../dist/qrpc.js"); 3 | 4 | var session = null; 5 | var listener = null; 6 | 7 | (async () => { 8 | var api = new qrpc.API(); 9 | api.handle("demo", qrpc.Export({ 10 | "lower": function(v) { 11 | return v.toLowerCase(); 12 | } 13 | })); 14 | 15 | listener = await qmux.ListenWebsocket(4242); 16 | var server = new qrpc.Server(); 17 | await server.serve(listener, api); 18 | })().catch(async (err) => { 19 | console.log(err.stack); 20 | }); 21 | 22 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/demos/browser-client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/demos/browser-server.js: -------------------------------------------------------------------------------- 1 | var qmux = require('qmux'); 2 | 3 | (async () => { 4 | 5 | var listener = await qmux.ListenWebsocket(8000); 6 | 7 | (async () => { 8 | var sess = await listener.accept(); 9 | var ch = await sess.open(); 10 | console.log("|Server echoing on channel..."); 11 | while(true) { 12 | var data = await ch.read(1); 13 | if (data === undefined) { 14 | console.log("|Server got EOF"); 15 | break; 16 | } 17 | await ch.write(data); 18 | } 19 | await listener.close(); 20 | })(); 21 | 22 | })().catch(async (err) => { 23 | console.log(err.stack); 24 | }); 25 | 26 | 27 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/tester/tester_client.js: -------------------------------------------------------------------------------- 1 | var qmux = require('../dist/qmux_node'); 2 | var net = require('net'); 3 | 4 | (async () => { 5 | var sess = new qmux.Session(await qmux.DialTCP(9998)); 6 | 7 | var ch = await sess.accept(); 8 | var buf = Buffer.from(""); 9 | while (true) { 10 | var data = await ch.read(1) 11 | if (data === undefined) { 12 | break; 13 | } 14 | buf = Buffer.concat([buf, data], buf.length+data.length); 15 | } 16 | await ch.close(); 17 | 18 | ch = await sess.open(); 19 | await ch.write(buf); 20 | await ch.close(); 21 | 22 | await sess.close(); 23 | 24 | })().catch(async (err) => { 25 | console.log(err.stack); 26 | }); 27 | -------------------------------------------------------------------------------- /golang/rpc/peer.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import "github.com/manifold/qtalk/golang/mux" 4 | 5 | type Peer struct { 6 | mux.Session 7 | 8 | caller Caller 9 | responder *RespondMux 10 | } 11 | 12 | func NewPeer(session mux.Session, codec Codec) *Peer { 13 | return &Peer{ 14 | Session: session, 15 | caller: &caller{ 16 | session: session, 17 | codec: codec, 18 | }, 19 | responder: NewRespondMux(codec), 20 | } 21 | } 22 | 23 | func (p *Peer) Respond() { 24 | srv := &Server{Mux: p.responder} 25 | srv.Respond(p.Session) 26 | } 27 | 28 | func (p *Peer) Call(path string, args, reply interface{}) (*Response, error) { 29 | return p.caller.Call(path, args, reply) 30 | } 31 | 32 | func (p *Peer) Bind(path string, v interface{}) { 33 | p.responder.Bind(path, v) 34 | } 35 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/progrium/prototypes/libmux/mux" 7 | "github.com/progrium/prototypes/qrpc" 8 | ) 9 | 10 | const addr = "localhost:4242" 11 | 12 | func main() { 13 | // define api 14 | api := qrpc.NewAPI() 15 | api.HandleFunc("echo", func(r qrpc.Responder, c *qrpc.Call) { 16 | defer log.Println("returned") 17 | var msg string 18 | log.Println("echo call") 19 | err := c.Decode(&msg) 20 | log.Println("arg decode:", msg) 21 | if err != nil { 22 | r.Return(err) 23 | return 24 | } 25 | r.Return(msg) 26 | }) 27 | 28 | // start server with api 29 | server := &qrpc.Server{} 30 | l, err := mux.ListenTCP(addr) 31 | if err != nil { 32 | panic(err) 33 | } 34 | log.Fatal(server.Serve(l, api)) 35 | } 36 | -------------------------------------------------------------------------------- /_examples/simplerpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/manifold/qtalk/golang/mux/transport" 9 | "github.com/manifold/qtalk/golang/rpc" 10 | ) 11 | 12 | func main() { 13 | rpc.Bind("upper", func(str string) string { 14 | return strings.ToUpper(str) 15 | }) 16 | rpc.Bind("add", func(a, b int) int { 17 | return a + b 18 | }) 19 | 20 | go http.ListenAndServe(":8888", rpc.DefaultRespondMux) 21 | 22 | sess, err := transport.DialWS("localhost:8888") 23 | fatal(err) 24 | 25 | caller := rpc.NewCaller(sess, rpc.JSONCodec{}) 26 | 27 | var reply int 28 | _, err = caller.Call("add", []int{4, 5}, &reply) 29 | fatal(err) 30 | 31 | fmt.Println(reply) 32 | } 33 | 34 | func fatal(err error) { 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /_examples/webview/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | 7 | "github.com/manifold/qtalk/golang/rpc" 8 | "github.com/webview/webview" 9 | ) 10 | 11 | func main() { 12 | rpc.Bind("upper", func(str string) string { 13 | return strings.ToUpper(str) 14 | }) 15 | rpc.Bind("add", func(a, b int) int { 16 | return a + b 17 | }) 18 | 19 | go func() { 20 | http.ListenAndServe("127.0.0.1:7171", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | if r.Header.Get("Upgrade") == "websocket" { 22 | rpc.DefaultRespondMux.ServeHTTP(w, r) 23 | return 24 | } 25 | http.FileServer(http.Dir(".")).ServeHTTP(w, r) 26 | })) 27 | }() 28 | 29 | wv := webview.New(true) 30 | defer wv.Destroy() 31 | wv.SetSize(720, 400, webview.HintNone) 32 | wv.Navigate("http://127.0.0.1:7171/main.html") 33 | wv.Run() 34 | } 35 | -------------------------------------------------------------------------------- /golang/mux/transport/dialers.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/manifold/qtalk/golang/mux" 9 | "golang.org/x/net/websocket" 10 | ) 11 | 12 | func DialTCP(addr string) (mux.Session, error) { 13 | return dialNet("tcp", addr) 14 | } 15 | 16 | func DialUnix(addr string) (mux.Session, error) { 17 | return dialNet("unix", addr) 18 | } 19 | 20 | func dialNet(proto, addr string) (mux.Session, error) { 21 | conn, err := net.Dial(proto, addr) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return mux.NewSession(context.Background(), conn), nil 26 | } 27 | 28 | func DialWS(addr string) (mux.Session, error) { 29 | ws, err := websocket.Dial(fmt.Sprintf("ws://%s/", addr), "", fmt.Sprintf("http://%s/", addr)) 30 | if err != nil { 31 | return nil, err 32 | } 33 | ws.PayloadType = websocket.BinaryFrame 34 | return mux.NewSession(context.Background(), ws), nil 35 | } 36 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/demo/_demo_libmux.js: -------------------------------------------------------------------------------- 1 | var libmux = require("libmux"); 2 | var qrpc = require("./qrpc.js"); 3 | 4 | var session = null; 5 | var listener = null; 6 | 7 | (async () => { 8 | var api = new qrpc.API(); 9 | api.handle("demo", qrpc.Export({ 10 | "lower": function(v) { 11 | return v.toLowerCase(); 12 | } 13 | })); 14 | 15 | listener = await libmux.ListenWebsocket("localhost:4242"); 16 | var server = new qrpc.Server(); 17 | await server.serve(listener, api); 18 | //await listener.close(); 19 | })().catch(async (err) => { 20 | console.log(err.stack); 21 | }); 22 | 23 | 24 | (async () => { 25 | console.log("dialing...") 26 | session = await libmux.DialWebsocket("localhost:4242"); 27 | console.log("connected...") 28 | var client = new qrpc.Client(session); 29 | console.log("calling...") 30 | console.log(await client.call("demo/lower", "HELLO WORLD!")); 31 | await session.close(); 32 | 33 | })().catch(async (err) => { 34 | console.log(err.stack); 35 | }); -------------------------------------------------------------------------------- /golang/mux/api.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "net" 6 | ) 7 | 8 | type Session interface { 9 | Context() context.Context 10 | Close() error 11 | Open() (Channel, error) 12 | Accept() (Channel, error) 13 | LocalAddr() net.Addr 14 | RemoteAddr() net.Addr 15 | Wait() error 16 | } 17 | 18 | // A Channel is an ordered, reliable, flow-controlled, duplex stream 19 | // that is multiplexed over a qmux connection. 20 | type Channel interface { 21 | Context() context.Context 22 | 23 | // Read reads up to len(data) bytes from the channel. 24 | Read(data []byte) (int, error) 25 | 26 | // Write writes len(data) bytes to the channel. 27 | Write(data []byte) (int, error) 28 | 29 | // Close signals end of channel use. No data may be sent after this 30 | // call. 31 | Close() error 32 | 33 | // CloseWrite signals the end of sending in-band 34 | // data. The other side may still send data 35 | CloseWrite() error 36 | 37 | ID() uint32 38 | } 39 | 40 | type Listener interface { 41 | Close() error 42 | Addr() net.Addr 43 | Accept() (Session, error) 44 | } 45 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/demos/node-ws.js: -------------------------------------------------------------------------------- 1 | var qmux = require('qmux'); 2 | 3 | (async () => { 4 | 5 | var listener = await qmux.ListenWebsocket(8000); 6 | 7 | (async () => { 8 | var sess = await listener.accept(); 9 | var ch = await sess.open(); 10 | console.log("|Server echoing on channel..."); 11 | while(true) { 12 | var data = await ch.read(1); 13 | if (data === undefined) { 14 | console.log("|Server got EOF"); 15 | break; 16 | } 17 | await ch.write(data); 18 | } 19 | await listener.close(); 20 | })(); 21 | 22 | 23 | var sess = new qmux.Session(await qmux.DialWebsocket("ws://localhost:8000/")); 24 | var ch = await sess.accept(); 25 | await ch.write(Buffer.from("Hello")); 26 | await ch.write(Buffer.from(" ")); 27 | await ch.write(Buffer.from("world")); 28 | 29 | var data = await ch.read(11); 30 | console.log(data.toString('ascii')); 31 | 32 | await sess.close(); 33 | 34 | })().catch(async (err) => { 35 | console.log(err.stack); 36 | }); 37 | 38 | 39 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/demos/node-tcp.js: -------------------------------------------------------------------------------- 1 | var qmux = require('qmux'); 2 | var net = require('net'); 3 | 4 | (async () => { 5 | 6 | var listener = await qmux.ListenTCP(7000); 7 | 8 | (async () => { 9 | var sess = await listener.accept(); 10 | var ch = await sess.open(); 11 | console.log("|Server echoing on channel..."); 12 | while(true) { 13 | var data = await ch.read(1); 14 | if (data === undefined) { 15 | console.log("|Server got EOF"); 16 | break; 17 | } 18 | await ch.write(data); 19 | } 20 | await listener.close(); 21 | })(); 22 | 23 | 24 | var sess = new qmux.Session(await qmux.DialTCP(7000)); 25 | var ch = await sess.accept(); 26 | await ch.write(Buffer.from("Hello")); 27 | await ch.write(Buffer.from(" ")); 28 | await ch.write(Buffer.from("world")); 29 | 30 | var data = await ch.read(11); 31 | console.log(data.toString('ascii')); 32 | 33 | await sess.close(); 34 | 35 | })().catch(async (err) => { 36 | console.log(err.stack); 37 | }); 38 | 39 | 40 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/demo/_demo_qmux.js: -------------------------------------------------------------------------------- 1 | var qmux = require("../../qmux/node/dist/qmux_node.js"); 2 | var qrpc = require("./dist/qrpc.js"); 3 | 4 | var session = null; 5 | var listener = null; 6 | 7 | (async () => { 8 | var api = new qrpc.API(); 9 | api.handle("demo", qrpc.Export({ 10 | "lower": function(v) { 11 | return v.toLowerCase(); 12 | } 13 | })); 14 | 15 | listener = await qmux.ListenWebsocket(4242); 16 | var server = new qrpc.Server(); 17 | await server.serve(listener, api); 18 | })().catch(async (err) => { 19 | console.log(err.stack); 20 | }); 21 | 22 | 23 | (async () => { 24 | console.log("dialing...") 25 | var conn = await qmux.DialWebsocket("ws://localhost:4242"); 26 | conn.client = true; 27 | session = new qmux.Session(conn); 28 | console.log("connected...") 29 | var client = new qrpc.Client(session); 30 | console.log("calling...") 31 | console.log((await client.call("demo/lower", "HELLO WORLD!")).reply); 32 | await session.close(); 33 | await listener.close(); 34 | 35 | })().catch(async (err) => { 36 | console.log(err.stack); 37 | }); -------------------------------------------------------------------------------- /_examples/webview/xterm.min.css: -------------------------------------------------------------------------------- 1 | .xterm{font-feature-settings:"liga" 0;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:10}.xterm .xterm-helper-textarea{position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-10;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:100;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /javascript/mux/util.ts: -------------------------------------------------------------------------------- 1 | 2 | export function concat(list: Uint8Array[], totalLength: number): Uint8Array { 3 | let buf = new Uint8Array(totalLength); 4 | let offset = 0; 5 | list.forEach((el) => { 6 | buf.set(el, offset); 7 | offset += el.length; 8 | }); 9 | return buf; 10 | } 11 | 12 | // queue primitive for incoming connections and 13 | // signaling channel ready state 14 | export class queue { 15 | q: Array 16 | waiters: Array 17 | closed: boolean 18 | 19 | constructor() { 20 | this.q = []; 21 | this.waiters = []; 22 | this.closed = false; 23 | } 24 | 25 | push(obj: any) { 26 | if (this.closed) throw "closed queue"; 27 | if (this.waiters.length > 0) { 28 | let waiter = this.waiters.shift() 29 | if (waiter) waiter(obj); 30 | return; 31 | } 32 | this.q.push(obj); 33 | } 34 | 35 | shift(): Promise { 36 | if (this.closed) return Promise.resolve(undefined); 37 | return new Promise(resolve => { 38 | if (this.q.length > 0) { 39 | resolve(this.q.shift()); 40 | return; 41 | } 42 | this.waiters.push(resolve); 43 | }) 44 | } 45 | 46 | close() { 47 | if (this.closed) return; 48 | this.closed = true; 49 | this.waiters.forEach(waiter => { 50 | waiter(undefined); 51 | }); 52 | } 53 | } -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 2 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 3 | github.com/webview/webview v0.0.0-20200724072439-e0c01595b361 h1:e0+/fQY5l9NdCwPsEg9S8AgE5lFhZ/6UX+b2KkpIBFg= 4 | github.com/webview/webview v0.0.0-20200724072439-e0c01595b361/go.mod h1:rpXAuuHgyEJb6kXcXldlkOjU6y4x+YcASKKXJNUhh0Y= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 7 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 8 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= 9 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 10 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 11 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 12 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 13 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 14 | gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38VS1jM= 15 | gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8= 16 | -------------------------------------------------------------------------------- /javascript/mux/codec/message.ts: -------------------------------------------------------------------------------- 1 | 2 | export const OpenID = 100; 3 | export const OpenConfirmID = 101; 4 | export const OpenFailureID = 102; 5 | export const WindowAdjustID = 103; 6 | export const DataID = 104; 7 | export const EofID = 105; 8 | export const CloseID = 106; 9 | 10 | export var payloadSizes = new Map([ 11 | [OpenID, 12], 12 | [OpenConfirmID, 16], 13 | [OpenFailureID, 4], 14 | [WindowAdjustID, 8], 15 | [DataID, 8], 16 | [EofID, 4], 17 | [CloseID, 4], 18 | ]); 19 | 20 | export interface Message { 21 | ID: number; 22 | } 23 | 24 | export interface OpenMessage { 25 | ID: 100; 26 | senderID: number; 27 | windowSize: number; 28 | maxPacketSize: number; 29 | } 30 | 31 | export interface OpenConfirmMessage { 32 | ID: 101; 33 | channelID: number; 34 | senderID: number; 35 | windowSize: number; 36 | maxPacketSize: number; 37 | } 38 | 39 | export interface OpenFailureMessage { 40 | ID: 102; 41 | channelID: number; 42 | } 43 | 44 | export interface WindowAdjustMessage { 45 | ID: 103; 46 | channelID: number; 47 | additionalBytes: number; 48 | } 49 | 50 | export interface DataMessage { 51 | ID: 104; 52 | channelID: number; 53 | length: number; 54 | data: Uint8Array; 55 | } 56 | 57 | export interface EOFMessage { 58 | ID: 105; 59 | channelID: number; 60 | } 61 | 62 | export interface CloseMessage { 63 | ID: 106; 64 | channelID: number; 65 | } 66 | 67 | export type ChannelMessage = ( 68 | OpenConfirmMessage | 69 | OpenFailureMessage | 70 | WindowAdjustMessage | 71 | DataMessage | 72 | EOFMessage | 73 | CloseMessage); 74 | 75 | export type AnyMessage = ChannelMessage | OpenMessage; -------------------------------------------------------------------------------- /golang/mux/qmux_test.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io/ioutil" 7 | "net" 8 | "testing" 9 | ) 10 | 11 | func fatal(err error, t *testing.T) { 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | } 16 | 17 | func TestQmux(t *testing.T) { 18 | l, err := net.Listen("tcp", "127.0.0.1:9998") 19 | fatal(err, t) 20 | defer l.Close() 21 | 22 | go func() { 23 | conn, err := l.Accept() 24 | fatal(err, t) 25 | defer conn.Close() 26 | 27 | sess := NewSession(context.Background(), conn) 28 | 29 | ch, err := sess.Open() 30 | fatal(err, t) 31 | b, err := ioutil.ReadAll(ch) 32 | fatal(err, t) 33 | ch.Close() // should already be closed by other end 34 | 35 | ch, err = sess.Accept() 36 | _, err = ch.Write(b) 37 | fatal(err, t) 38 | err = ch.CloseWrite() 39 | fatal(err, t) 40 | 41 | err = sess.Close() 42 | fatal(err, t) 43 | }() 44 | 45 | conn, err := net.Dial("tcp", "127.0.0.1:9998") 46 | fatal(err, t) 47 | defer conn.Close() 48 | 49 | sess := NewSession(context.Background(), conn) 50 | 51 | var ch Channel 52 | t.Run("session accept", func(t *testing.T) { 53 | ch, err = sess.Accept() 54 | fatal(err, t) 55 | }) 56 | 57 | t.Run("channel write", func(t *testing.T) { 58 | _, err = ch.Write([]byte("Hello world")) 59 | fatal(err, t) 60 | err = ch.Close() 61 | fatal(err, t) 62 | }) 63 | 64 | t.Run("session open", func(t *testing.T) { 65 | ch, err = sess.Open() 66 | fatal(err, t) 67 | }) 68 | 69 | var b []byte 70 | t.Run("channel read", func(t *testing.T) { 71 | b, err = ioutil.ReadAll(ch) 72 | fatal(err, t) 73 | ch.Close() // should already be closed by other end 74 | }) 75 | 76 | if !bytes.Equal(b, []byte("Hello world")) { 77 | t.Fatalf("unexpected bytes: %s", b) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /_examples/webview/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 18 |
19 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/demo/_dev.js: -------------------------------------------------------------------------------- 1 | var qrpc = require("./qrpc.js"); 2 | var libmux = require("libmux"); 3 | 4 | var session = null; 5 | var listener = null; 6 | 7 | function sleep(ms) { 8 | return new Promise(res => setTimeout(res, ms)); 9 | } 10 | 11 | class Person { 12 | constructor(name) { 13 | this.name = name; 14 | this.age = 30; 15 | } 16 | 17 | Name() { 18 | return this.name; 19 | } 20 | 21 | Age() { 22 | return this.age.toString(); 23 | } 24 | 25 | IncrAge() { 26 | this.age++; 27 | } 28 | } 29 | 30 | class Service { 31 | constructor(objs) { 32 | this.objs = objs; 33 | } 34 | 35 | NewPerson(name) { 36 | var obj = this.objs.register(new Person("Jeff")); 37 | return {"ObjectPath": obj.path()}; 38 | } 39 | 40 | Person(handle) { 41 | return JSON.stringify(this.objs.object(handle.ObjectPath).value); 42 | } 43 | 44 | upper(v) { 45 | return v.toUpperCase(); 46 | } 47 | } 48 | 49 | (async () => { 50 | var api = new qrpc.API(); 51 | var objs = new qrpc.ObjectManager(); 52 | api.handle("demo", qrpc.Export(new Service(objs))); 53 | objs.mount(api, "objs"); 54 | api.handle("obj", qrpc.Export({ 55 | "lower": function(v) { 56 | return v.toLowerCase(); 57 | } 58 | })); 59 | 60 | listener = await libmux.ListenWebsocket("localhost:4242"); 61 | var server = new qrpc.Server(); 62 | console.log("serving..."); 63 | await server.serve(listener, api); 64 | 65 | // console.log("connecting..."); 66 | // session = await libmux.DialTCP("localhost:4242"); 67 | // var client = new qrpc.Client(session, api); 68 | // //client.serveAPI(); 69 | // console.log("ret: "+await client.call("echo", "Hello world!")); 70 | // await session.close(); 71 | // await listener.close(); 72 | })().catch(async (err) => { 73 | console.log(err.stack); 74 | }); -------------------------------------------------------------------------------- /javascript/rpc/peer.ts: -------------------------------------------------------------------------------- 1 | import * as internal from "./internal.ts"; 2 | 3 | export class Peer { 4 | session: internal.ISession; 5 | caller: internal.Caller; 6 | responder: internal.RespondMux; 7 | 8 | constructor(session: internal.ISession, codec: internal.Codec) { 9 | this.session = session; 10 | this.caller = new internal.caller(session, codec); 11 | this.responder = new internal.RespondMux(codec); 12 | } 13 | 14 | async respond() { 15 | while (true) { 16 | let ch = await this.session.accept(); 17 | internal.Respond(this.session, ch, this.responder); 18 | } 19 | } 20 | 21 | async call(path: string, args: any): Promise { 22 | return this.caller.call(path, args); 23 | } 24 | 25 | bind(path: string, handler: internal.Handler): void { 26 | this.responder.bind(path, handler); 27 | } 28 | 29 | bindFunc(path: string, handler: (r: internal.Responder, c: internal.Call) => void): void { 30 | this.responder.bindFunc(path, handler); 31 | } 32 | 33 | } 34 | 35 | export function CallProxy(caller: internal.Caller): any { 36 | return new Proxy(caller, { 37 | get: (t,p,r) => { 38 | let prop = p as string; 39 | if (prop.startsWith("$")) { 40 | return async (...args: any[]) => { 41 | let params: any = args; 42 | if (args.length === 1) { 43 | params = args[0]; 44 | } 45 | if (args.length === 0) { 46 | params = undefined; 47 | } 48 | let resp = await caller.call(prop.slice(1), params); 49 | if (resp.error) { 50 | throw resp.error; 51 | } 52 | return resp.reply; 53 | } 54 | } 55 | return Reflect.get(t, p, r); 56 | } 57 | }) 58 | } -------------------------------------------------------------------------------- /exp/bus/bus.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/progrium/prototypes/libmux/mux" 11 | "github.com/progrium/prototypes/qrpc" 12 | ) 13 | 14 | type Bus struct { 15 | qrpc.API 16 | 17 | backends map[string]mux.Session 18 | mu sync.Mutex 19 | } 20 | 21 | func NewBus() *Bus { 22 | bus := &Bus{ 23 | API: qrpc.NewAPI(), 24 | backends: make(map[string]mux.Session), 25 | } 26 | bus.API.HandleFunc("register", func(r qrpc.Responder, c *qrpc.Call) { 27 | session := c.Caller.(*qrpc.Client).Session 28 | var paths []string 29 | err := c.Decode(&paths) 30 | if err != nil { 31 | r.Return(err) 32 | return 33 | } 34 | bus.mu.Lock() 35 | for _, path := range paths { 36 | bus.backends[path] = session 37 | } 38 | bus.mu.Unlock() 39 | r.Return(nil) 40 | }) 41 | return bus 42 | } 43 | 44 | func (b *Bus) ServeAPI(sess mux.Session, ch mux.Channel, c qrpc.Codec) { 45 | var buf bytes.Buffer 46 | dec := c.Decoder(io.TeeReader(ch, &buf)) 47 | var call qrpc.Call 48 | err := dec.Decode(&call) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | err = call.Parse() 54 | if err != nil { 55 | panic(err) 56 | } 57 | resp := qrpc.NewResponder(ch, c, &qrpc.ResponseHeader{}) 58 | 59 | handler := b.API.Handler(call.Destination) 60 | if handler != nil { 61 | call.Decoder = dec 62 | call.Caller = &qrpc.Client{ 63 | Session: sess, 64 | } 65 | handler.ServeRPC(resp, &call) 66 | return 67 | } 68 | 69 | b.mu.Lock() 70 | var backend mux.Session 71 | for k, v := range b.backends { 72 | if strings.HasPrefix(call.Destination, k) { 73 | backend = v 74 | break 75 | } 76 | } 77 | b.mu.Unlock() 78 | if backend == nil { 79 | resp.Return(errors.New("backend does not exist for this destination")) 80 | return 81 | } 82 | 83 | bch, err := backend.Open() 84 | if err != nil { 85 | resp.Return(err) 86 | return 87 | } 88 | var wg sync.WaitGroup 89 | wg.Add(1) 90 | go func() { 91 | defer wg.Done() 92 | io.Copy(ch, bch) 93 | }() 94 | _, err = buf.WriteTo(bch) 95 | if err != nil { 96 | resp.Return(err) 97 | return 98 | } 99 | wg.Add(1) 100 | go func() { 101 | defer wg.Done() 102 | io.Copy(bch, ch) 103 | }() 104 | wg.Wait() 105 | } 106 | -------------------------------------------------------------------------------- /golang/rpc/codec.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "io" 8 | ) 9 | 10 | type Encoder interface { 11 | Encode(v interface{}) error 12 | } 13 | 14 | type Decoder interface { 15 | Decode(v interface{}) error 16 | } 17 | 18 | type Codec interface { 19 | Encoder(w io.Writer) Encoder 20 | Decoder(r io.Reader) Decoder 21 | } 22 | 23 | // length prefixed frame wrapper codec 24 | type frameCodec struct { 25 | Codec 26 | } 27 | 28 | func (c *frameCodec) Encoder(w io.Writer) Encoder { 29 | return &frameEncoder{ 30 | w: w, 31 | c: c.Codec, 32 | } 33 | } 34 | 35 | type frameEncoder struct { 36 | w io.Writer 37 | c Codec 38 | } 39 | 40 | func (e *frameEncoder) Encode(v interface{}) error { 41 | var buf bytes.Buffer 42 | enc := e.c.Encoder(&buf) 43 | err := enc.Encode(v) 44 | if err != nil { 45 | return err 46 | } 47 | b := buf.Bytes() 48 | prefix := make([]byte, 4) 49 | binary.BigEndian.PutUint32(prefix, uint32(len(b))) 50 | _, err = e.w.Write(append(prefix, b...)) 51 | if err != nil { 52 | return err 53 | } 54 | return nil 55 | } 56 | 57 | func (c *frameCodec) Decoder(r io.Reader) Decoder { 58 | return &frameDecoder{ 59 | r: r, 60 | c: c.Codec, 61 | } 62 | } 63 | 64 | type frameDecoder struct { 65 | r io.Reader 66 | c Codec 67 | } 68 | 69 | func (d *frameDecoder) Decode(v interface{}) error { 70 | prefix := make([]byte, 4) 71 | _, err := d.r.Read(prefix) 72 | if err != nil { 73 | return err 74 | } 75 | size := binary.BigEndian.Uint32(prefix) 76 | buf := make([]byte, size) 77 | _, err = d.r.Read(buf) 78 | if err != nil { 79 | return err 80 | } 81 | dec := d.c.Decoder(bytes.NewBuffer(buf)) 82 | err = dec.Decode(v) 83 | if err != nil { 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | // type MsgpackCodec struct{} 90 | 91 | // func (c MsgpackCodec) Encoder(w io.Writer) Encoder { 92 | // return msgpack.NewEncoder(w) 93 | // } 94 | 95 | // func (c MsgpackCodec) Decoder(r io.Reader) Decoder { 96 | // return msgpack.NewDecoder(r) 97 | // } 98 | 99 | type JSONCodec struct{} 100 | 101 | func (c JSONCodec) Encoder(w io.Writer) Encoder { 102 | return json.NewEncoder(w) 103 | } 104 | 105 | func (c JSONCodec) Decoder(r io.Reader) Decoder { 106 | return json.NewDecoder(r) 107 | } 108 | -------------------------------------------------------------------------------- /golang/mux/transport/listeners.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net" 7 | "net/http" 8 | 9 | "github.com/manifold/qtalk/golang/mux" 10 | "golang.org/x/net/websocket" 11 | ) 12 | 13 | func ListenTCP(addr string) (mux.Listener, error) { 14 | return listenNet("tcp", addr) 15 | } 16 | 17 | func ListenUnix(addr string) (mux.Listener, error) { 18 | return listenNet("unix", addr) 19 | } 20 | 21 | func listenNet(proto, addr string) (mux.Listener, error) { 22 | l, err := net.Listen(proto, addr) 23 | if err != nil { 24 | return nil, err 25 | } 26 | closer := make(chan bool, 1) 27 | errs := make(chan error, 1) 28 | accepted := make(chan mux.Session) 29 | go func(l net.Listener) { 30 | for { 31 | conn, err := l.Accept() 32 | if err != nil { 33 | errs <- err 34 | return 35 | } 36 | accepted <- mux.NewSession(context.Background(), conn) 37 | } 38 | }(l) 39 | return &listener{ 40 | Listener: l, 41 | errs: errs, 42 | accepted: accepted, 43 | closer: closer, 44 | }, nil 45 | } 46 | 47 | func ListenWS(addr string) (mux.Listener, error) { 48 | l, err := net.Listen("tcp", addr) 49 | if err != nil { 50 | return nil, err 51 | } 52 | closer := make(chan bool, 1) 53 | errs := make(chan error, 1) 54 | accepted := make(chan mux.Session) 55 | s := &http.Server{ 56 | Addr: addr, 57 | Handler: websocket.Handler(func(ws *websocket.Conn) { 58 | ws.PayloadType = websocket.BinaryFrame 59 | sess := mux.NewSession(context.Background(), ws) 60 | accepted <- sess 61 | sess.Wait() 62 | }), 63 | } 64 | go s.Serve(l) 65 | return &listener{ 66 | Listener: l, 67 | errs: errs, 68 | accepted: accepted, 69 | closer: closer, 70 | }, nil 71 | } 72 | 73 | type listener struct { 74 | net.Listener 75 | accepted chan mux.Session 76 | closer chan bool 77 | errs chan error 78 | } 79 | 80 | func (l *listener) Accept() (mux.Session, error) { 81 | // TODO: context cancelation 82 | select { 83 | case <-l.closer: 84 | return nil, io.EOF 85 | case err := <-l.errs: 86 | return nil, err 87 | case sess := <-l.accepted: 88 | return sess, nil 89 | } 90 | } 91 | 92 | func (l *listener) Addr() net.Addr { 93 | return l.Addr() 94 | } 95 | 96 | func (l *listener) Close() error { 97 | if l.closer != nil { 98 | l.closer <- true 99 | } 100 | return l.Listener.Close() 101 | } 102 | -------------------------------------------------------------------------------- /golang/mux/codec/encoder.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | type Encoder struct { 11 | w io.Writer 12 | sync.Mutex 13 | } 14 | 15 | func NewEncoder(w io.Writer) *Encoder { 16 | return &Encoder{w: w} 17 | } 18 | 19 | func (enc *Encoder) Encode(msg interface{}) error { 20 | enc.Lock() 21 | defer enc.Unlock() 22 | 23 | // fmt.Println("<<", msg) 24 | 25 | b, err := Marshal(msg) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | _, err = enc.w.Write(b) 31 | return err 32 | } 33 | 34 | func Marshal(v interface{}) ([]byte, error) { 35 | switch msg := v.(type) { 36 | case OpenMessage: 37 | packet := make([]byte, payloadSizes[msgChannelOpen]+1) 38 | packet[0] = msgChannelOpen 39 | binary.BigEndian.PutUint32(packet[1:5], msg.SenderID) 40 | binary.BigEndian.PutUint32(packet[5:9], msg.WindowSize) 41 | binary.BigEndian.PutUint32(packet[9:13], msg.MaxPacketSize) 42 | return packet, nil 43 | 44 | case OpenConfirmMessage: 45 | packet := make([]byte, payloadSizes[msgChannelOpenConfirm]+1) 46 | packet[0] = msgChannelOpenConfirm 47 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 48 | binary.BigEndian.PutUint32(packet[5:9], msg.SenderID) 49 | binary.BigEndian.PutUint32(packet[9:13], msg.WindowSize) 50 | binary.BigEndian.PutUint32(packet[13:17], msg.MaxPacketSize) 51 | return packet, nil 52 | 53 | case OpenFailureMessage: 54 | packet := make([]byte, payloadSizes[msgChannelOpenFailure]+1) 55 | packet[0] = msgChannelOpenFailure 56 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 57 | return packet, nil 58 | 59 | case WindowAdjustMessage: 60 | packet := make([]byte, payloadSizes[msgChannelWindowAdjust]+1) 61 | packet[0] = msgChannelWindowAdjust 62 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 63 | binary.BigEndian.PutUint32(packet[5:9], msg.AdditionalBytes) 64 | return packet, nil 65 | 66 | case DataMessage: 67 | packet := make([]byte, payloadSizes[msgChannelData]+1) 68 | packet[0] = msgChannelData 69 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 70 | binary.BigEndian.PutUint32(packet[5:9], msg.Length) 71 | return append(packet, msg.Data...), nil 72 | 73 | case EOFMessage: 74 | packet := make([]byte, payloadSizes[msgChannelEOF]+1) 75 | packet[0] = msgChannelEOF 76 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 77 | return packet, nil 78 | 79 | case CloseMessage: 80 | packet := make([]byte, payloadSizes[msgChannelClose]+1) 81 | packet[0] = msgChannelClose 82 | binary.BigEndian.PutUint32(packet[1:5], msg.ChannelID) 83 | return packet, nil 84 | 85 | default: 86 | return []byte{}, fmt.Errorf("qmux: unable to marshal type") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /javascript/rpc/caller.ts: -------------------------------------------------------------------------------- 1 | import * as internal from "./internal.ts"; 2 | 3 | export interface Caller { 4 | call(method: string, args: any): Promise; 5 | } 6 | 7 | export class Call { 8 | Destination: string; 9 | objectPath: string; 10 | method: string; 11 | caller: Caller|undefined; 12 | decode: any; 13 | 14 | constructor(destination: string) { 15 | this.Destination = destination; 16 | if (this.Destination.length === 0) { 17 | throw new Error("no destination specified"); 18 | } 19 | if (this.Destination[0] == "/") { 20 | this.Destination = this.Destination.substr(1); 21 | } 22 | var parts = this.Destination.split("/"); 23 | if (parts.length === 1) { 24 | this.objectPath = "/"; 25 | this.method = parts[0]; 26 | return; 27 | } 28 | this.method = parts.pop() as string; 29 | this.objectPath = parts.join("/"); 30 | } 31 | 32 | } 33 | 34 | export class Response { 35 | error: string|undefined; 36 | hijacked: boolean; 37 | reply: any; 38 | channel: internal.IChannel; 39 | 40 | constructor(channel: internal.IChannel) { 41 | this.channel = channel; 42 | this.error = undefined; 43 | this.hijacked = false; 44 | } 45 | } 46 | 47 | export class caller implements Caller { 48 | session: internal.ISession; 49 | codec: internal.Codec; 50 | 51 | constructor(session: internal.ISession, codec: internal.Codec) { 52 | this.session = session; 53 | this.codec = codec; 54 | } 55 | 56 | async call(path: string, args: any): Promise { 57 | try { 58 | let ch = await this.session.open(); 59 | let resp: internal.Response = new internal.Response(ch); 60 | let codec = new internal.FrameCodec(ch, this.codec, 2); 61 | 62 | await codec.encode(new Call(path)); 63 | await codec.encode(args); 64 | 65 | let header: internal.ResponseHeader = await codec.decode(); 66 | if (header.Error !== undefined && header.Error !== null) { 67 | // await ch.close(); 68 | // return Promise.reject(header.Error); 69 | console.error(header); 70 | } 71 | 72 | resp.error = header.Error; 73 | resp.hijacked = header.Hijacked; 74 | resp.reply = await codec.decode(); 75 | if (resp.hijacked !== true) { 76 | await ch.close(); 77 | } 78 | 79 | return resp; 80 | } catch (e) { 81 | console.error(e, path, args); 82 | return Promise.reject("call?"); 83 | //await ch.close(); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /javascript/legacy/qmux/src/qmux_browser.ts: -------------------------------------------------------------------------------- 1 | import * as qmux from "./qmux"; 2 | 3 | export var Session = qmux.Session; 4 | export var Channel = qmux.Session; 5 | export var Buffer = require("buffer").Buffer; 6 | 7 | interface IConn { 8 | read(len: number): Promise; 9 | write(buffer: Buffer): Promise; 10 | close(): Promise; 11 | } 12 | 13 | export function DialWebsocket(addr: string): Promise { 14 | return new Promise((resolve, reject) => { 15 | var socket = new WebSocket(addr); 16 | socket.onopen = () => { 17 | resolve(new Conn(socket)); 18 | }; 19 | socket.onerror = (err) => { 20 | reject(err); 21 | }; 22 | }) 23 | } 24 | 25 | 26 | export class Conn implements IConn { 27 | socket: WebSocket 28 | error: any 29 | waiters: Array 30 | buf: Buffer 31 | isClosed: boolean 32 | 33 | constructor(socket: WebSocket) { 34 | this.isClosed = false; 35 | this.buf = new Buffer(0); 36 | this.waiters = []; 37 | this.socket = socket; 38 | this.socket.binaryType = "arraybuffer"; 39 | this.socket.onmessage = (event) => { 40 | var buf = Buffer.from(event.data); 41 | this.buf = Buffer.concat([this.buf, buf], this.buf.length+buf.length); 42 | if (this.waiters.length > 0) { 43 | this.waiters.shift()(); 44 | } 45 | }; 46 | this.socket.onclose = () => { 47 | this.close(); 48 | }; 49 | this.socket.onerror = (err) => { 50 | console.log("err", err) 51 | }; 52 | } 53 | 54 | read(len: number): Promise { 55 | return new Promise((resolve, reject) => { 56 | var tryRead = () => { 57 | if (this.buf === undefined) { 58 | resolve(undefined); 59 | return; 60 | } 61 | if (this.buf.length >= len) { 62 | var data = this.buf.slice(0, len); 63 | this.buf = this.buf.slice(len); 64 | resolve(data); 65 | return; 66 | } 67 | this.waiters.push(tryRead); 68 | } 69 | tryRead(); 70 | }) 71 | } 72 | 73 | write(buffer: Buffer): Promise { 74 | this.socket.send(buffer.buffer); 75 | return Promise.resolve(buffer.byteLength); 76 | } 77 | 78 | close(): Promise { 79 | if (this.isClosed) return Promise.resolve(); 80 | return new Promise((resolve, reject) => { 81 | this.isClosed = true; 82 | this.buf = undefined; 83 | this.waiters.forEach(waiter => waiter()); 84 | this.socket.close(); 85 | resolve(); 86 | }); 87 | } 88 | } -------------------------------------------------------------------------------- /golang/rpc/object/objects.go: -------------------------------------------------------------------------------- 1 | package object 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | path_ "path" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/oklog/ulid" 12 | ) 13 | 14 | func generateULID() string { 15 | t := time.Unix(1000000, 0) 16 | entropy := rand.New(rand.NewSource(t.UnixNano())) 17 | return ulid.MustNew(ulid.Timestamp(t), entropy).String() 18 | } 19 | 20 | type ObjectManager struct { 21 | mountPath string 22 | values map[string]interface{} 23 | mu sync.Mutex 24 | } 25 | 26 | func NewObjectManager() *ObjectManager { 27 | return &ObjectManager{ 28 | values: make(map[string]interface{}), 29 | mountPath: "/", 30 | } 31 | } 32 | 33 | func (m *ObjectManager) Object(path string) ManagedObject { 34 | m.mu.Lock() 35 | defer m.mu.Unlock() 36 | id := strings.TrimPrefix(path, m.mountPath) 37 | if id[0] == '/' { 38 | id = id[1:] 39 | } 40 | v, ok := m.values[id] 41 | if !ok { 42 | return nil 43 | } 44 | return &object{value: v, id: id, manager: m} 45 | } 46 | 47 | func (m *ObjectManager) Register(v interface{}) ManagedObject { 48 | m.mu.Lock() 49 | defer m.mu.Unlock() 50 | id := generateULID() 51 | m.values[id] = v 52 | return &object{value: v, id: id, manager: m} 53 | } 54 | 55 | func (m *ObjectManager) Handle(v interface{}) ObjectHandle { 56 | return m.Register(v).Handle() 57 | } 58 | 59 | func (m *ObjectManager) ServeRPC(r Responder, c *Call) { 60 | parts := strings.Split(c.ObjectPath, "/") 61 | id := parts[len(parts)-1] 62 | m.mu.Lock() 63 | v, ok := m.values[id] 64 | m.mu.Unlock() 65 | if !ok { 66 | r.Return(fmt.Errorf("object not registered: %s", c.ObjectPath)) 67 | return 68 | } 69 | handler, err := Export(v) 70 | if err != nil { 71 | r.Return(err) 72 | return 73 | } 74 | handler.ServeRPC(r, c) 75 | } 76 | 77 | func (m *ObjectManager) Mount(api API, path string) { 78 | m.mu.Lock() 79 | defer m.mu.Unlock() 80 | m.mountPath = path 81 | api.Handle(path, m) 82 | } 83 | 84 | type ManagedObject interface { 85 | Dispose() 86 | Path() string 87 | Value() interface{} 88 | Handle() ObjectHandle 89 | } 90 | 91 | type ObjectHandle struct { 92 | ObjectPath string 93 | } 94 | 95 | type object struct { 96 | manager *ObjectManager 97 | id string 98 | value interface{} 99 | } 100 | 101 | func (o *object) Dispose() { 102 | o.manager.mu.Lock() 103 | defer o.manager.mu.Unlock() 104 | delete(o.manager.values, o.id) 105 | } 106 | 107 | func (o *object) Path() string { 108 | o.manager.mu.Lock() 109 | defer o.manager.mu.Unlock() 110 | return path_.Join(o.manager.mountPath, o.id) 111 | } 112 | 113 | func (o *object) Handle() ObjectHandle { 114 | return ObjectHandle{ 115 | ObjectPath: o.Path(), 116 | } 117 | } 118 | 119 | func (o *object) Value() interface{} { 120 | return o.value 121 | } 122 | -------------------------------------------------------------------------------- /golang/rpc/caller.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "strings" 9 | 10 | "github.com/manifold/qtalk/golang/mux" 11 | ) 12 | 13 | type Caller interface { 14 | Call(path string, args, reply interface{}) (*Response, error) 15 | } 16 | 17 | type Response struct { 18 | ResponseHeader 19 | 20 | Reply interface{} 21 | Channel mux.Channel 22 | } 23 | 24 | type Call struct { 25 | Destination string 26 | ObjectPath string 27 | Method string 28 | Caller Caller 29 | Decoder Decoder 30 | Context context.Context 31 | } 32 | 33 | func (c *Call) Parse() error { 34 | if len(c.Destination) == 0 { 35 | return fmt.Errorf("no destination specified") 36 | } 37 | if c.Destination[0] == '/' { 38 | c.Destination = c.Destination[1:] 39 | } 40 | parts := strings.Split(c.Destination, "/") 41 | if len(parts) == 1 { 42 | c.ObjectPath = "/" 43 | c.Method = parts[0] 44 | return nil 45 | } 46 | c.ObjectPath = strings.Join(parts[0:len(parts)-1], "/") 47 | c.Method = parts[len(parts)-1] 48 | return nil 49 | } 50 | 51 | func (c *Call) Decode(v interface{}) error { 52 | return c.Decoder.Decode(v) 53 | } 54 | 55 | type caller struct { 56 | session mux.Session 57 | codec Codec 58 | } 59 | 60 | func NewCaller(session mux.Session, codec Codec) Caller { 61 | return &caller{ 62 | session: session, 63 | codec: codec, 64 | } 65 | } 66 | 67 | func (c *caller) Call(path string, args, reply interface{}) (*Response, error) { 68 | ch, err := c.session.Open() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | codec := &frameCodec{c.codec} 74 | enc := codec.Encoder(ch) 75 | dec := codec.Decoder(ch) 76 | 77 | // request 78 | err = enc.Encode(Call{ 79 | Destination: path, 80 | }) 81 | if err != nil { 82 | ch.Close() 83 | return nil, err 84 | } 85 | 86 | err = enc.Encode(args) 87 | if err != nil { 88 | ch.Close() 89 | return nil, err 90 | } 91 | 92 | // response 93 | var header ResponseHeader 94 | err = dec.Decode(&header) 95 | if err != nil { 96 | ch.Close() 97 | return nil, err 98 | } 99 | 100 | if !header.Hijacked { 101 | defer ch.Close() 102 | } 103 | 104 | resp := &Response{ 105 | ResponseHeader: header, 106 | Channel: ch, 107 | Reply: reply, 108 | } 109 | if resp.Error != nil { 110 | return resp, fmt.Errorf("remote: %s", *(resp.Error)) 111 | } 112 | 113 | if reply == nil { 114 | // read into throwaway buffer 115 | var buf []byte 116 | if err := dec.Decode(&buf); err != nil { 117 | if errors.Is(err, io.EOF) { 118 | return resp, nil 119 | } 120 | return resp, err 121 | } 122 | } else { 123 | if err := dec.Decode(resp.Reply); err != nil { 124 | return resp, err 125 | } 126 | } 127 | 128 | return resp, nil 129 | } 130 | -------------------------------------------------------------------------------- /javascript/mux/codec/encoder.ts: -------------------------------------------------------------------------------- 1 | import * as api from "../api.ts"; 2 | import * as msg from "./message.ts"; 3 | 4 | export class Encoder { 5 | conn: api.IConn; 6 | debug: boolean; 7 | 8 | constructor(conn: api.IConn, debug: boolean = false) { 9 | this.conn = conn; 10 | this.debug = debug; 11 | } 12 | 13 | async encode(m: msg.AnyMessage): Promise { 14 | if (this.debug) { 15 | console.log("<<", m); 16 | } 17 | return this.conn.write(Marshal(m)); 18 | } 19 | } 20 | 21 | export function Marshal(obj: msg.AnyMessage): Uint8Array { 22 | if (obj.ID === msg.CloseID) { 23 | let m = obj as msg.CloseMessage; 24 | let data = new DataView(new ArrayBuffer(5)); 25 | data.setUint8(0, m.ID); 26 | data.setUint32(1, m.channelID); 27 | return new Uint8Array(data.buffer); 28 | } 29 | if (obj.ID === msg.DataID) { 30 | let m = obj as msg.DataMessage; 31 | let data = new DataView(new ArrayBuffer(9)); 32 | data.setUint8(0, m.ID); 33 | data.setUint32(1, m.channelID); 34 | data.setUint32(5, m.length); 35 | let buf = new Uint8Array(9+m.length); 36 | buf.set(new Uint8Array(data.buffer), 0); 37 | buf.set(m.data, 9); 38 | return buf; 39 | } 40 | if (obj.ID === msg.EofID) { 41 | let m = obj as msg.EOFMessage; 42 | let data = new DataView(new ArrayBuffer(5)); 43 | data.setUint8(0, m.ID); 44 | data.setUint32(1, m.channelID); 45 | return new Uint8Array(data.buffer); 46 | } 47 | if (obj.ID === msg.OpenID) { 48 | let m = obj as msg.OpenMessage; 49 | let data = new DataView(new ArrayBuffer(13)); 50 | data.setUint8(0, m.ID); 51 | data.setUint32(1, m.senderID); 52 | data.setUint32(5, m.windowSize); 53 | data.setUint32(9, m.maxPacketSize); 54 | return new Uint8Array(data.buffer); 55 | } 56 | if (obj.ID === msg.OpenConfirmID) { 57 | let m = obj as msg.OpenConfirmMessage; 58 | let data = new DataView(new ArrayBuffer(17)); 59 | data.setUint8(0, m.ID); 60 | data.setUint32(1, m.channelID); 61 | data.setUint32(5, m.senderID); 62 | data.setUint32(9, m.windowSize); 63 | data.setUint32(13, m.maxPacketSize); 64 | return new Uint8Array(data.buffer); 65 | } 66 | if (obj.ID === msg.OpenFailureID) { 67 | let m = obj as msg.OpenFailureMessage; 68 | let data = new DataView(new ArrayBuffer(5)); 69 | data.setUint8(0, m.ID); 70 | data.setUint32(1, m.channelID); 71 | return new Uint8Array(data.buffer); 72 | } 73 | if (obj.ID === msg.WindowAdjustID) { 74 | let m = obj as msg.WindowAdjustMessage; 75 | let data = new DataView(new ArrayBuffer(9)); 76 | data.setUint8(0, m.ID); 77 | data.setUint32(1, m.channelID); 78 | data.setUint32(5, m.additionalBytes); 79 | return new Uint8Array(data.buffer); 80 | } 81 | throw `marshal of unknown type: ${obj}`; 82 | } -------------------------------------------------------------------------------- /javascript/mux/transport/websocket.ts: -------------------------------------------------------------------------------- 1 | import * as api from "./../api.ts"; 2 | import * as internal from "./../internal.ts"; 3 | import * as util from "./../util.ts"; 4 | 5 | export function Dial(addr: string, debug: boolean = false, onclose?: () => void): Promise { 6 | return new Promise((resolve) => { 7 | var socket = new WebSocket(addr); 8 | socket.onopen = () => resolve(new internal.Session(new Conn(socket), debug)); 9 | //socket.onerror = (err) => console.error("qtalk", err); 10 | if (onclose) socket.onclose = onclose; 11 | }) 12 | } 13 | 14 | export class Conn implements api.IConn { 15 | socket: WebSocket 16 | error: any 17 | waiters: Array 18 | buf: Uint8Array; 19 | isClosed: boolean 20 | 21 | constructor(socket: WebSocket) { 22 | this.isClosed = false; 23 | this.buf = new Uint8Array(0); 24 | this.waiters = []; 25 | this.socket = socket; 26 | this.socket.binaryType = "arraybuffer"; 27 | this.socket.onmessage = (event) => { 28 | var buf = new Uint8Array(event.data); 29 | this.buf = util.concat([this.buf, buf], this.buf.length+buf.length); 30 | if (this.waiters.length > 0) { 31 | let waiter = this.waiters.shift(); 32 | if (waiter) waiter(); 33 | } 34 | }; 35 | let onclose = this.socket.onclose; 36 | this.socket.onclose = (e: CloseEvent) => { 37 | if (onclose) onclose.bind(this.socket)(e); 38 | this.close(); 39 | } 40 | //this.socket.onerror = (err) => console.error("qtalk", err); 41 | } 42 | 43 | read(len: number): Promise { 44 | return new Promise((resolve) => { 45 | var tryRead = () => { 46 | if (this.isClosed) { 47 | resolve(undefined); 48 | return; 49 | } 50 | if (!len && this.buf.length > 0) { 51 | let data = this.buf; 52 | this.buf = this.buf.slice(this.buf.length); 53 | resolve(data); 54 | return; 55 | } 56 | if (this.buf.length >= len) { 57 | var data = this.buf.slice(0, len); 58 | this.buf = this.buf.slice(len); 59 | resolve(data); 60 | return; 61 | } 62 | this.waiters.push(tryRead); 63 | } 64 | tryRead(); 65 | }) 66 | } 67 | 68 | write(buffer: Uint8Array): Promise { 69 | this.socket.send(buffer); 70 | return Promise.resolve(buffer.byteLength); 71 | } 72 | 73 | close(): Promise { 74 | if (this.isClosed) return Promise.resolve(); 75 | return new Promise((resolve) => { 76 | this.isClosed = true; 77 | this.waiters.forEach(waiter => waiter()); 78 | this.socket.close(); 79 | resolve(); 80 | }); 81 | } 82 | } -------------------------------------------------------------------------------- /golang/mux/codec/messages.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import "fmt" 4 | 5 | const ( 6 | msgChannelOpen = iota + 100 7 | msgChannelOpenConfirm 8 | msgChannelOpenFailure 9 | msgChannelWindowAdjust 10 | msgChannelData 11 | msgChannelEOF 12 | msgChannelClose 13 | ) 14 | 15 | var ( 16 | payloadSizes = map[byte]int{ 17 | msgChannelOpen: 12, 18 | msgChannelOpenConfirm: 16, 19 | msgChannelOpenFailure: 4, 20 | msgChannelWindowAdjust: 8, 21 | msgChannelData: 8, 22 | msgChannelEOF: 4, 23 | msgChannelClose: 4, 24 | } 25 | ) 26 | 27 | type Message interface { 28 | Channel() (uint32, bool) 29 | } 30 | 31 | type OpenMessage struct { 32 | SenderID uint32 33 | WindowSize uint32 34 | MaxPacketSize uint32 35 | } 36 | 37 | func (msg OpenMessage) String() string { 38 | return fmt.Sprintf("{OpenMessage SenderID:%d WindowSize:%d MaxPacketSize:%d}", 39 | msg.SenderID, msg.WindowSize, msg.MaxPacketSize) 40 | } 41 | 42 | func (msg OpenMessage) Channel() (uint32, bool) { 43 | return 0, false 44 | } 45 | 46 | type OpenConfirmMessage struct { 47 | ChannelID uint32 48 | SenderID uint32 49 | WindowSize uint32 50 | MaxPacketSize uint32 51 | } 52 | 53 | func (msg OpenConfirmMessage) String() string { 54 | return fmt.Sprintf("{OpenConfirmMessage ChannelID:%d SenderID:%d WindowSize:%d MaxPacketSize:%d}", 55 | msg.ChannelID, msg.SenderID, msg.WindowSize, msg.MaxPacketSize) 56 | } 57 | 58 | func (msg OpenConfirmMessage) Channel() (uint32, bool) { 59 | return msg.ChannelID, true 60 | } 61 | 62 | type OpenFailureMessage struct { 63 | ChannelID uint32 64 | } 65 | 66 | func (msg OpenFailureMessage) String() string { 67 | return fmt.Sprintf("{OpenFailureMessage ChannelID:%d}", msg.ChannelID) 68 | } 69 | 70 | func (msg OpenFailureMessage) Channel() (uint32, bool) { 71 | return msg.ChannelID, true 72 | } 73 | 74 | type WindowAdjustMessage struct { 75 | ChannelID uint32 76 | AdditionalBytes uint32 77 | } 78 | 79 | func (msg WindowAdjustMessage) String() string { 80 | return fmt.Sprintf("{WindowAdjustMessage ChannelID:%d AdditionalBytes:%d}", 81 | msg.ChannelID, msg.AdditionalBytes) 82 | } 83 | 84 | func (msg WindowAdjustMessage) Channel() (uint32, bool) { 85 | return msg.ChannelID, true 86 | } 87 | 88 | type DataMessage struct { 89 | ChannelID uint32 90 | Length uint32 91 | Data []byte 92 | } 93 | 94 | func (msg DataMessage) String() string { 95 | return fmt.Sprintf("{DataMessage ChannelID:%d Length:%d Data: ... }", 96 | msg.ChannelID, msg.Length) 97 | } 98 | 99 | func (msg DataMessage) Channel() (uint32, bool) { 100 | return msg.ChannelID, true 101 | } 102 | 103 | type EOFMessage struct { 104 | ChannelID uint32 105 | } 106 | 107 | func (msg EOFMessage) String() string { 108 | return fmt.Sprintf("{EOFMessage ChannelID:%d}", msg.ChannelID) 109 | } 110 | 111 | func (msg EOFMessage) Channel() (uint32, bool) { 112 | return msg.ChannelID, true 113 | } 114 | 115 | type CloseMessage struct { 116 | ChannelID uint32 117 | } 118 | 119 | func (msg CloseMessage) String() string { 120 | return fmt.Sprintf("{CloseMessage ChannelID:%d}", msg.ChannelID) 121 | } 122 | 123 | func (msg CloseMessage) Channel() (uint32, bool) { 124 | return msg.ChannelID, true 125 | } 126 | -------------------------------------------------------------------------------- /golang/mux/codec/decoder.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | type Decoder struct { 11 | r io.Reader 12 | sync.Mutex 13 | } 14 | 15 | func NewDecoder(r io.Reader) *Decoder { 16 | return &Decoder{r: r} 17 | } 18 | 19 | func (dec *Decoder) Decode() (Message, error) { 20 | dec.Lock() 21 | defer dec.Unlock() 22 | 23 | packet, err := readPacket(dec.r) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return decode(packet) 29 | } 30 | 31 | func readPacket(c io.Reader) ([]byte, error) { 32 | msgNum := make([]byte, 1) 33 | _, err := c.Read(msgNum) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | rest := make([]byte, payloadSizes[msgNum[0]]) 39 | _, err = c.Read(rest) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | packet := append(msgNum, rest...) 45 | 46 | if msgNum[0] == msgChannelData { 47 | dataSize := binary.BigEndian.Uint32(rest[4:8]) 48 | data := make([]byte, dataSize) 49 | _, err := c.Read(data) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | packet = append(packet, data...) 55 | } 56 | 57 | return packet, nil 58 | } 59 | 60 | func decode(packet []byte) (Message, error) { 61 | var msg Message 62 | switch packet[0] { 63 | case msgChannelOpen: 64 | msg = new(OpenMessage) 65 | case msgChannelData: 66 | msg = new(DataMessage) 67 | case msgChannelOpenConfirm: 68 | msg = new(OpenConfirmMessage) 69 | case msgChannelOpenFailure: 70 | msg = new(OpenFailureMessage) 71 | case msgChannelWindowAdjust: 72 | msg = new(WindowAdjustMessage) 73 | case msgChannelEOF: 74 | msg = new(EOFMessage) 75 | case msgChannelClose: 76 | msg = new(CloseMessage) 77 | default: 78 | return nil, fmt.Errorf("qmux: unexpected message type %d", packet[0]) 79 | } 80 | if err := Unmarshal(packet, msg); err != nil { 81 | return nil, err 82 | } 83 | // fmt.Println(">>", msg) 84 | return msg, nil 85 | } 86 | 87 | func Unmarshal(b []byte, v interface{}) error { 88 | switch msg := v.(type) { 89 | case *OpenMessage: 90 | msg.SenderID = binary.BigEndian.Uint32(b[1:5]) 91 | msg.WindowSize = binary.BigEndian.Uint32(b[5:9]) 92 | msg.MaxPacketSize = binary.BigEndian.Uint32(b[9:13]) 93 | return nil 94 | 95 | case *OpenConfirmMessage: 96 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 97 | msg.SenderID = binary.BigEndian.Uint32(b[5:9]) 98 | msg.WindowSize = binary.BigEndian.Uint32(b[9:13]) 99 | msg.MaxPacketSize = binary.BigEndian.Uint32(b[13:17]) 100 | return nil 101 | 102 | case *OpenFailureMessage: 103 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 104 | return nil 105 | 106 | case *WindowAdjustMessage: 107 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 108 | msg.AdditionalBytes = binary.BigEndian.Uint32(b[5:9]) 109 | return nil 110 | 111 | case *DataMessage: 112 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 113 | msg.Length = binary.BigEndian.Uint32(b[5:9]) 114 | msg.Data = b[9:] 115 | return nil 116 | 117 | case *EOFMessage: 118 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 119 | return nil 120 | 121 | case *CloseMessage: 122 | msg.ChannelID = binary.BigEndian.Uint32(b[1:5]) 123 | return nil 124 | 125 | default: 126 | return fmt.Errorf("qmux: unmarshal not supported for value %#v", v) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /javascript/mux/codec/decoder.ts: -------------------------------------------------------------------------------- 1 | import * as msg from "./message.ts"; 2 | import * as api from "../api.ts"; 3 | import * as util from "../util.ts"; 4 | 5 | export class Decoder { 6 | conn: api.IConn; 7 | debug: boolean; 8 | 9 | constructor(conn: api.IConn, debug: boolean = false) { 10 | this.conn = conn; 11 | this.debug = debug; 12 | } 13 | 14 | async decode(): Promise { 15 | let packet = await readPacket(this.conn); 16 | if (packet === undefined) { 17 | return Promise.resolve(undefined); 18 | } 19 | let msg = Unmarshal(packet); 20 | if (this.debug) { 21 | console.log(">>", msg); 22 | } 23 | return msg; 24 | } 25 | } 26 | 27 | async function readPacket(conn: api.IConn): Promise { 28 | let head = await conn.read(1); 29 | if (head === undefined) { 30 | return Promise.resolve(undefined); 31 | } 32 | let msgID = head[0]; 33 | 34 | let size = msg.payloadSizes.get(msgID); 35 | if (size === undefined || msgID < msg.OpenID || msgID > msg.CloseID) { 36 | return Promise.reject(`bad packet: ${msgID}`); 37 | } 38 | 39 | let rest = await conn.read(size); 40 | if (rest === undefined) { 41 | return Promise.reject("unexpected EOF"); 42 | } 43 | 44 | if (msgID === msg.DataID) { 45 | let view = new DataView(rest.buffer); 46 | let length = view.getUint32(4); 47 | let data = await conn.read(length); 48 | if (data === undefined) { 49 | return Promise.reject("unexpected EOF"); 50 | } 51 | return util.concat([head, rest, data], length+rest.length+1); 52 | } 53 | 54 | return util.concat([head, rest], rest.length+1); 55 | } 56 | 57 | export function Unmarshal(packet: Uint8Array): msg.Message { 58 | let data = new DataView(packet.buffer); 59 | switch (packet[0]) { 60 | case msg.CloseID: 61 | return { 62 | ID: packet[0], 63 | channelID: data.getUint32(1) 64 | } as msg.CloseMessage; 65 | case msg.DataID: 66 | let dataLength = data.getUint32(5); 67 | let rest = new Uint8Array(packet.buffer.slice(9)); 68 | return { 69 | ID: packet[0], 70 | channelID: data.getUint32(1), 71 | length: dataLength, 72 | data: rest, 73 | } as msg.DataMessage; 74 | case msg.EofID: 75 | return { 76 | ID: packet[0], 77 | channelID: data.getUint32(1) 78 | } as msg.EOFMessage; 79 | case msg.OpenID: 80 | return { 81 | ID: packet[0], 82 | senderID: data.getUint32(1), 83 | windowSize: data.getUint32(5), 84 | maxPacketSize: data.getUint32(9), 85 | } as msg.OpenMessage; 86 | case msg.OpenConfirmID: 87 | return { 88 | ID: packet[0], 89 | channelID: data.getUint32(1), 90 | senderID: data.getUint32(5), 91 | windowSize: data.getUint32(9), 92 | maxPacketSize: data.getUint32(13), 93 | } as msg.OpenConfirmMessage; 94 | case msg.OpenFailureID: 95 | return { 96 | ID: packet[0], 97 | channelID: data.getUint32(1), 98 | } as msg.OpenFailureMessage; 99 | case msg.WindowAdjustID: 100 | return { 101 | ID: packet[0], 102 | channelID: data.getUint32(1), 103 | additionalBytes: data.getUint32(5), 104 | } as msg.WindowAdjustMessage; 105 | default: 106 | throw `unmarshal of unknown type: ${packet[0]}`; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /javascript/rpc/responder.ts: -------------------------------------------------------------------------------- 1 | import * as internal from "./internal.ts"; 2 | 3 | export interface Handler { 4 | respondRPC(r: Responder, c: internal.Call): void; 5 | } 6 | 7 | export interface Responder { 8 | header: ResponseHeader; 9 | return(v: any): void; 10 | hijack(v: any): Promise; 11 | } 12 | 13 | export class ResponseHeader { 14 | Error: string|undefined; 15 | Hijacked: boolean; 16 | 17 | constructor() { 18 | this.Error = undefined; 19 | this.Hijacked = false; 20 | } 21 | } 22 | 23 | class responder implements Responder { 24 | header: ResponseHeader; 25 | ch: internal.IChannel; 26 | codec: internal.FrameCodec; 27 | 28 | constructor(ch: internal.IChannel, codec: internal.FrameCodec, header: ResponseHeader) { 29 | this.ch = ch; 30 | this.codec = codec; 31 | this.header = header; 32 | } 33 | 34 | async return(v: any): Promise { 35 | if (v instanceof Error) { 36 | this.header.Error = v.message; 37 | v = null; 38 | } 39 | await this.codec.encode(this.header); 40 | await this.codec.encode(v); 41 | return this.ch.close(); 42 | } 43 | 44 | async hijack(v: any): Promise { 45 | if (v instanceof Error) { 46 | this.header.Error = v.message; 47 | v = null; 48 | } 49 | this.header.Hijacked = true; 50 | await this.codec.encode(this.header); 51 | await this.codec.encode(v); 52 | return this.ch; 53 | } 54 | } 55 | 56 | export async function Respond(session: internal.ISession, ch: internal.IChannel, mux: RespondMux): Promise { 57 | let codec = new internal.FrameCodec(ch, mux.codec); 58 | let frame = await codec.decode(); 59 | 60 | let call = new internal.Call(frame.Destination); 61 | 62 | call.decode = () => codec.decode(); 63 | call.caller = new internal.caller(session, mux.codec); 64 | 65 | let header = new ResponseHeader(); 66 | let resp = new responder(ch, codec, header); 67 | 68 | let handler = mux.handler(call.Destination); 69 | if (!handler) { 70 | resp.return(new Error(`handler does not exist for this destination: ${call.Destination}`)); 71 | return; 72 | } 73 | 74 | await handler.respondRPC(resp, call); 75 | 76 | return Promise.resolve(); 77 | } 78 | 79 | export class RespondMux { 80 | handlers: { [key: string]: Handler; }; 81 | codec: internal.Codec; 82 | 83 | constructor(codec: internal.Codec) { 84 | this.codec = codec; 85 | this.handlers = {}; 86 | } 87 | 88 | // TODO: make more like Go's RespondMux#Bind, can take Handler, HandlerFunc, or tries to export 89 | bind(path: string, handler: Handler): void { 90 | this.handlers[path] = handler; 91 | } 92 | 93 | bindFunc(path: string, handler: (r: Responder, c: internal.Call) => void): void { 94 | this.bind(path, { 95 | respondRPC: async (rr: Responder, cc: internal.Call) => { 96 | await handler(rr, cc); 97 | } 98 | }) 99 | } 100 | 101 | handler(path: string): Handler|undefined { 102 | for (var p in this.handlers) { 103 | if (this.handlers.hasOwnProperty(p)) { 104 | if (path.startsWith(p)) { 105 | return this.handlers[p]; 106 | } 107 | } 108 | } 109 | return undefined; 110 | } 111 | } 112 | 113 | 114 | -------------------------------------------------------------------------------- /javascript/rpc/codec.ts: -------------------------------------------------------------------------------- 1 | import * as internal from "./internal.ts"; 2 | 3 | function sleep(ms: number): Promise { 4 | return new Promise(res => setTimeout(res, ms)); 5 | } 6 | 7 | function loopYield(name: string): Promise { 8 | //console.log(name); 9 | //return sleep(10); 10 | return Promise.resolve(); 11 | } 12 | 13 | export interface Codec { 14 | encode(v: any): Uint8Array 15 | decode(buf: Uint8Array): any 16 | } 17 | 18 | export class JSONCodec { 19 | enc: TextEncoder; 20 | dec: TextDecoder; 21 | debug: boolean; 22 | 23 | constructor(debug: boolean = false) { 24 | this.enc = new TextEncoder(); 25 | this.dec = new TextDecoder("utf-8"); 26 | this.debug = debug; 27 | } 28 | 29 | encode(v: any): Uint8Array { 30 | if (this.debug) { 31 | console.log("<<", v); 32 | } 33 | return this.enc.encode(JSON.stringify(v)); 34 | } 35 | 36 | decode(buf: Uint8Array): any { 37 | let v = JSON.parse(this.dec.decode(buf)); 38 | if (this.debug) { 39 | console.log(">>", v); 40 | } 41 | return v; 42 | } 43 | } 44 | 45 | // only one codec per channel because of read loop! 46 | export class FrameCodec { 47 | channel: internal.IChannel; 48 | codec: Codec; 49 | buf: Array; 50 | waiters: Array; 51 | readLimit: number; 52 | readCount: number; 53 | 54 | constructor(channel: internal.IChannel, codec: Codec, readLimit: number = -1) { 55 | this.channel = channel; 56 | this.codec = codec; 57 | this.buf = []; 58 | this.waiters = []; 59 | this.readLimit = readLimit; 60 | this.readCount = 0; 61 | this.readLoop(); 62 | } 63 | 64 | async readLoop() { 65 | while (true) { 66 | if (this.readLimit > 0 && this.readCount >= this.readLimit) { 67 | return; 68 | } 69 | try { 70 | await loopYield("readloop"); 71 | var lenPrefix = await this.channel.read(4); 72 | if (lenPrefix === undefined) { 73 | //console.log("DEBUG: readloop exited on length"); 74 | return; 75 | } 76 | var data = new DataView(lenPrefix.buffer); 77 | var size = data.getUint32(0); 78 | var buf = await this.channel.read(size); 79 | if (buf === undefined) { 80 | //console.log("DEBUG: readloop exited on data"); 81 | return; 82 | } 83 | this.readCount++; 84 | var v = this.codec.decode(buf); 85 | if (this.waiters.length > 0) { 86 | this.waiters.shift()(v); 87 | continue; 88 | } 89 | this.buf.push(v); 90 | } catch (e) { 91 | throw new Error("codec readLoop: " + e); 92 | } 93 | } 94 | } 95 | 96 | async encode(v: any): Promise { 97 | let buf = this.codec.encode(v); 98 | let lenPrefix = new DataView(new ArrayBuffer(4)); 99 | lenPrefix.setUint32(0, buf.byteLength); 100 | await this.channel.write(new Uint8Array(lenPrefix.buffer)); 101 | await this.channel.write(buf); 102 | return Promise.resolve(); 103 | } 104 | 105 | decode(): Promise { 106 | return new Promise((resolve, reject) => { 107 | if (this.buf.length > 0) { 108 | resolve(this.buf.shift()); 109 | return; 110 | } 111 | this.waiters.push(resolve); 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /javascript/mux/session.ts: -------------------------------------------------------------------------------- 1 | import * as api from "./api.ts"; 2 | import * as codec from "./codec/index.ts"; 3 | import * as util from "./util.ts"; 4 | import * as internal from "./internal.ts"; 5 | 6 | export const minPacketLength = 9; 7 | export const maxPacketLength = Number.MAX_VALUE; 8 | 9 | 10 | export class Session implements api.ISession { 11 | conn: api.IConn; 12 | channels: Array; 13 | incoming: util.queue; 14 | enc: codec.Encoder; 15 | dec: codec.Decoder; 16 | 17 | constructor(conn: api.IConn, debug: boolean = false) { 18 | this.conn = conn; 19 | this.enc = new codec.Encoder(conn, debug); 20 | this.dec = new codec.Decoder(conn, debug); 21 | this.channels = []; 22 | this.incoming = new util.queue(); 23 | this.loop(); 24 | } 25 | 26 | async open(): Promise { 27 | let ch = this.newChannel(); 28 | ch.maxIncomingPayload = internal.channelMaxPacket; 29 | await this.enc.encode({ 30 | ID: codec.OpenID, 31 | windowSize: ch.myWindow, 32 | maxPacketSize: ch.maxIncomingPayload, 33 | senderID: ch.localId 34 | }); 35 | if (await ch.ready.shift()) { 36 | return ch; 37 | } 38 | throw "failed to open"; 39 | } 40 | 41 | accept(): Promise { 42 | return this.incoming.shift(); 43 | } 44 | 45 | async close(): Promise { 46 | for (const ids of Object.keys(this.channels)) { 47 | let id = parseInt(ids); 48 | if (this.channels[id] !== undefined) { 49 | this.channels[id].shutdown(); 50 | } 51 | } 52 | return this.conn.close(); 53 | } 54 | 55 | async loop() { 56 | try { 57 | while (true) { 58 | let msg = await this.dec.decode(); 59 | if (msg === undefined) { 60 | this.close(); 61 | return; 62 | } 63 | if (msg.ID === codec.OpenID) { 64 | await this.handleOpen(msg as codec.OpenMessage); 65 | continue; 66 | } 67 | 68 | let cmsg: codec.ChannelMessage = msg as codec.ChannelMessage; 69 | 70 | let ch = this.getCh(cmsg.channelID); 71 | if (ch === undefined) { 72 | throw `invalid channel (${cmsg.channelID}) on op ${cmsg.ID}`; 73 | } 74 | await ch.handle(cmsg); 75 | } 76 | } catch (e) { 77 | throw new Error(`session readloop: ${e}`); 78 | } 79 | // catch { 80 | // this.channels.forEach(async (ch) => { 81 | // await ch.close(); 82 | // }) 83 | // this.channels = []; 84 | // await this.conn.close(); 85 | // } 86 | } 87 | 88 | async handleOpen(msg: codec.OpenMessage) { 89 | if (msg.maxPacketSize < minPacketLength || msg.maxPacketSize > maxPacketLength) { 90 | await this.enc.encode({ 91 | ID: codec.OpenFailureID, 92 | channelID: msg.senderID 93 | }); 94 | return; 95 | } 96 | let c = this.newChannel(); 97 | c.remoteId = msg.senderID; 98 | c.maxRemotePayload = msg.maxPacketSize; 99 | c.remoteWin = msg.windowSize; 100 | c.maxIncomingPayload = internal.channelMaxPacket; 101 | this.incoming.push(c); 102 | await this.enc.encode({ 103 | ID: codec.OpenConfirmID, 104 | channelID: c.remoteId, 105 | senderID: c.localId, 106 | windowSize: c.myWindow, 107 | maxPacketSize: c.maxIncomingPayload 108 | }); 109 | } 110 | 111 | newChannel(): internal.Channel { 112 | let ch = new internal.Channel(this); 113 | ch.remoteWin = 0; 114 | ch.myWindow = internal.channelWindowSize; 115 | ch.readBuf = new Uint8Array(0); 116 | ch.localId = this.addCh(ch); 117 | return ch; 118 | } 119 | 120 | getCh(id: number): internal.Channel { 121 | let ch = this.channels[id]; 122 | if (ch.localId !== id) { 123 | console.log("bad ids:", id, ch.localId, ch.remoteId); 124 | } 125 | return ch; 126 | } 127 | 128 | addCh(ch: internal.Channel): number { 129 | this.channels.forEach((v,i) => { 130 | if (v === undefined) { 131 | this.channels[i] = ch; 132 | return i; 133 | } 134 | }); 135 | this.channels.push(ch); 136 | return this.channels.length-1; 137 | } 138 | 139 | rmCh(id: number): void { 140 | delete this.channels[id]; 141 | } 142 | 143 | } 144 | 145 | -------------------------------------------------------------------------------- /golang/rpc/export.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func MustExport(v interface{}) map[string]Handler { 9 | h, err := Export(v) 10 | if err != nil { 11 | panic(err) 12 | } 13 | return h 14 | } 15 | 16 | func Export(v interface{}) (map[string]Handler, error) { 17 | rt := reflect.TypeOf(v) 18 | if rt.Kind() == reflect.Func { 19 | h, e := exportFunc(v, nil) 20 | return map[string]Handler{"": h}, e 21 | } 22 | return exportStruct(rt, v) 23 | } 24 | 25 | func exportStruct(t reflect.Type, rcvr interface{}) (map[string]Handler, error) { 26 | handlers := make(map[string]Handler) 27 | for i := 0; i < t.NumMethod(); i++ { 28 | method := t.Method(i) 29 | handler, err := exportFunc(method.Func.Interface(), rcvr) 30 | if err != nil { 31 | return nil, fmt.Errorf("unable to export method %s: %s", method.Name, err.Error()) 32 | } 33 | handlers[method.Name] = handler 34 | } 35 | return handlers, nil 36 | } 37 | 38 | func exportFunc(fn interface{}, rcvr interface{}) (Handler, error) { 39 | rfn := reflect.ValueOf(fn) 40 | rt := reflect.TypeOf(fn) 41 | 42 | if rt.Kind() != reflect.Func { 43 | return nil, fmt.Errorf("takes only a function") 44 | } 45 | 46 | var baseParams []reflect.Value 47 | var hasReceiver bool 48 | if rcvr != nil { 49 | if rt.NumIn() == 0 { 50 | return nil, fmt.Errorf("expecting 1 receiver argument, got 0") 51 | } 52 | hasReceiver = true 53 | baseParams = append(baseParams, reflect.ValueOf(rcvr)) 54 | } 55 | 56 | if rt.NumOut() > 2 { 57 | return nil, fmt.Errorf("expecting 1 return value and optional error, got >2") 58 | } 59 | 60 | var pt reflect.Type 61 | if rt.NumIn() > len(baseParams)+1 { 62 | pt = reflect.TypeOf([]interface{}{}) 63 | } 64 | if rt.NumIn() == len(baseParams)+1 { 65 | pt = rt.In(len(baseParams)) 66 | } 67 | 68 | errorInterface := reflect.TypeOf((*error)(nil)).Elem() 69 | 70 | return HandlerFunc(func(r Responder, c *Call) { 71 | var params []reflect.Value 72 | for _, p := range baseParams { 73 | params = append(params, p) 74 | } 75 | 76 | if pt != nil { 77 | var pv reflect.Value 78 | if pt.Kind() == reflect.Ptr { 79 | pv = reflect.New(pt.Elem()) 80 | } else { 81 | pv = reflect.New(pt) 82 | } 83 | 84 | err := c.Decode(pv.Interface()) 85 | if err != nil { 86 | var debug interface{} 87 | c.Decode(&debug) 88 | fmt.Println(debug) 89 | // arguments weren't what was expected, 90 | // or any other error 91 | panic(err) 92 | } 93 | 94 | switch pt.Kind() { 95 | case reflect.Slice: 96 | startIdx := len(params) 97 | args := reflect.Indirect(pv).Interface().([]interface{}) 98 | for idx, arg := range args { 99 | if startIdx+idx >= rt.NumIn() { 100 | break 101 | } 102 | if rt.In(startIdx+idx).Kind() == reflect.Int { 103 | params = append(params, reflect.ValueOf(int(arg.(float64)))) 104 | } else { 105 | params = append(params, reflect.ValueOf(arg)) 106 | } 107 | } 108 | expected := rt.NumIn() 109 | if hasReceiver { 110 | expected-- 111 | } 112 | if len(args) < expected { 113 | params = append(params, reflect.ValueOf(c)) 114 | } 115 | case reflect.Ptr: 116 | params = append(params, pv) 117 | default: 118 | params = append(params, pv.Elem()) 119 | } 120 | } 121 | 122 | // TODO capture panic: Call with too few input arguments 123 | // TODO type assertions for simple named types 124 | retVals := rfn.Call(params) 125 | 126 | if len(retVals) == 0 { 127 | r.Return(nil) 128 | return 129 | } 130 | 131 | // assuming up to 2 return values, one being an error 132 | var retVal reflect.Value 133 | for _, v := range retVals { 134 | if v.Type().Implements(errorInterface) { 135 | if !v.IsNil() { 136 | r.Return(v.Interface().(error)) 137 | return 138 | } 139 | } else { 140 | retVal = v 141 | } 142 | } 143 | 144 | if !retVal.IsValid() { 145 | r.Return(nil) 146 | } else { 147 | r.Return(retVal.Interface()) 148 | } 149 | 150 | }), nil 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qtalk 2 | 3 | qtalk is a minimal set of packages for several languages that 4 | achieve an opinionated but expressive way for programmers to get 5 | their programs to interact (IPC, as they say). 6 | 7 | right now there are two main parts: mux, rpc. 8 | 9 | this repo is actively trying to settle as we determine how to break 10 | up parts into packages for multiple languages while being a single 11 | project. 12 | 13 | ## mux 14 | 15 | mux is a connection multiplexing layer. the goal of this layer is to provide 16 | a muxed socket API for opening and accepting virtual TCP connections (channels). 17 | the API looks something like this (excuse the Go shorthand): 18 | 19 | ``` 20 | type Session interface { 21 | Close() error 22 | Open() (Channel, error) 23 | Accept() (Channel, error) 24 | LocalAddr() net.Addr 25 | RemoteAddr() net.Addr 26 | } 27 | 28 | type Channel interface { 29 | ID() uint64 30 | io.Reader 31 | io.Writer 32 | io.Closer 33 | } 34 | ``` 35 | 36 | this API can be implemented many ways and some roll their own. it being about 37 | the API though means you can use whatever works, and the spin-off project libmux is a 38 | C library that exposes a pluggable version of this API with various implementations including 39 | ones based on subsets of HTTP2 and SSH, the most widely deployed protocols that do connection 40 | multiplexing. 41 | 42 | personally, i'm most excited about QUIC as an ideal implementation of this layer because 43 | it's TCP+TLS+muxing on top of UDP, built to outperform the typical TCP/TLS stack, in part 44 | by building in connection multiplexing at this layer to avoid the overhead of many TCP(+TLS) 45 | connections. being UDP also potentially has advantages in making easier peer-to-peer connections. 46 | 47 | besides performance benefits, a muxing API can be used as a primitive to simplify higher level 48 | protocols. the main example being our rpc layer, which implements a request and reply as 49 | the two directions of a virtual channel (a la http/2). no need to keep track of request and reply 50 | ID in the rpc protocol if they just live in their own channel that binds them together using existing 51 | semantics. an extra bonus to this is explained later in the rpc layer. 52 | 53 | however, there is no good standalone protocol spec for connection multiplexing that is simple 54 | enough to be ported, adapted, and used within many contexts (languages, transports). however, SSH was 55 | architected with very clear layers of abstraction, including a whole layer dedicated to connection 56 | multiplexing. by extracting that part of the spec into its own standalone spec (with nothing 57 | changed but names!) we get a protocol we call qmux, which by nature of literally being a subset 58 | of SSH makes it the most mature and widely deployed muxing protocol. it also happens to be as 59 | simple as it can be afaik. 60 | 61 | i explain all this because it's important to understand that as a layer in qtalk, it just needs to 62 | implement the muxing API above. the implementation provided in this repo is the qmux protocol, but 63 | everything else was designed to work with the API, not specifically qmux. it is possible to replace qmux 64 | in qtalk with QUIC, for example, or your fancy custom muxing transport if it can implement this API. 65 | 66 | the q prefix of this project started in reference to QUIC. but until the browser context can 67 | natively do QUIC (maybe never), we have to have some kind of layer over WebSocket. this can then also be 68 | used over any other bytestream transport (TCP, stdio) or transport capable of re-modeling bytestreams 69 | (UDP, messaging). hence qmux... simplest standalone mux protocol based on most one of the most common 70 | protocols on the net. 71 | 72 | ## rpc 73 | 74 | TODO 75 | 76 | ## future layers 77 | 78 | these two pieces alone give you the ability to call functions and stream multiple channels 79 | of bytes in either direction. given that you can tunnel most protocols over a stream of bytes and can 80 | implement/model nearly any protocol as rpc (did you know NFS is just an rpc protocol?), and can run this 81 | over nearly any transport... there's a lot of bang for buck here. 82 | 83 | however, there are some more related concerns and higher level ideas that we need. but i won't 84 | get into them until we have clearer ideas of what they look like. we know that: 85 | 86 | * realtime data replication is extremely powerful and often "what you really want" 87 | * schemas can feel like overkill when forced, but as an option open up lots of magical possibilities 88 | * message brokers are an anti-pattern, but some kind of bus/hub primitive opens up more topologies 89 | * distributed objects have their issues, but sometimes you need to work with a remote object 90 | -------------------------------------------------------------------------------- /golang/rpc/responder.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "strings" 9 | "sync" 10 | 11 | "github.com/manifold/qtalk/golang/mux" 12 | "golang.org/x/net/websocket" 13 | ) 14 | 15 | type Handler interface { 16 | RespondRPC(Responder, *Call) 17 | } 18 | 19 | type HandlerFunc func(Responder, *Call) 20 | 21 | func (f HandlerFunc) RespondRPC(resp Responder, call *Call) { 22 | f(resp, call) 23 | } 24 | 25 | type Responder interface { 26 | Header() *ResponseHeader 27 | Return(interface{}) error 28 | Hijack(interface{}) (mux.Channel, error) 29 | } 30 | 31 | type ResponseHeader struct { 32 | Error *string 33 | Hijacked bool // after parsing response, keep stream open for whatever protocol 34 | } 35 | 36 | func Respond(sess mux.Session, ch mux.Channel, mux *RespondMux) { 37 | defer ch.Close() 38 | 39 | codec := &frameCodec{mux.codec} 40 | dec := codec.Decoder(ch) 41 | 42 | var call Call 43 | err := dec.Decode(&call) 44 | if err != nil { 45 | log.Println("rpc.Respond:", err) 46 | return 47 | } 48 | 49 | err = call.Parse() 50 | if err != nil { 51 | log.Println("rpc.Respond:", err) 52 | return 53 | } 54 | 55 | call.Context = ch.Context() 56 | call.Decoder = dec 57 | call.Caller = &caller{ 58 | session: sess, 59 | codec: mux.codec, 60 | } 61 | 62 | header := &ResponseHeader{} 63 | resp := &responder{ 64 | ch: ch, 65 | c: codec, 66 | header: header, 67 | } 68 | 69 | handler := mux.Handler(call.Destination) 70 | if handler == nil { 71 | resp.Return(fmt.Errorf("handler does not exist for this destination: %s", call.Destination)) 72 | return 73 | } 74 | 75 | handler.RespondRPC(resp, &call) 76 | } 77 | 78 | type responder struct { 79 | ch mux.Channel 80 | header *ResponseHeader 81 | c Codec 82 | } 83 | 84 | func (r *responder) Header() *ResponseHeader { 85 | return r.header 86 | } 87 | 88 | func (r *responder) Return(v interface{}) error { 89 | enc := r.c.Encoder(r.ch) 90 | var e error 91 | var ok bool 92 | if e, ok = v.(error); ok { 93 | v = nil 94 | } 95 | if e != nil { 96 | var errStr = e.Error() 97 | r.header.Error = &errStr 98 | } 99 | err := enc.Encode(r.header) 100 | if err != nil { 101 | return err 102 | } 103 | err = enc.Encode(v) 104 | if err != nil { 105 | return err 106 | } 107 | return r.ch.Close() 108 | } 109 | 110 | func (r *responder) Hijack(v interface{}) (mux.Channel, error) { 111 | enc := r.c.Encoder(r.ch) 112 | var e error 113 | var ok bool 114 | if e, ok = v.(error); ok { 115 | v = nil 116 | } 117 | if e != nil { 118 | var errStr = e.Error() 119 | r.header.Error = &errStr 120 | } 121 | r.header.Hijacked = true 122 | err := enc.Encode(r.header) 123 | if err != nil { 124 | return nil, err 125 | } 126 | err = enc.Encode(v) 127 | if err != nil { 128 | return nil, err 129 | } 130 | return r.ch, nil 131 | } 132 | 133 | type RespondMux struct { 134 | handlers map[string]Handler 135 | codec Codec 136 | mu sync.Mutex 137 | } 138 | 139 | var DefaultRespondMux = &RespondMux{ 140 | handlers: make(map[string]Handler), 141 | codec: JSONCodec{}, 142 | } 143 | 144 | func NewRespondMux(codec Codec) *RespondMux { 145 | return &RespondMux{ 146 | handlers: make(map[string]Handler), 147 | codec: codec, 148 | } 149 | } 150 | 151 | // Bind makes a Handler accessible at a path. Non-Handlers 152 | // are exported with MustExport. 153 | func (m *RespondMux) Bind(path string, v interface{}) { 154 | var handlers map[string]Handler 155 | if h, ok := v.(Handler); ok { 156 | handlers = map[string]Handler{"": h} 157 | } else { 158 | handlers = MustExport(v) 159 | } 160 | m.mu.Lock() 161 | defer m.mu.Unlock() 162 | for p, h := range handlers { 163 | if path != "" && p != "" { 164 | p = strings.Join([]string{path, p}, ".") 165 | } else { 166 | p = strings.Join([]string{path, p}, "") 167 | } 168 | m.handlers[p] = h 169 | } 170 | } 171 | 172 | func Bind(path string, v interface{}) { 173 | DefaultRespondMux.Bind(path, v) 174 | } 175 | 176 | func (m *RespondMux) Handler(path string) Handler { 177 | var handler Handler 178 | m.mu.Lock() 179 | for k, v := range m.handlers { 180 | if (strings.HasSuffix(k, "/") && strings.HasPrefix(path, k)) || path == k { 181 | handler = v 182 | break 183 | } 184 | } 185 | m.mu.Unlock() 186 | return handler 187 | } 188 | 189 | func (m *RespondMux) RespondRPC(Responder, *Call) { 190 | // TODO 191 | } 192 | 193 | func (m *RespondMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { 194 | websocket.Handler(func(ws *websocket.Conn) { 195 | ws.PayloadType = websocket.BinaryFrame 196 | sess := mux.NewSession(r.Context(), ws) 197 | for { 198 | ch, err := sess.Accept() 199 | if err != nil { 200 | if err == io.EOF { 201 | return 202 | } 203 | panic(err) 204 | } 205 | go Respond(sess, ch, m) 206 | } 207 | }).ServeHTTP(w, r) 208 | } 209 | -------------------------------------------------------------------------------- /javascript/mux/channel.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as util from "./util.ts"; 3 | import * as codec from "./codec/index.ts"; 4 | import * as internal from "./internal.ts"; 5 | 6 | export const channelMaxPacket = 1 << 15; 7 | export const channelWindowSize = 64 * channelMaxPacket; 8 | 9 | // channel represents a virtual muxed connection 10 | export class Channel { 11 | localId: number; 12 | remoteId: number; 13 | maxIncomingPayload: number; 14 | maxRemotePayload: number; 15 | session: internal.Session; 16 | ready: util.queue; 17 | sentEOF: boolean; 18 | gotEOF: boolean; 19 | sentClose: boolean; 20 | remoteWin: number; 21 | myWindow: number; 22 | readBuf: Uint8Array|undefined; 23 | readers: Array; 24 | 25 | constructor(sess: internal.Session) { 26 | this.localId = 0; 27 | this.remoteId = 0; 28 | this.maxIncomingPayload = 0; 29 | this.maxRemotePayload = 0; 30 | this.sentEOF = false; 31 | this.gotEOF = false; 32 | this.sentClose = false; 33 | this.remoteWin = 0; 34 | this.myWindow = 0; 35 | this.ready = new util.queue(); 36 | this.session = sess; 37 | this.readers = []; 38 | } 39 | 40 | ident(): number { 41 | return this.localId; 42 | } 43 | 44 | read(len: number): Promise { 45 | return new Promise(resolve => { 46 | let tryRead = () => { 47 | if (this.readBuf === undefined) { 48 | resolve(undefined); 49 | return; 50 | } 51 | if (!len && this.readBuf.length > 0) { 52 | let data = this.readBuf; 53 | this.readBuf = this.readBuf.slice(this.readBuf.length); 54 | resolve(data); 55 | return; 56 | } 57 | if (this.readBuf.length >= len) { 58 | let data = this.readBuf.slice(0, len); 59 | this.readBuf = this.readBuf.slice(len); 60 | resolve(data); 61 | if (this.readBuf.length == 0 && this.gotEOF) { 62 | this.readBuf = undefined; 63 | } 64 | return; 65 | } 66 | this.readers.push(tryRead); 67 | } 68 | tryRead(); 69 | }); 70 | } 71 | 72 | write(buffer: Uint8Array): Promise { 73 | if (this.sentEOF) { 74 | return Promise.reject("EOF"); 75 | } 76 | // TODO: use window 77 | 78 | return this.send({ 79 | ID: codec.DataID, 80 | channelID: this.remoteId, 81 | length: buffer.byteLength, 82 | data: buffer 83 | }); 84 | } 85 | 86 | async closeWrite() { 87 | this.sentEOF = true; 88 | await this.send({ 89 | ID: codec.EofID, 90 | channelID: this.remoteId 91 | }); 92 | } 93 | 94 | async close(): Promise { 95 | if (!this.sentClose) { 96 | await this.send({ 97 | ID: codec.CloseID, 98 | channelID: this.remoteId 99 | }); 100 | this.sentClose = true; 101 | while (await this.ready.shift() !== undefined) {} 102 | return; 103 | } 104 | this.shutdown(); 105 | } 106 | 107 | shutdown(): void { 108 | this.readBuf = undefined; 109 | this.readers.forEach(reader => reader()); 110 | this.ready.close(); 111 | this.session.rmCh(this.localId); 112 | } 113 | 114 | async adjustWindow(n: number) { 115 | // TODO 116 | } 117 | 118 | send(msg: codec.ChannelMessage): Promise { 119 | if (this.sentClose) { 120 | throw "EOF"; 121 | } 122 | 123 | this.sentClose = (msg.ID === codec.CloseID); 124 | 125 | return this.session.enc.encode(msg); 126 | } 127 | 128 | handle(msg: codec.ChannelMessage): void { 129 | if (msg.ID === codec.DataID) { 130 | this.handleData(msg as codec.DataMessage); 131 | return; 132 | } 133 | if (msg.ID === codec.CloseID) { 134 | this.close(); // is this right? 135 | return; 136 | } 137 | if (msg.ID === codec.EofID) { 138 | this.gotEOF = true; 139 | // if (this.readers.length > 0) { 140 | // this.readers.shift()(); 141 | // } 142 | return; 143 | } 144 | if (msg.ID === codec.OpenFailureID) { 145 | this.session.rmCh(msg.channelID); 146 | this.ready.push(false); 147 | return; 148 | } 149 | if (msg.ID === codec.OpenConfirmID) { 150 | if (msg.maxPacketSize < internal.minPacketLength || msg.maxPacketSize > internal.maxPacketLength) { 151 | throw "invalid max packet size"; 152 | } 153 | this.remoteId = msg.senderID; 154 | this.maxRemotePayload = msg.maxPacketSize; 155 | this.remoteWin += msg.windowSize; 156 | this.ready.push(true); 157 | return; 158 | } 159 | if (msg.ID === codec.WindowAdjustID) { 160 | this.remoteWin += msg.additionalBytes; 161 | } 162 | } 163 | 164 | handleData(msg: codec.DataMessage) { 165 | if (msg.length > this.maxIncomingPayload) { 166 | throw "incoming packet exceeds maximum payload size"; 167 | } 168 | 169 | // TODO: check packet length 170 | if (this.myWindow < msg.length) { 171 | throw "remote side wrote too much"; 172 | } 173 | 174 | this.myWindow -= msg.length; 175 | 176 | if (this.readBuf) { 177 | this.readBuf = util.concat([this.readBuf, msg.data], this.readBuf.length+msg.data.length); 178 | } 179 | 180 | if (this.readers.length > 0) { 181 | let reader = this.readers.shift(); 182 | if (reader) reader(); 183 | } 184 | } 185 | 186 | } 187 | 188 | -------------------------------------------------------------------------------- /golang/mux/session.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net" 8 | "sync" 9 | 10 | "github.com/manifold/qtalk/golang/mux/codec" 11 | ) 12 | 13 | const ( 14 | minPacketLength = 9 15 | maxPacketLength = 1 << 31 16 | 17 | // channelMaxPacket contains the maximum number of bytes that will be 18 | // sent in a single packet. As per RFC 4253, section 6.1, 32k is also 19 | // the minimum. 20 | channelMaxPacket = 1 << 15 21 | // We follow OpenSSH here. 22 | channelWindowSize = 64 * channelMaxPacket 23 | ) 24 | 25 | // chanSize sets the amount of buffering qmux connections. This is 26 | // primarily for testing: setting chanSize=0 uncovers deadlocks more 27 | // quickly. 28 | const chanSize = 16 29 | 30 | type channelDirection uint8 31 | 32 | const ( 33 | channelInbound channelDirection = iota 34 | channelOutbound 35 | ) 36 | 37 | type session struct { 38 | ctx context.Context 39 | conn io.ReadWriteCloser 40 | chanList chanList 41 | 42 | enc *codec.Encoder 43 | dec *codec.Decoder 44 | 45 | incomingChannels chan Channel 46 | 47 | errCond *sync.Cond 48 | err error 49 | closeCh chan bool 50 | } 51 | 52 | // NewSession returns a session that runs over the given connection. 53 | func NewSession(ctx context.Context, rwc io.ReadWriteCloser) Session { 54 | if rwc == nil { 55 | return nil 56 | } 57 | s := &session{ 58 | ctx: ctx, 59 | conn: rwc, 60 | enc: codec.NewEncoder(rwc), 61 | dec: codec.NewDecoder(rwc), 62 | incomingChannels: make(chan Channel, chanSize), 63 | errCond: sync.NewCond(new(sync.Mutex)), 64 | closeCh: make(chan bool, 1), 65 | } 66 | go s.loop() 67 | return s 68 | } 69 | 70 | func (s *session) Context() context.Context { 71 | return s.ctx 72 | } 73 | 74 | func (s *session) Close() error { 75 | s.conn.Close() 76 | return nil 77 | } 78 | 79 | func (s *session) LocalAddr() net.Addr { 80 | if conn, ok := s.conn.(net.Conn); ok { 81 | return conn.LocalAddr() 82 | } 83 | return nil 84 | } 85 | 86 | func (s *session) RemoteAddr() net.Addr { 87 | if conn, ok := s.conn.(net.Conn); ok { 88 | return conn.RemoteAddr() 89 | } 90 | return nil 91 | } 92 | 93 | func (s *session) Wait() error { 94 | s.errCond.L.Lock() 95 | defer s.errCond.L.Unlock() 96 | for s.err == nil { 97 | s.errCond.Wait() 98 | } 99 | return s.err 100 | } 101 | 102 | func (s *session) Accept() (Channel, error) { 103 | // TODO: context cancel 104 | select { 105 | case ch := <-s.incomingChannels: 106 | return ch, nil 107 | case <-s.closeCh: 108 | return nil, io.EOF 109 | } 110 | } 111 | 112 | func (s *session) Open() (Channel, error) { 113 | ch := s.newChannel(channelOutbound) 114 | ch.maxIncomingPayload = channelMaxPacket 115 | 116 | if err := s.enc.Encode(codec.OpenMessage{ 117 | WindowSize: ch.myWindow, 118 | MaxPacketSize: ch.maxIncomingPayload, 119 | SenderID: ch.localId, 120 | }); err != nil { 121 | return nil, err 122 | } 123 | 124 | // TODO: timeout? context cancel? 125 | m := <-ch.msg 126 | if m == nil { 127 | return nil, fmt.Errorf("qmux: channel closed early during open") 128 | } 129 | switch msg := m.(type) { 130 | case *codec.OpenConfirmMessage: 131 | return ch, nil 132 | 133 | case *codec.OpenFailureMessage: 134 | return nil, fmt.Errorf("qmux: channel open failed on remote side") 135 | 136 | default: 137 | return nil, fmt.Errorf("qmux: unexpected packet in response to channel open: %v", msg) 138 | } 139 | } 140 | 141 | func (s *session) newChannel(direction channelDirection) *channel { 142 | ch := &channel{ 143 | ctx: s.ctx, 144 | remoteWin: window{Cond: sync.NewCond(new(sync.Mutex))}, 145 | myWindow: channelWindowSize, 146 | pending: newBuffer(), 147 | direction: direction, 148 | msg: make(chan codec.Message, chanSize), 149 | session: s, 150 | packetBuf: make([]byte, 0), 151 | } 152 | ch.localId = s.chanList.add(ch) 153 | return ch 154 | } 155 | 156 | // loop runs the connection machine. It will process packets until an 157 | // error is encountered. To synchronize on loop exit, use session.Wait. 158 | func (s *session) loop() { 159 | var err error 160 | for err == nil { 161 | err = s.onePacket() 162 | } 163 | 164 | for _, ch := range s.chanList.dropAll() { 165 | ch.close() 166 | } 167 | 168 | s.conn.Close() 169 | s.closeCh <- true 170 | 171 | s.errCond.L.Lock() 172 | s.err = err 173 | s.errCond.Broadcast() 174 | s.errCond.L.Unlock() 175 | } 176 | 177 | // onePacket reads and processes one packet. 178 | func (s *session) onePacket() error { 179 | var err error 180 | var msg codec.Message 181 | 182 | msg, err = s.dec.Decode() 183 | if err != nil { 184 | return err 185 | } 186 | 187 | id, isChan := msg.Channel() 188 | if !isChan { 189 | return s.handleOpen(msg.(*codec.OpenMessage)) 190 | } 191 | 192 | ch := s.chanList.getChan(id) 193 | if ch == nil { 194 | return fmt.Errorf("qmux: invalid channel %d", id) 195 | } 196 | 197 | return ch.handle(msg) 198 | } 199 | 200 | // handleChannelOpen schedules a channel to be Accept()ed. 201 | func (s *session) handleOpen(msg *codec.OpenMessage) error { 202 | if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > maxPacketLength { 203 | return s.enc.Encode(codec.OpenFailureMessage{ 204 | ChannelID: msg.SenderID, 205 | }) 206 | } 207 | 208 | c := s.newChannel(channelInbound) 209 | c.remoteId = msg.SenderID 210 | c.maxRemotePayload = msg.MaxPacketSize 211 | c.remoteWin.add(msg.WindowSize) 212 | c.maxIncomingPayload = channelMaxPacket 213 | s.incomingChannels <- c 214 | 215 | return s.enc.Encode(codec.OpenConfirmMessage{ 216 | ChannelID: c.remoteId, 217 | SenderID: c.localId, 218 | WindowSize: c.myWindow, 219 | MaxPacketSize: c.maxIncomingPayload, 220 | }) 221 | } 222 | -------------------------------------------------------------------------------- /golang/mux/util.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | func min(a uint32, b int) uint32 { 9 | if a < uint32(b) { 10 | return a 11 | } 12 | return uint32(b) 13 | } 14 | 15 | // chanList is a thread safe channel list. 16 | type chanList struct { 17 | // protects concurrent access to chans 18 | sync.Mutex 19 | 20 | // chans are indexed by the local id of the channel, which the 21 | // other side should send in the PeersId field. 22 | chans []*channel 23 | } 24 | 25 | // Assigns a channel ID to the given channel. 26 | func (c *chanList) add(ch *channel) uint32 { 27 | c.Lock() 28 | defer c.Unlock() 29 | for i := range c.chans { 30 | if c.chans[i] == nil { 31 | c.chans[i] = ch 32 | return uint32(i) 33 | } 34 | } 35 | c.chans = append(c.chans, ch) 36 | return uint32(len(c.chans) - 1) 37 | } 38 | 39 | // getChan returns the channel for the given ID. 40 | func (c *chanList) getChan(id uint32) *channel { 41 | c.Lock() 42 | defer c.Unlock() 43 | if id < uint32(len(c.chans)) { 44 | return c.chans[id] 45 | } 46 | return nil 47 | } 48 | 49 | func (c *chanList) remove(id uint32) { 50 | c.Lock() 51 | if id < uint32(len(c.chans)) { 52 | c.chans[id] = nil 53 | } 54 | c.Unlock() 55 | } 56 | 57 | // dropAll forgets all channels it knows, returning them in a slice. 58 | func (c *chanList) dropAll() []*channel { 59 | c.Lock() 60 | defer c.Unlock() 61 | var r []*channel 62 | 63 | for _, ch := range c.chans { 64 | if ch == nil { 65 | continue 66 | } 67 | r = append(r, ch) 68 | } 69 | c.chans = nil 70 | return r 71 | } 72 | 73 | // buffer provides a linked list buffer for data exchange 74 | // between producer and consumer. Theoretically the buffer is 75 | // of unlimited capacity as it does no allocation of its own. 76 | type buffer struct { 77 | // protects concurrent access to head, tail and closed 78 | *sync.Cond 79 | 80 | head *element // the buffer that will be read first 81 | tail *element // the buffer that will be read last 82 | 83 | closed bool 84 | } 85 | 86 | // An element represents a single link in a linked list. 87 | type element struct { 88 | buf []byte 89 | next *element 90 | } 91 | 92 | // newBuffer returns an empty buffer that is not closed. 93 | func newBuffer() *buffer { 94 | e := new(element) 95 | b := &buffer{ 96 | Cond: sync.NewCond(new(sync.Mutex)), 97 | head: e, 98 | tail: e, 99 | } 100 | return b 101 | } 102 | 103 | // write makes buf available for Read to receive. 104 | // buf must not be modified after the call to write. 105 | func (b *buffer) write(buf []byte) { 106 | b.Cond.L.Lock() 107 | e := &element{buf: buf} 108 | b.tail.next = e 109 | b.tail = e 110 | b.Cond.Signal() 111 | b.Cond.L.Unlock() 112 | } 113 | 114 | // eof closes the buffer. Reads from the buffer once all 115 | // the data has been consumed will receive io.EOF. 116 | func (b *buffer) eof() { 117 | b.Cond.L.Lock() 118 | b.closed = true 119 | b.Cond.Signal() 120 | b.Cond.L.Unlock() 121 | } 122 | 123 | // Read reads data from the internal buffer in buf. Reads will block 124 | // if no data is available, or until the buffer is closed. 125 | func (b *buffer) Read(buf []byte) (n int, err error) { 126 | b.Cond.L.Lock() 127 | defer b.Cond.L.Unlock() 128 | 129 | for len(buf) > 0 { 130 | // if there is data in b.head, copy it 131 | if len(b.head.buf) > 0 { 132 | r := copy(buf, b.head.buf) 133 | buf, b.head.buf = buf[r:], b.head.buf[r:] 134 | n += r 135 | continue 136 | } 137 | // if there is a next buffer, make it the head 138 | if len(b.head.buf) == 0 && b.head != b.tail { 139 | b.head = b.head.next 140 | continue 141 | } 142 | 143 | // if at least one byte has been copied, return 144 | if n > 0 { 145 | break 146 | } 147 | 148 | // if nothing was read, and there is nothing outstanding 149 | // check to see if the buffer is closed. 150 | if b.closed { 151 | err = io.EOF 152 | break 153 | } 154 | // out of buffers, wait for producer 155 | b.Cond.Wait() 156 | } 157 | return 158 | } 159 | 160 | // window represents the buffer available to clients 161 | // wishing to write to a channel. 162 | type window struct { 163 | *sync.Cond 164 | win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1 165 | writeWaiters int 166 | closed bool 167 | } 168 | 169 | // add adds win to the amount of window available 170 | // for consumers. 171 | func (w *window) add(win uint32) bool { 172 | // a zero sized window adjust is a noop. 173 | if win == 0 { 174 | return true 175 | } 176 | w.L.Lock() 177 | if w.win+win < win { 178 | w.L.Unlock() 179 | return false 180 | } 181 | w.win += win 182 | // It is unusual that multiple goroutines would be attempting to reserve 183 | // window space, but not guaranteed. Use broadcast to notify all waiters 184 | // that additional window is available. 185 | w.Broadcast() 186 | w.L.Unlock() 187 | return true 188 | } 189 | 190 | // close sets the window to closed, so all reservations fail 191 | // immediately. 192 | func (w *window) close() { 193 | w.L.Lock() 194 | w.closed = true 195 | w.Broadcast() 196 | w.L.Unlock() 197 | } 198 | 199 | // reserve reserves win from the available window capacity. 200 | // If no capacity remains, reserve will block. reserve may 201 | // return less than requested. 202 | func (w *window) reserve(win uint32) (uint32, error) { 203 | var err error 204 | w.L.Lock() 205 | w.writeWaiters++ 206 | w.Broadcast() 207 | for w.win == 0 && !w.closed { 208 | w.Wait() 209 | } 210 | w.writeWaiters-- 211 | if w.win < win { 212 | win = w.win 213 | } 214 | w.win -= win 215 | if w.closed { 216 | err = io.EOF 217 | } 218 | w.L.Unlock() 219 | return win, err 220 | } 221 | 222 | // waitWriterBlocked waits until some goroutine is blocked for further 223 | // writes. It is used in tests only. 224 | func (w *window) waitWriterBlocked() { 225 | w.Cond.L.Lock() 226 | for w.writeWaiters == 0 { 227 | w.Cond.Wait() 228 | } 229 | w.Cond.L.Unlock() 230 | } 231 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/demo/_demo_mock.js: -------------------------------------------------------------------------------- 1 | class MockListener { 2 | constructor(pair) { 3 | this.pair = pair; 4 | } 5 | 6 | accept() { 7 | if (this.session !== undefined) { 8 | return new Promise(res => { 9 | this.acceptCloser = res; 10 | }) 11 | } 12 | this.session = new MockSession(this.pair); 13 | this.pair.pair = this.session; 14 | return new Promise(res => res(this.session)); 15 | } 16 | 17 | close() { 18 | if (this.acceptCloser !== undefined) { 19 | this.acceptCloser(); 20 | } 21 | return new Promise(async (res) => { 22 | await this.session.close(); 23 | res(); 24 | }); 25 | } 26 | } 27 | 28 | class MockSession { 29 | constructor(pair) { 30 | this.pair = pair; 31 | this.ch = []; 32 | this.closed = false; 33 | this.pendingAccept = []; 34 | this.pendingOpen = []; 35 | } 36 | 37 | close() { 38 | if (this.closed) return Promise.resolve(); 39 | return new Promise(async (res) => { 40 | this.closed = true; 41 | this.ch.forEach(ch => ch.close()); 42 | await this.pair.close(); 43 | res(); 44 | }); 45 | } 46 | 47 | open() { 48 | return new Promise((resolve, reject) => { 49 | if (this.pendingOpen.length > 0) { 50 | var pending = this.pendingOpen.shift(); 51 | var openCh = new MockChannel(pending.ch); 52 | this.ch.push(openCh); 53 | resolve(openCh); 54 | pending.resolve(pending.ch); 55 | return; 56 | } 57 | this.pair.pendingAccept.push({ 58 | ch: new MockChannel(), 59 | resolve: resolve 60 | }); 61 | }) 62 | } 63 | 64 | accept() { 65 | return new Promise((resolve, reject) => { 66 | if (this.pendingAccept.length > 0) { 67 | var pending = this.pendingAccept.shift(); 68 | var acceptCh = new MockChannel(pending.ch); 69 | this.ch.push(acceptCh); 70 | resolve(acceptCh); 71 | pending.resolve(pending.ch); 72 | return; 73 | } 74 | this.pair.pendingOpen.push({ 75 | ch: new MockChannel(), 76 | resolve: resolve 77 | }); 78 | }) 79 | } 80 | } 81 | 82 | class MockChannel { 83 | constructor(pair) { 84 | if (pair !== undefined) { 85 | this.pair = pair; 86 | pair.pair = this; 87 | } 88 | this.buf = []; 89 | this.waiters = []; 90 | this.closed = false; 91 | this.once = false; 92 | } 93 | 94 | close() { 95 | if (this.closed) return Promise.resolve(); 96 | return new Promise(res => { 97 | this.closed = true; 98 | this.buf = undefined; 99 | this.waiters.forEach(waiter => waiter(undefined)); 100 | res(); 101 | }); 102 | } 103 | 104 | write(buffer) { 105 | return new Promise((resolve, reject) => { 106 | var written = 0; 107 | for (const value of buffer) { 108 | if (this.pair.buf !== undefined) { 109 | this.pair.buf.push(value); 110 | written++; 111 | } else { 112 | break; 113 | } 114 | } 115 | if (this.pair.waiters.length > 0) { 116 | this.pair.waiters.shift()(); 117 | } 118 | resolve(written); 119 | }); 120 | } 121 | 122 | read(len) { 123 | return new Promise((resolve, reject) => { 124 | var tryRead = () => { 125 | if (this.buf === undefined) { 126 | resolve(undefined); 127 | return; 128 | } 129 | if (this.buf.length >= len) { 130 | var data = this.buf.splice(0, len); 131 | var buf = Buffer.from(new Uint8Array(data).buffer); 132 | resolve(buf); 133 | return; 134 | } 135 | this.waiters.push(tryRead); 136 | } 137 | tryRead(); 138 | }); 139 | } 140 | } 141 | 142 | var session = new MockSession(); 143 | var listener = new MockListener(session); 144 | 145 | // (async () => { 146 | 147 | // var sess = await listener.accept(); 148 | // var ch = await sess.accept(); 149 | // console.log("|Server echoing on channel..."); 150 | // while(true) { 151 | // var data = await ch.read(1); 152 | // if (data === undefined) { 153 | // console.log("|Server got EOF"); 154 | // break; 155 | // } 156 | // await ch.write(data); 157 | // } 158 | 159 | // })().catch(async (err) => { 160 | // console.log(err.stack); 161 | // }); 162 | 163 | // (async () => { 164 | 165 | // var ch = await session.open(); 166 | 167 | // await ch.write(Buffer.from("Hello")); 168 | // await ch.write(Buffer.from(" ")); 169 | // await ch.write(Buffer.from("world")); 170 | 171 | // var data = await ch.read(11); 172 | // console.log(data.toString('ascii')); 173 | 174 | // await session.close(); 175 | 176 | // //await new Promise(res => setTimeout(res, 1000)); 177 | // })().catch(async (err) => { 178 | // console.log(err.stack); 179 | // }); 180 | 181 | var qrpc = require("./dist/qrpc.js"); 182 | 183 | (async () => { 184 | var api = new qrpc.API(); 185 | api.handle("demo", qrpc.Export({ 186 | "lower": function(v) { 187 | return v.toLowerCase(); 188 | } 189 | })); 190 | 191 | //console.log("serving..."); 192 | var server = new qrpc.Server(); 193 | await server.serve(listener, api); 194 | //console.log("closing..."); 195 | await listener.close(); 196 | })().catch(async (err) => { 197 | console.log(err.stack); 198 | }); 199 | 200 | 201 | (async () => { 202 | //console.log("connecting..."); 203 | var client = new qrpc.Client(session); 204 | console.log((await client.call("demo/lower", "HELLO WORLD!")).reply); 205 | await session.close(); 206 | 207 | })().catch(async (err) => { 208 | console.log(err.stack); 209 | }); -------------------------------------------------------------------------------- /spec/qmux.md: -------------------------------------------------------------------------------- 1 | # qmux 2 | 3 | qmux is a wire protocol for multiplexing connections or streams into a single connection. 4 | It's extracted directly from the [SSH Connection Protocol](https://tools.ietf.org/html/rfc4254#page-5) 5 | and simplified (no channel types, accept/reject step, or extended data). 6 | 7 | ## Channels 8 | 9 | Either side may open a channel. Multiple channels are multiplexed 10 | into a single connection. 11 | 12 | Channels are identified by numbers at each end. The number referring 13 | to a channel may be different on each side. Requests to open a 14 | channel contain the sender's channel number. Any other channel- 15 | related messages contain the recipient's channel number for the 16 | channel. 17 | 18 | Channels are flow-controlled. No data may be sent to a channel until 19 | a message is received to indicate that window space is available. 20 | 21 | ### Opening a Channel 22 | 23 | When either side wishes to open a new channel, it allocates a local 24 | number for the channel. It then sends the following message to the 25 | other side, and includes the local channel number and initial window 26 | size in the message. 27 | 28 | byte QMUX_MSG_CHANNEL_OPEN 29 | uint32 sender channel 30 | uint32 initial window size 31 | uint32 maximum packet size 32 | 33 | The 'sender channel' is a local identifier for the channel used by the 34 | sender of this message. The 'initial window size' specifies how many 35 | bytes of channel data can be sent to the sender of this message 36 | without adjusting the window. The 'maximum packet size' specifies the 37 | maximum size of an individual data packet that can be sent to the 38 | sender. For example, one might want to use smaller packets for 39 | interactive connections to get better interactive response on slow 40 | links. 41 | 42 | The remote side then decides whether it can open the channel, and 43 | responds with either QMUX_MSG_CHANNEL_OPEN_CONFIRMATION or 44 | QMUX_MSG_CHANNEL_OPEN_FAILURE. 45 | 46 | byte QMUX_MSG_CHANNEL_OPEN_CONFIRMATION 47 | uint32 recipient channel 48 | uint32 sender channel 49 | uint32 initial window size 50 | uint32 maximum packet size 51 | 52 | The 'recipient channel' is the channel number given in the original 53 | open request, and 'sender channel' is the channel number allocated by 54 | the other side. 55 | 56 | byte QMUX_MSG_CHANNEL_OPEN_FAILURE 57 | uint32 recipient channel 58 | 59 | ### Data Transfer 60 | 61 | The window size specifies how many bytes the other party can send 62 | before it must wait for the window to be adjusted. Both parties use 63 | the following message to adjust the window. 64 | 65 | byte QMUX_MSG_CHANNEL_WINDOW_ADJUST 66 | uint32 recipient channel 67 | uint32 bytes to add 68 | 69 | After receiving this message, the recipient MAY send the given number 70 | of bytes more than it was previously allowed to send; the window size 71 | is incremented. Implementations MUST correctly handle window sizes 72 | of up to 2^32 - 1 bytes. The window MUST NOT be increased above 73 | 2^32 - 1 bytes. 74 | 75 | Data transfer is done with messages of the following type. 76 | 77 | byte QMUX_MSG_CHANNEL_DATA 78 | uint32 recipient channel 79 | string data 80 | 81 | The maximum amount of data allowed is determined by the maximum 82 | packet size for the channel, and the current window size, whichever 83 | is smaller. The window size is decremented by the amount of data 84 | sent. Both parties MAY ignore all extra data sent after the allowed 85 | window is empty. 86 | 87 | Implementations are expected to have some limit on the transport 88 | layer packet size. 89 | 90 | ### Closing a Channel 91 | 92 | When a party will no longer send more data to a channel, it SHOULD 93 | send QMUX_MSG_CHANNEL_EOF. 94 | 95 | byte QMUX_MSG_CHANNEL_EOF 96 | uint32 recipient channel 97 | 98 | No explicit response is sent to this message. However, the 99 | application may send EOF to whatever is at the other end of the 100 | channel. Note that the channel remains open after this message, and 101 | more data may still be sent in the other direction. This message 102 | does not consume window space and can be sent even if no window space 103 | is available. 104 | 105 | When either party wishes to terminate the channel, it sends 106 | QMUX_MSG_CHANNEL_CLOSE. Upon receiving this message, a party MUST 107 | send back an QMUX_MSG_CHANNEL_CLOSE unless it has already sent this 108 | message for the channel. The channel is considered closed for a 109 | party when it has both sent and received QMUX_MSG_CHANNEL_CLOSE, and 110 | the party may then reuse the channel number. A party MAY send 111 | QMUX_MSG_CHANNEL_CLOSE without having sent or received 112 | QMUX_MSG_CHANNEL_EOF. 113 | 114 | byte QMUX_MSG_CHANNEL_CLOSE 115 | uint32 recipient channel 116 | 117 | This message does not consume window space and can be sent even if no 118 | window space is available. 119 | 120 | It is RECOMMENDED that all data sent before this message be delivered 121 | to the actual destination, if possible. 122 | 123 | ## Summary of Message Numbers 124 | 125 | The following is a summary of messages and their associated message 126 | number byte value. 127 | 128 | QMUX_MSG_CHANNEL_OPEN 100 129 | QMUX_MSG_CHANNEL_OPEN_CONFIRMATION 101 130 | QMUX_MSG_CHANNEL_OPEN_FAILURE 102 131 | QMUX_MSG_CHANNEL_WINDOW_ADJUST 103 132 | QMUX_MSG_CHANNEL_DATA 104 133 | QMUX_MSG_CHANNEL_EOF 105 134 | QMUX_MSG_CHANNEL_CLOSE 106 135 | 136 | ## Data Type Representations Used 137 | 138 | byte 139 | 140 | A byte represents an arbitrary 8-bit value (octet). Fixed length 141 | data is sometimes represented as an array of bytes, written 142 | byte[n], where n is the number of bytes in the array. 143 | 144 | uint32 145 | 146 | Represents a 32-bit unsigned integer. Stored as four bytes in the 147 | order of decreasing significance (network byte order). For 148 | example: the value 699921578 (0x29b7f4aa) is stored as 29 b7 f4 149 | aa. 150 | 151 | string 152 | 153 | Arbitrary length binary string. Strings are allowed to contain 154 | arbitrary binary data, including null characters and 8-bit 155 | characters. They are stored as a uint32 containing its length 156 | (number of bytes that follow) and zero (= empty string) or more 157 | bytes that are the value of the string. Terminating null 158 | characters are not used. -------------------------------------------------------------------------------- /golang/mux/channel.go: -------------------------------------------------------------------------------- 1 | package mux 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "sync" 9 | 10 | "github.com/manifold/qtalk/golang/mux/codec" 11 | ) 12 | 13 | // channel is an implementation of the Channel interface that works 14 | // with the session class. 15 | type channel struct { 16 | ctx context.Context 17 | 18 | // R/O after creation 19 | localId, remoteId uint32 20 | 21 | // maxIncomingPayload and maxRemotePayload are the maximum 22 | // payload sizes of normal and extended data packets for 23 | // receiving and sending, respectively. The wire packet will 24 | // be 9 or 13 bytes larger (excluding encryption overhead). 25 | maxIncomingPayload uint32 26 | maxRemotePayload uint32 27 | 28 | session *session 29 | 30 | // direction contains either channelOutbound, for channels created 31 | // locally, or channelInbound, for channels created by the peer. 32 | direction channelDirection 33 | 34 | // Pending internal channel messages. 35 | msg chan codec.Message 36 | 37 | sentEOF bool 38 | 39 | // thread-safe data 40 | remoteWin window 41 | pending *buffer 42 | 43 | // windowMu protects myWindow, the flow-control window. 44 | windowMu sync.Mutex 45 | myWindow uint32 46 | 47 | // writeMu serializes calls to session.conn.Write() and 48 | // protects sentClose and packetPool. This mutex must be 49 | // different from windowMu, as writePacket can block if there 50 | // is a key exchange pending. 51 | writeMu sync.Mutex 52 | sentClose bool 53 | 54 | // packet buffer for writing 55 | packetBuf []byte 56 | } 57 | 58 | func (ch *channel) ID() uint32 { 59 | return ch.localId 60 | } 61 | 62 | func (ch *channel) Context() context.Context { 63 | return ch.ctx 64 | } 65 | 66 | func (ch *channel) CloseWrite() error { 67 | ch.sentEOF = true 68 | return ch.send(codec.EOFMessage{ 69 | ChannelID: ch.remoteId}) 70 | } 71 | 72 | func (ch *channel) Close() error { 73 | return ch.send(codec.CloseMessage{ 74 | ChannelID: ch.remoteId}) 75 | } 76 | 77 | // Write writes len(data) bytes to the channel. 78 | func (ch *channel) Write(data []byte) (n int, err error) { 79 | if ch.sentEOF { 80 | return 0, io.EOF 81 | } 82 | 83 | for len(data) > 0 { 84 | space := min(ch.maxRemotePayload, len(data)) 85 | if space, err = ch.remoteWin.reserve(space); err != nil { 86 | return n, err 87 | } 88 | 89 | toSend := data[:space] 90 | 91 | if err = ch.session.enc.Encode(codec.DataMessage{ 92 | ChannelID: ch.remoteId, 93 | Length: uint32(len(toSend)), 94 | Data: toSend, 95 | }); err != nil { 96 | return n, err 97 | } 98 | 99 | n += len(toSend) 100 | data = data[len(toSend):] 101 | } 102 | 103 | return n, err 104 | } 105 | 106 | // Read reads up to len(data) bytes from the channel. 107 | func (c *channel) Read(data []byte) (n int, err error) { 108 | n, err = c.pending.Read(data) 109 | 110 | if n > 0 { 111 | err = c.adjustWindow(uint32(n)) 112 | // sendWindowAdjust can return io.EOF if the remote 113 | // peer has closed the connection, however we want to 114 | // defer forwarding io.EOF to the caller of Read until 115 | // the buffer has been drained. 116 | if n > 0 && err == io.EOF { 117 | err = nil 118 | } 119 | } 120 | return n, err 121 | } 122 | 123 | // writePacket sends a packet. If the packet is a channel close, it updates 124 | // sentClose. This method takes the lock c.writeMu. 125 | func (ch *channel) send(msg interface{}) error { 126 | ch.writeMu.Lock() 127 | defer ch.writeMu.Unlock() 128 | 129 | if ch.sentClose { 130 | return io.EOF 131 | } 132 | 133 | if _, ok := msg.(codec.CloseMessage); ok { 134 | ch.sentClose = true 135 | } 136 | 137 | return ch.session.enc.Encode(msg) 138 | } 139 | 140 | func (c *channel) adjustWindow(n uint32) error { 141 | c.windowMu.Lock() 142 | // Since myWindow is managed on our side, and can never exceed 143 | // the initial window setting, we don't worry about overflow. 144 | c.myWindow += uint32(n) 145 | c.windowMu.Unlock() 146 | return c.send(codec.WindowAdjustMessage{ 147 | ChannelID: c.remoteId, 148 | AdditionalBytes: uint32(n), 149 | }) 150 | } 151 | 152 | func (c *channel) close() { 153 | c.pending.eof() 154 | close(c.msg) 155 | c.writeMu.Lock() 156 | // This is not necessary for a normal channel teardown, but if 157 | // there was another error, it is. 158 | c.sentClose = true 159 | c.writeMu.Unlock() 160 | // Unblock writers. 161 | c.remoteWin.close() 162 | } 163 | 164 | // responseMessageReceived is called when a success or failure message is 165 | // received on a channel to check that such a message is reasonable for the 166 | // given channel. 167 | func (ch *channel) responseMessageReceived() error { 168 | if ch.direction == channelInbound { 169 | return errors.New("qmux: channel response message received on inbound channel") 170 | } 171 | return nil 172 | } 173 | 174 | func (ch *channel) handle(msg codec.Message) error { 175 | switch m := msg.(type) { 176 | case *codec.DataMessage: 177 | return ch.handleData(m) 178 | 179 | case *codec.CloseMessage: 180 | ch.send(codec.CloseMessage{ 181 | ChannelID: ch.remoteId, 182 | }) 183 | ch.session.chanList.remove(ch.localId) 184 | ch.close() 185 | return nil 186 | 187 | case *codec.EOFMessage: 188 | ch.pending.eof() 189 | return nil 190 | 191 | case *codec.WindowAdjustMessage: 192 | if !ch.remoteWin.add(m.AdditionalBytes) { 193 | return fmt.Errorf("qmux: invalid window update for %d bytes", m.AdditionalBytes) 194 | } 195 | return nil 196 | 197 | case *codec.OpenConfirmMessage: 198 | if err := ch.responseMessageReceived(); err != nil { 199 | return err 200 | } 201 | if m.MaxPacketSize < minPacketLength || m.MaxPacketSize > maxPacketLength { 202 | return fmt.Errorf("qmux: invalid MaxPacketSize %d from peer", m.MaxPacketSize) 203 | } 204 | ch.remoteId = m.SenderID 205 | ch.maxRemotePayload = m.MaxPacketSize 206 | ch.remoteWin.add(m.WindowSize) 207 | ch.msg <- m 208 | return nil 209 | 210 | case *codec.OpenFailureMessage: 211 | if err := ch.responseMessageReceived(); err != nil { 212 | return err 213 | } 214 | ch.session.chanList.remove(m.ChannelID) 215 | ch.msg <- m 216 | return nil 217 | 218 | default: 219 | return fmt.Errorf("qmux: invalid channel message %v", msg) 220 | } 221 | } 222 | 223 | func (ch *channel) handleData(msg *codec.DataMessage) error { 224 | if msg.Length > ch.maxIncomingPayload { 225 | // TODO(hanwen): should send Disconnect? 226 | return errors.New("qmux: incoming packet exceeds maximum payload size") 227 | } 228 | 229 | if msg.Length != uint32(len(msg.Data)) { 230 | return errors.New("qmux: wrong packet length") 231 | } 232 | 233 | ch.windowMu.Lock() 234 | if ch.myWindow < msg.Length { 235 | ch.windowMu.Unlock() 236 | // TODO(hanwen): should send Disconnect with reason? 237 | return errors.New("qmux: remote side wrote too much") 238 | } 239 | ch.myWindow -= msg.Length 240 | ch.windowMu.Unlock() 241 | 242 | ch.pending.write(msg.Data) 243 | return nil 244 | } 245 | -------------------------------------------------------------------------------- /javascript/legacy/qmux/src/qmux_node.ts: -------------------------------------------------------------------------------- 1 | import * as qmux from "./qmux"; 2 | import * as WebSocket from "ws"; 3 | import * as net from "net"; 4 | 5 | export var Session = qmux.Session; 6 | export var Channel = qmux.Channel; 7 | 8 | interface IListener { 9 | accept(): Promise; 10 | close(): Promise; 11 | } 12 | 13 | interface IConn { 14 | read(len: number): Promise; 15 | write(buffer: Buffer): Promise; 16 | close(): Promise; 17 | } 18 | 19 | class queue { 20 | q: Array 21 | waiters: Array 22 | closed: boolean 23 | 24 | constructor() { 25 | this.q = []; 26 | this.waiters = []; 27 | this.closed = false; 28 | } 29 | 30 | push(obj: any) { 31 | if (this.closed) throw "closed queue"; 32 | if (this.waiters.length > 0) { 33 | this.waiters.shift()(obj); 34 | return; 35 | } 36 | this.q.push(obj); 37 | } 38 | 39 | shift(): Promise { 40 | if (this.closed) return; 41 | return new Promise(resolve => { 42 | if (this.q.length > 0) { 43 | resolve(this.q.shift()); 44 | return; 45 | } 46 | this.waiters.push(resolve); 47 | }) 48 | } 49 | 50 | close() { 51 | if (this.closed) return; 52 | this.closed = true; 53 | this.waiters.forEach(waiter => { 54 | waiter(undefined); 55 | }); 56 | } 57 | } 58 | 59 | export function DialUnix(path: string): Promise { 60 | return new Promise(resolve => { 61 | var socket = net.createConnection(path, () => { 62 | resolve(new TCPConn(socket)); 63 | }); 64 | }) 65 | } 66 | 67 | export function DialTCP(port: number, host?: string): Promise { 68 | return new Promise(resolve => { 69 | var socket = net.createConnection(port, host, () => { 70 | resolve(new TCPConn(socket)); 71 | }); 72 | }) 73 | } 74 | 75 | // TODO: modify TCPListener.listen() to support paths 76 | // export async function ListenUnix(path: string): Promise { 77 | // var listener = new TCPListener(); 78 | // await listener.listen(path); 79 | // return listener; 80 | // } 81 | 82 | export async function ListenTCP(port: number, host?: string): Promise { 83 | var listener = new TCPListener(); 84 | await listener.listen(port, host); 85 | return listener; 86 | } 87 | 88 | export class TCPListener implements IListener { 89 | server: net.Server 90 | pending: queue 91 | 92 | constructor() { 93 | this.pending = new queue(); 94 | } 95 | 96 | listen(port: number, host?: string): Promise { 97 | return new Promise(resolve => { 98 | this.server = net.createServer((c) => { 99 | this.pending.push(new qmux.Session(new TCPConn(c))); 100 | }); 101 | this.server.on('error', (err) => { 102 | throw err; 103 | }); 104 | this.server.on("close", () => { 105 | this.pending.close(); 106 | }); 107 | this.server.listen(port, host, resolve); 108 | }); 109 | } 110 | 111 | accept(): Promise { 112 | return this.pending.shift(); 113 | } 114 | 115 | close(): Promise { 116 | return new Promise((resolve, reject) => { 117 | this.server.close((err: Error) => { 118 | if (err !== undefined) { 119 | reject(err); 120 | } else { 121 | resolve(); 122 | } 123 | }); 124 | }); 125 | } 126 | } 127 | 128 | export class TCPConn implements IConn { 129 | socket: net.Socket 130 | error: any 131 | 132 | constructor(socket: net.Socket) { 133 | this.socket = socket; 134 | this.socket.on('error', (err) => this.error = err); 135 | } 136 | 137 | read(len: number): Promise { 138 | const stream = this.socket; 139 | 140 | return new Promise((resolve, reject) => { 141 | if (this.error) { 142 | const err = this.error 143 | delete this.error 144 | return reject(err) 145 | } 146 | 147 | if (!stream.readable || stream.destroyed) { 148 | return resolve() 149 | } 150 | 151 | const readableHandler = () => { 152 | let chunk = stream.read(len); 153 | 154 | if (chunk != null) { 155 | removeListeners(); 156 | resolve(chunk); 157 | } 158 | } 159 | 160 | const closeHandler = () => { 161 | removeListeners(); 162 | resolve(); 163 | } 164 | 165 | const endHandler = () => { 166 | removeListeners(); 167 | resolve(); 168 | } 169 | 170 | const errorHandler = (err) => { 171 | delete this.error; 172 | removeListeners(); 173 | reject(err); 174 | } 175 | 176 | const removeListeners = () => { 177 | stream.removeListener('close', closeHandler); 178 | stream.removeListener('error', errorHandler); 179 | stream.removeListener('end', endHandler); 180 | stream.removeListener('readable', readableHandler); 181 | } 182 | 183 | stream.on('close', closeHandler); 184 | stream.on('end', endHandler); 185 | stream.on('error', errorHandler); 186 | stream.on('readable', readableHandler); 187 | 188 | readableHandler(); 189 | }); 190 | } 191 | 192 | write(buffer: Buffer): Promise { 193 | return new Promise(resolve => { 194 | this.socket.write(buffer, () => resolve(buffer.byteLength)); 195 | }); 196 | } 197 | 198 | close(): Promise { 199 | return new Promise(resolve => { 200 | this.socket.destroy(); 201 | resolve(); 202 | }); 203 | } 204 | } 205 | 206 | 207 | 208 | export function DialWebsocket(addr: string): Promise { 209 | return new Promise((resolve, reject) => { 210 | var socket = new WebSocket(addr, { 211 | perMessageDeflate: false, 212 | origin: 'https://'+addr 213 | }); 214 | socket.on("open", () => { 215 | resolve(new WebsocketConn(socket)); 216 | }); 217 | socket.on("error", (err) => { 218 | reject(err); 219 | }); 220 | }) 221 | } 222 | 223 | export async function ListenWebsocket(port: number, host?: string): Promise { 224 | var listener = new WebsocketListener(); 225 | await listener.listen(port, host); 226 | return listener; 227 | } 228 | 229 | export class WebsocketListener implements IListener { 230 | server: WebSocket.Server 231 | pending: queue 232 | 233 | constructor() { 234 | this.pending = new queue(); 235 | } 236 | 237 | listen(port: number, host?: string): Promise { 238 | return new Promise(resolve => { 239 | this.server = new WebSocket.Server({ 240 | host: host, 241 | port: port, 242 | perMessageDeflate: false, 243 | }, resolve); 244 | this.server.on("connection", (c) => { 245 | this.pending.push(new qmux.Session(new WebsocketConn(c))); 246 | }); 247 | this.server.on("error", (err) => { 248 | throw err; 249 | }); 250 | this.server.on("close", () => { 251 | this.pending.close(); 252 | }); 253 | }); 254 | } 255 | 256 | accept(): Promise { 257 | return this.pending.shift(); 258 | } 259 | 260 | close(): Promise { 261 | return new Promise(resolve => { 262 | this.server.close(() => resolve()); 263 | }); 264 | } 265 | } 266 | 267 | export class WebsocketConn implements IConn { 268 | socket: WebSocket 269 | error: any 270 | waiters: Array 271 | buf: Buffer 272 | isClosed: boolean 273 | 274 | constructor(socket: WebSocket) { 275 | this.isClosed = false; 276 | this.buf = Buffer.alloc(0); 277 | this.waiters = []; 278 | this.socket = socket; 279 | this.socket.on("message", data => { 280 | var buf = data as Buffer; 281 | this.buf = Buffer.concat([this.buf, buf], this.buf.length+buf.length); 282 | if (this.waiters.length > 0) { 283 | this.waiters.shift()(); 284 | } 285 | }); 286 | this.socket.on("close", () => { 287 | this.close(); 288 | }); 289 | this.socket.on("error", (err) => { 290 | console.log("err", err) 291 | }) 292 | } 293 | 294 | read(len: number): Promise { 295 | return new Promise((resolve, reject) => { 296 | var tryRead = () => { 297 | if (this.buf === undefined) { 298 | resolve(undefined); 299 | return; 300 | } 301 | if (this.buf.length >= len) { 302 | var data = this.buf.slice(0, len); 303 | this.buf = this.buf.slice(len); 304 | resolve(data); 305 | return; 306 | } 307 | this.waiters.push(tryRead); 308 | } 309 | tryRead(); 310 | }) 311 | } 312 | 313 | write(buffer: Buffer): Promise { 314 | return new Promise((resolve, reject) => { 315 | this.socket.send(buffer, {binary: true}, () => { 316 | resolve(buffer.length) 317 | }); 318 | }); 319 | } 320 | 321 | close(): Promise { 322 | if (this.isClosed) return Promise.resolve(); 323 | return new Promise((resolve, reject) => { 324 | this.isClosed = true; 325 | this.buf = undefined; 326 | this.waiters.forEach(waiter => waiter()); 327 | this.socket.close(); 328 | resolve(); 329 | }); 330 | } 331 | } -------------------------------------------------------------------------------- /javascript/legacy/qmux/dist/qmux_node.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | var __generator = (this && this.__generator) || function (thisArg, body) { 11 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 12 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 13 | function verb(n) { return function (v) { return step([n, v]); }; } 14 | function step(op) { 15 | if (f) throw new TypeError("Generator is already executing."); 16 | while (_) try { 17 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 18 | if (y = 0, t) op = [op[0] & 2, t.value]; 19 | switch (op[0]) { 20 | case 0: case 1: t = op; break; 21 | case 4: _.label++; return { value: op[1], done: false }; 22 | case 5: _.label++; y = op[1]; op = [0]; continue; 23 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 24 | default: 25 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 26 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 27 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 28 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 29 | if (t[2]) _.ops.pop(); 30 | _.trys.pop(); continue; 31 | } 32 | op = body.call(thisArg, _); 33 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 34 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 35 | } 36 | }; 37 | Object.defineProperty(exports, "__esModule", { value: true }); 38 | var qmux = require("./qmux"); 39 | var WebSocket = require("ws"); 40 | var net = require("net"); 41 | exports.Session = qmux.Session; 42 | exports.Channel = qmux.Channel; 43 | var queue = /** @class */ (function () { 44 | function queue() { 45 | this.q = []; 46 | this.waiters = []; 47 | this.closed = false; 48 | } 49 | queue.prototype.push = function (obj) { 50 | if (this.closed) 51 | throw "closed queue"; 52 | if (this.waiters.length > 0) { 53 | this.waiters.shift()(obj); 54 | return; 55 | } 56 | this.q.push(obj); 57 | }; 58 | queue.prototype.shift = function () { 59 | var _this = this; 60 | if (this.closed) 61 | return; 62 | return new Promise(function (resolve) { 63 | if (_this.q.length > 0) { 64 | resolve(_this.q.shift()); 65 | return; 66 | } 67 | _this.waiters.push(resolve); 68 | }); 69 | }; 70 | queue.prototype.close = function () { 71 | if (this.closed) 72 | return; 73 | this.closed = true; 74 | this.waiters.forEach(function (waiter) { 75 | waiter(undefined); 76 | }); 77 | }; 78 | return queue; 79 | }()); 80 | function DialUnix(path) { 81 | return new Promise(function (resolve) { 82 | var socket = net.createConnection(path, function () { 83 | resolve(new TCPConn(socket)); 84 | }); 85 | }); 86 | } 87 | exports.DialUnix = DialUnix; 88 | function DialTCP(port, host) { 89 | return new Promise(function (resolve) { 90 | var socket = net.createConnection(port, host, function () { 91 | resolve(new TCPConn(socket)); 92 | }); 93 | }); 94 | } 95 | exports.DialTCP = DialTCP; 96 | // TODO: modify TCPListener.listen() to support paths 97 | // export async function ListenUnix(path: string): Promise { 98 | // var listener = new TCPListener(); 99 | // await listener.listen(path); 100 | // return listener; 101 | // } 102 | function ListenTCP(port, host) { 103 | return __awaiter(this, void 0, void 0, function () { 104 | var listener; 105 | return __generator(this, function (_a) { 106 | switch (_a.label) { 107 | case 0: 108 | listener = new TCPListener(); 109 | return [4 /*yield*/, listener.listen(port, host)]; 110 | case 1: 111 | _a.sent(); 112 | return [2 /*return*/, listener]; 113 | } 114 | }); 115 | }); 116 | } 117 | exports.ListenTCP = ListenTCP; 118 | var TCPListener = /** @class */ (function () { 119 | function TCPListener() { 120 | this.pending = new queue(); 121 | } 122 | TCPListener.prototype.listen = function (port, host) { 123 | var _this = this; 124 | return new Promise(function (resolve) { 125 | _this.server = net.createServer(function (c) { 126 | _this.pending.push(new qmux.Session(new TCPConn(c))); 127 | }); 128 | _this.server.on('error', function (err) { 129 | throw err; 130 | }); 131 | _this.server.on("close", function () { 132 | _this.pending.close(); 133 | }); 134 | _this.server.listen(port, host, resolve); 135 | }); 136 | }; 137 | TCPListener.prototype.accept = function () { 138 | return this.pending.shift(); 139 | }; 140 | TCPListener.prototype.close = function () { 141 | var _this = this; 142 | return new Promise(function (resolve, reject) { 143 | _this.server.close(function (err) { 144 | if (err !== undefined) { 145 | reject(err); 146 | } 147 | else { 148 | resolve(); 149 | } 150 | }); 151 | }); 152 | }; 153 | return TCPListener; 154 | }()); 155 | exports.TCPListener = TCPListener; 156 | var TCPConn = /** @class */ (function () { 157 | function TCPConn(socket) { 158 | var _this = this; 159 | this.socket = socket; 160 | this.socket.on('error', function (err) { return _this.error = err; }); 161 | } 162 | TCPConn.prototype.read = function (len) { 163 | var _this = this; 164 | var stream = this.socket; 165 | return new Promise(function (resolve, reject) { 166 | if (_this.error) { 167 | var err = _this.error; 168 | delete _this.error; 169 | return reject(err); 170 | } 171 | if (!stream.readable || stream.destroyed) { 172 | return resolve(); 173 | } 174 | var readableHandler = function () { 175 | var chunk = stream.read(len); 176 | if (chunk != null) { 177 | removeListeners(); 178 | resolve(chunk); 179 | } 180 | }; 181 | var closeHandler = function () { 182 | removeListeners(); 183 | resolve(); 184 | }; 185 | var endHandler = function () { 186 | removeListeners(); 187 | resolve(); 188 | }; 189 | var errorHandler = function (err) { 190 | delete _this.error; 191 | removeListeners(); 192 | reject(err); 193 | }; 194 | var removeListeners = function () { 195 | stream.removeListener('close', closeHandler); 196 | stream.removeListener('error', errorHandler); 197 | stream.removeListener('end', endHandler); 198 | stream.removeListener('readable', readableHandler); 199 | }; 200 | stream.on('close', closeHandler); 201 | stream.on('end', endHandler); 202 | stream.on('error', errorHandler); 203 | stream.on('readable', readableHandler); 204 | readableHandler(); 205 | }); 206 | }; 207 | TCPConn.prototype.write = function (buffer) { 208 | var _this = this; 209 | return new Promise(function (resolve) { 210 | _this.socket.write(buffer, function () { return resolve(buffer.byteLength); }); 211 | }); 212 | }; 213 | TCPConn.prototype.close = function () { 214 | var _this = this; 215 | return new Promise(function (resolve) { 216 | _this.socket.destroy(); 217 | resolve(); 218 | }); 219 | }; 220 | return TCPConn; 221 | }()); 222 | exports.TCPConn = TCPConn; 223 | function DialWebsocket(addr) { 224 | return new Promise(function (resolve, reject) { 225 | var socket = new WebSocket(addr, { 226 | perMessageDeflate: false, 227 | origin: 'https://' + addr 228 | }); 229 | socket.on("open", function () { 230 | resolve(new WebsocketConn(socket)); 231 | }); 232 | socket.on("error", function (err) { 233 | reject(err); 234 | }); 235 | }); 236 | } 237 | exports.DialWebsocket = DialWebsocket; 238 | function ListenWebsocket(port, host) { 239 | return __awaiter(this, void 0, void 0, function () { 240 | var listener; 241 | return __generator(this, function (_a) { 242 | switch (_a.label) { 243 | case 0: 244 | listener = new WebsocketListener(); 245 | return [4 /*yield*/, listener.listen(port, host)]; 246 | case 1: 247 | _a.sent(); 248 | return [2 /*return*/, listener]; 249 | } 250 | }); 251 | }); 252 | } 253 | exports.ListenWebsocket = ListenWebsocket; 254 | var WebsocketListener = /** @class */ (function () { 255 | function WebsocketListener() { 256 | this.pending = new queue(); 257 | } 258 | WebsocketListener.prototype.listen = function (port, host) { 259 | var _this = this; 260 | return new Promise(function (resolve) { 261 | _this.server = new WebSocket.Server({ 262 | host: host, 263 | port: port, 264 | perMessageDeflate: false, 265 | }, resolve); 266 | _this.server.on("connection", function (c) { 267 | _this.pending.push(new qmux.Session(new WebsocketConn(c))); 268 | }); 269 | _this.server.on("error", function (err) { 270 | throw err; 271 | }); 272 | _this.server.on("close", function () { 273 | _this.pending.close(); 274 | }); 275 | }); 276 | }; 277 | WebsocketListener.prototype.accept = function () { 278 | return this.pending.shift(); 279 | }; 280 | WebsocketListener.prototype.close = function () { 281 | var _this = this; 282 | return new Promise(function (resolve) { 283 | _this.server.close(function () { return resolve(); }); 284 | }); 285 | }; 286 | return WebsocketListener; 287 | }()); 288 | exports.WebsocketListener = WebsocketListener; 289 | var WebsocketConn = /** @class */ (function () { 290 | function WebsocketConn(socket) { 291 | var _this = this; 292 | this.isClosed = false; 293 | this.buf = Buffer.alloc(0); 294 | this.waiters = []; 295 | this.socket = socket; 296 | this.socket.on("message", function (data) { 297 | var buf = data; 298 | _this.buf = Buffer.concat([_this.buf, buf], _this.buf.length + buf.length); 299 | if (_this.waiters.length > 0) { 300 | _this.waiters.shift()(); 301 | } 302 | }); 303 | this.socket.on("close", function () { 304 | _this.close(); 305 | }); 306 | this.socket.on("error", function (err) { 307 | console.log("err", err); 308 | }); 309 | } 310 | WebsocketConn.prototype.read = function (len) { 311 | var _this = this; 312 | return new Promise(function (resolve, reject) { 313 | var tryRead = function () { 314 | if (_this.buf === undefined) { 315 | resolve(undefined); 316 | return; 317 | } 318 | if (_this.buf.length >= len) { 319 | var data = _this.buf.slice(0, len); 320 | _this.buf = _this.buf.slice(len); 321 | resolve(data); 322 | return; 323 | } 324 | _this.waiters.push(tryRead); 325 | }; 326 | tryRead(); 327 | }); 328 | }; 329 | WebsocketConn.prototype.write = function (buffer) { 330 | var _this = this; 331 | return new Promise(function (resolve, reject) { 332 | _this.socket.send(buffer, { binary: true }, function () { 333 | resolve(buffer.length); 334 | }); 335 | }); 336 | }; 337 | WebsocketConn.prototype.close = function () { 338 | var _this = this; 339 | if (this.isClosed) 340 | return Promise.resolve(); 341 | return new Promise(function (resolve, reject) { 342 | _this.isClosed = true; 343 | _this.buf = undefined; 344 | _this.waiters.forEach(function (waiter) { return waiter(); }); 345 | _this.socket.close(); 346 | resolve(); 347 | }); 348 | }; 349 | return WebsocketConn; 350 | }()); 351 | exports.WebsocketConn = WebsocketConn; 352 | -------------------------------------------------------------------------------- /javascript/legacy/qrpc/src/qrpc.ts: -------------------------------------------------------------------------------- 1 | import * as msgpack from "msgpack-lite"; 2 | 3 | interface Session { 4 | open(): Promise; 5 | accept(): Promise; 6 | close(): Promise; 7 | } 8 | 9 | interface Listener { 10 | accept(): Promise; 11 | close(): Promise; 12 | } 13 | 14 | interface Channel { 15 | read(len: number): Promise; 16 | write(buffer: Buffer): Promise; 17 | close(): Promise; 18 | } 19 | 20 | interface Codec { 21 | encode(v: any): Uint8Array 22 | decode(buf: Uint8Array): any 23 | } 24 | 25 | function errable(p: Promise): Promise { 26 | return p 27 | .then(ret => [ret, null]) 28 | .catch(err => [null, err]); 29 | } 30 | 31 | function sleep(ms: number): Promise { 32 | return new Promise(res => setTimeout(res, ms)); 33 | } 34 | 35 | function loopYield(name: string): Promise { 36 | //console.log(name); 37 | //return sleep(10); 38 | return Promise.resolve(); 39 | } 40 | 41 | // only one codec per channel because of read loop! 42 | class FrameCodec { 43 | channel: Channel; 44 | codec: Codec; 45 | buf: Array; 46 | waiters: Array; 47 | readLimit: number; 48 | readCount: number; 49 | 50 | constructor(channel: Channel, readLimit: number = -1) { 51 | this.channel = channel; 52 | this.codec = msgpack; 53 | this.buf = []; 54 | this.waiters = []; 55 | this.readLimit = readLimit; 56 | this.readCount = 0; 57 | this.readLoop(); 58 | } 59 | 60 | async readLoop() { 61 | while (true) { 62 | if (this.readLimit > 0 && this.readCount >= this.readLimit) { 63 | return; 64 | } 65 | try { 66 | await loopYield("readloop"); 67 | var sbuf = await this.channel.read(4); 68 | if (sbuf === undefined) { 69 | //console.log("DEBUG: readloop exited on length"); 70 | return; 71 | } 72 | var sdata = new DataView(new Uint8Array(sbuf).buffer); 73 | var size = sdata.getUint32(0); 74 | var buf = await this.channel.read(size); 75 | if (buf === undefined) { 76 | //console.log("DEBUG: readloop exited on data"); 77 | return; 78 | } 79 | this.readCount++; 80 | var v = this.codec.decode(buf); 81 | if (this.waiters.length > 0) { 82 | this.waiters.shift()(v); 83 | continue; 84 | } 85 | this.buf.push(v); 86 | } catch (e) { 87 | throw new Error("codec readLoop: " + e); 88 | } 89 | } 90 | } 91 | 92 | async encode(v: any): Promise { 93 | var buf = this.codec.encode(v); 94 | var sdata = new DataView(new ArrayBuffer(4)); 95 | sdata.setUint32(0, buf.length); 96 | await this.channel.write(Buffer.from(sdata.buffer)); 97 | await this.channel.write(buf as Buffer); 98 | return Promise.resolve(); 99 | } 100 | 101 | decode(): Promise { 102 | return new Promise((resolve, reject) => { 103 | if (this.buf.length > 0) { 104 | resolve(this.buf.shift()); 105 | return; 106 | } 107 | this.waiters.push(resolve); 108 | }) 109 | } 110 | } 111 | 112 | export class API { 113 | handlers: { [key: string]: Handler; }; 114 | 115 | constructor() { 116 | this.handlers = {}; 117 | } 118 | 119 | handle(path: string, handler: Handler): void { 120 | this.handlers[path] = handler; 121 | } 122 | 123 | handleFunc(path: string, handler: (r: Responder, c: Call) => void): void { 124 | this.handle(path, { 125 | serveRPC: async (rr: Responder, cc: Call) => { 126 | await handler(rr, cc); 127 | } 128 | }) 129 | } 130 | 131 | handler(path: string): Handler { 132 | for (var p in this.handlers) { 133 | if (this.handlers.hasOwnProperty(p)) { 134 | if (path.startsWith(p)) { 135 | return this.handlers[p]; 136 | } 137 | } 138 | } 139 | } 140 | 141 | async serveAPI(session: Session, ch: Channel): Promise { 142 | var codec = new FrameCodec(ch); 143 | var cdata = await codec.decode(); 144 | var call = new Call(cdata.Destination); 145 | call.parse(); 146 | call.decode = () => codec.decode(); 147 | call.caller = new Client(session); 148 | var header = new ResponseHeader(); 149 | var resp = new responder(ch, codec, header); 150 | var handler = this.handler(call.Destination); 151 | if (!handler) { 152 | resp.return(new Error("handler does not exist for this destination: " + call.Destination)); 153 | return; 154 | } 155 | await handler.serveRPC(resp, call); 156 | return Promise.resolve(); 157 | } 158 | } 159 | 160 | export interface Responder { 161 | header: ResponseHeader; 162 | return(v: any): void; 163 | hijack(v: any): Promise; 164 | } 165 | 166 | class responder implements Responder { 167 | header: ResponseHeader; 168 | ch: Channel; 169 | codec: FrameCodec; 170 | 171 | constructor(ch: Channel, codec: FrameCodec, header: ResponseHeader) { 172 | this.ch = ch; 173 | this.codec = codec; 174 | this.header = header; 175 | } 176 | 177 | async return(v: any): Promise { 178 | if (v instanceof Error) { 179 | this.header.Error = v.message; 180 | v = null; 181 | } 182 | await this.codec.encode(this.header); 183 | await this.codec.encode(v); 184 | return this.ch.close(); 185 | } 186 | 187 | async hijack(v: any): Promise { 188 | if (v instanceof Error) { 189 | this.header.Error = v.message; 190 | v = null; 191 | } 192 | this.header.Hijacked = true; 193 | await this.codec.encode(this.header); 194 | await this.codec.encode(v); 195 | return this.ch; 196 | } 197 | } 198 | 199 | class ResponseHeader { 200 | Error: string; 201 | Hijacked: boolean; 202 | } 203 | 204 | class Response { 205 | error: string; 206 | hijacked: boolean; 207 | reply: any; 208 | channel: Channel; 209 | } 210 | 211 | interface Caller { 212 | call(method: string, args: any): Promise; 213 | } 214 | 215 | export class Call { 216 | Destination: string; 217 | objectPath: string; 218 | method: string; 219 | caller: Caller; 220 | decode: () => Promise; 221 | 222 | constructor(Destination: string) { 223 | this.Destination = Destination; 224 | } 225 | 226 | parse() { 227 | if (this.Destination.length === 0) { 228 | throw new Error("no destination specified"); 229 | } 230 | if (this.Destination[0] == "/") { 231 | this.Destination = this.Destination.substr(1); 232 | } 233 | var parts = this.Destination.split("/"); 234 | if (parts.length === 1) { 235 | this.objectPath = "/"; 236 | this.method = parts[0]; 237 | return; 238 | } 239 | this.method = parts.pop(); 240 | this.objectPath = parts.join("/"); 241 | } 242 | } 243 | 244 | interface Handler { 245 | serveRPC(r: Responder, c: Call): void; 246 | } 247 | 248 | export class Client implements Caller { 249 | session: Session; 250 | api: API; 251 | 252 | constructor(session: Session, api?: API) { 253 | this.session = session; 254 | this.api = api; 255 | } 256 | 257 | async serveAPI(): Promise { 258 | if (this.api === undefined) { 259 | this.api = new API(); 260 | } 261 | while (true) { 262 | var ch = await this.session.accept(); 263 | if (ch === undefined) { 264 | return; 265 | } 266 | this.api.serveAPI(this.session, ch) 267 | await loopYield("client channel accept loop"); 268 | } 269 | } 270 | 271 | close(): Promise { 272 | return this.session.close(); 273 | } 274 | 275 | async call(path: string, args: any): Promise { 276 | var resp: Response = new Response(); 277 | try { 278 | var ch = await this.session.open(); 279 | var codec = new FrameCodec(ch, 2); 280 | await codec.encode(new Call(path)); 281 | await codec.encode(args); 282 | 283 | var header: ResponseHeader = await codec.decode(); 284 | if (header.Error !== undefined && header.Error !== null) { 285 | await ch.close(); 286 | return Promise.reject(header.Error); 287 | } 288 | resp.error = header.Error; 289 | resp.hijacked = header.Hijacked; 290 | resp.channel = ch; 291 | resp.reply = await codec.decode(); 292 | if (resp.hijacked !== true) { 293 | await ch.close(); 294 | } 295 | return resp; 296 | } catch (e) { 297 | console.error(e, path, args, resp); 298 | //await ch.close(); 299 | } 300 | } 301 | } 302 | 303 | export class Server { 304 | API: API; 305 | 306 | async serveAPI(sess: Session) { 307 | while (true) { 308 | var ch = await sess.accept(); 309 | if (ch === undefined) { 310 | sess.close(); 311 | return; 312 | } 313 | this.API.serveAPI(sess, ch); 314 | await loopYield("server channel accept loop"); 315 | } 316 | } 317 | 318 | async serve(l: Listener, api?: API) { 319 | if (api) { 320 | this.API = api; 321 | } 322 | if (!this.API) { 323 | this.API = new API(); 324 | } 325 | while (true) { 326 | var sess = await l.accept(); 327 | if (sess === undefined) { 328 | return; 329 | } 330 | this.serveAPI(sess); 331 | await loopYield("server connection accept loop"); 332 | } 333 | } 334 | } 335 | 336 | function exportFunc(fn: Function, rcvr: any): Handler { 337 | return { 338 | serveRPC: async function (r: Responder, c: Call) { 339 | var args = await c.decode(); 340 | try { 341 | r.return((await fn.apply(rcvr, [args]) || null)); 342 | } catch (e) { 343 | switch (typeof e) { 344 | case 'string': 345 | r.return(new Error(e)); 346 | case 'undefined': 347 | r.return(new Error("unknown error")); 348 | case 'object': 349 | r.return(new Error(e.message)); 350 | default: 351 | r.return(new Error("unknown error: " + e)); 352 | } 353 | } 354 | 355 | } 356 | }; 357 | } 358 | 359 | export function Export(v: any): Handler { 360 | if (typeof v === 'function') { 361 | return exportFunc(v, null); 362 | } 363 | if (typeof v != 'object') { 364 | throw new Error("can only export functions and objects"); 365 | } 366 | var handlers = {}; 367 | if (v.constructor.name === "Object") { 368 | for (var key in v) { 369 | if (v.hasOwnProperty(key) && typeof v[key] === 'function') { 370 | handlers[key] = exportFunc(v[key], v); 371 | } 372 | } 373 | } else { 374 | var props = Object.getOwnPropertyNames(Object.getPrototypeOf(v)); 375 | for (var idx in props) { 376 | var propName = props[idx]; 377 | if (propName != "constructor" && typeof v[propName] === 'function') { 378 | handlers[propName] = exportFunc(v[propName], v); 379 | } 380 | } 381 | } 382 | return { 383 | serveRPC: async function (r: Responder, c: Call) { 384 | if (!handlers.hasOwnProperty(c.method)) { 385 | r.return(new Error("method handler does not exist for this destination: " + c.method)); 386 | return; 387 | } 388 | await handlers[c.method].serveRPC(r, c); 389 | } 390 | }; 391 | } 392 | 393 | function uuid4(): string { 394 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (cc) { 395 | var rr = Math.random() * 16 | 0; return (cc === 'x' ? rr : (rr & 0x3 | 0x8)).toString(16); 396 | }); 397 | } 398 | 399 | export class ObjectManager { 400 | values: any; 401 | mountPath: string; 402 | 403 | constructor() { 404 | this.values = {}; 405 | this.mountPath = "/"; 406 | } 407 | 408 | object(path: string): ManagedObject { 409 | var id = path.replace(this.mountPath, ""); 410 | if (id[0] === "/") { 411 | id = id.substr(1); 412 | } 413 | var v = this.values[id]; 414 | if (!v) { 415 | return undefined; 416 | } 417 | return new ManagedObject(this, id, v); 418 | } 419 | 420 | register(v: any): ManagedObject { 421 | var id = uuid4(); 422 | this.values[id] = v; 423 | return new ManagedObject(this, id, v); 424 | } 425 | 426 | async serveRPC(r: Responder, c: Call) { 427 | var parts = c.objectPath.split("/"); 428 | var id = parts.pop(); 429 | var v = this.values[id]; 430 | if (!v) { 431 | r.return(new Error("object not registered: " + c.objectPath)); 432 | return; 433 | } 434 | if (typeof v.serveRPC === "function") { 435 | v.serveRPC(r, c) 436 | } else { 437 | Export(v).serveRPC(r, c); 438 | } 439 | 440 | } 441 | 442 | mount(api: API, path: string) { 443 | this.mountPath = path; 444 | api.handle(path, this); 445 | } 446 | } 447 | 448 | class ManagedObject { 449 | manager: ObjectManager; 450 | id: string; 451 | value: any; 452 | 453 | constructor(manager: ObjectManager, id: string, value: any) { 454 | this.manager = manager; 455 | this.id = id; 456 | this.value = value; 457 | } 458 | 459 | dispose() { 460 | delete this.manager.values[this.id]; 461 | } 462 | 463 | path(): string { 464 | return [this.manager.mountPath, this.id].join("/"); 465 | } 466 | 467 | handle(): any { 468 | return { "ObjectPath": this.path() }; 469 | } 470 | } -------------------------------------------------------------------------------- /javascript/legacy/qmux/src/qmux.ts: -------------------------------------------------------------------------------- 1 | // message numbers 2 | const msgChannelOpen = 100; 3 | const msgChannelOpenConfirm = 101; 4 | const msgChannelOpenFailure = 102; 5 | const msgChannelWindowAdjust = 103; 6 | const msgChannelData = 104; 7 | const msgChannelEOF = 105; 8 | const msgChannelClose = 106; 9 | 10 | // hardcoded window size constants 11 | const minPacketLength = 9; 12 | const channelMaxPacket = 1 << 15; 13 | const channelWindowSize = 64 * channelMaxPacket; 14 | 15 | // message types as objects 16 | interface channelOpenMsg { 17 | peersID: number; 18 | peersWindow: number; 19 | maxPacketSize: number; 20 | } 21 | 22 | interface channelOpenConfirmMsg { 23 | peersID: number; 24 | myID: number; 25 | myWindow: number; 26 | maxPacketSize: number; 27 | } 28 | 29 | interface channelOpenFailureMsg { 30 | peersID: number; 31 | } 32 | 33 | interface channelWindowAdjustMsg { 34 | peersID: number; 35 | additionalBytes: number; 36 | } 37 | 38 | interface channelDataMsg { 39 | peersID: number; 40 | length: number; 41 | rest: Uint8Array; 42 | } 43 | 44 | interface channelEOFMsg { 45 | peersID: number; 46 | } 47 | 48 | interface channelCloseMsg { 49 | peersID: number; 50 | } 51 | 52 | // queue primitive for incoming connections and 53 | // signaling channel ready state 54 | class queue { 55 | q: Array 56 | waiters: Array 57 | closed: boolean 58 | 59 | constructor() { 60 | this.q = []; 61 | this.waiters = []; 62 | this.closed = false; 63 | } 64 | 65 | push(obj: any) { 66 | if (this.closed) throw "closed queue"; 67 | if (this.waiters.length > 0) { 68 | this.waiters.shift()(obj); 69 | return; 70 | } 71 | this.q.push(obj); 72 | } 73 | 74 | shift(): Promise { 75 | if (this.closed) return; 76 | return new Promise(resolve => { 77 | if (this.q.length > 0) { 78 | resolve(this.q.shift()); 79 | return; 80 | } 81 | this.waiters.push(resolve); 82 | }) 83 | } 84 | 85 | close() { 86 | if (this.closed) return; 87 | this.closed = true; 88 | this.waiters.forEach(waiter => { 89 | waiter(undefined); 90 | }); 91 | } 92 | } 93 | 94 | // the libmux API 95 | interface IConn { 96 | read(len: number): Promise; 97 | write(buffer: Buffer): Promise; 98 | close(): Promise; 99 | } 100 | 101 | interface ISession { 102 | open(): Promise; 103 | accept(): Promise; 104 | close(): Promise; 105 | //wait(): Promise; 106 | } 107 | 108 | interface IChannel extends IConn { 109 | ident(): number 110 | } 111 | 112 | // session class that manages the "connection" 113 | export class Session implements ISession { 114 | conn: IConn; 115 | channels: Array; 116 | incoming: queue; 117 | 118 | constructor(conn: IConn) { 119 | this.conn = conn; 120 | this.channels = []; 121 | this.incoming = new queue(); 122 | this.loop(); 123 | } 124 | 125 | async readPacket(): Promise { 126 | var sizes = new Map([ 127 | [msgChannelOpen, 12], 128 | [msgChannelOpenConfirm, 16], 129 | [msgChannelOpenFailure, 4], 130 | [msgChannelWindowAdjust, 8], 131 | [msgChannelData, 8], 132 | [msgChannelEOF, 4], 133 | [msgChannelClose, 4], 134 | ]); 135 | var msg = await this.conn.read(1); 136 | if (msg === undefined) { 137 | return; 138 | } 139 | if (msg[0] < msgChannelOpen || msg[0] > msgChannelClose) { 140 | return Promise.reject("bad packet: "+msg[0]); 141 | } 142 | var rest = await this.conn.read(sizes.get(msg[0])); 143 | if (rest === undefined) { 144 | return Promise.reject("unexpected EOF"); 145 | } 146 | if (msg[0] === msgChannelData) { 147 | var view = new DataView(new Uint8Array(rest).buffer); 148 | var length = view.getUint32(4); 149 | var data = await this.conn.read(length); 150 | if (data === undefined) { 151 | return Promise.reject("unexpected EOF"); 152 | } 153 | return Buffer.concat([msg, rest, data], length+rest.length+1); 154 | } 155 | return Buffer.concat([msg, rest], rest.length+1); 156 | } 157 | 158 | async handleChannelOpen(packet: Buffer) { 159 | var msg: channelOpenMsg = decode(packet); 160 | if (msg.maxPacketSize < minPacketLength || msg.maxPacketSize > 1<<30) { 161 | await this.conn.write(encode(msgChannelOpenFailure, { 162 | peersID: msg.peersID 163 | })); 164 | return; 165 | } 166 | var c = this.newChannel(); 167 | c.remoteId = msg.peersID; 168 | c.maxRemotePayload = msg.maxPacketSize; 169 | c.remoteWin = msg.peersWindow; 170 | c.maxIncomingPayload = channelMaxPacket; 171 | this.incoming.push(c); 172 | await this.conn.write(encode(msgChannelOpenConfirm, { 173 | peersID: c.remoteId, 174 | myID: c.localId, 175 | myWindow: c.myWindow, 176 | maxPacketSize: c.maxIncomingPayload 177 | })); 178 | } 179 | 180 | async open(): Promise { 181 | var ch = this.newChannel(); 182 | ch.maxIncomingPayload = channelMaxPacket; 183 | await this.conn.write(encode(msgChannelOpen, { 184 | peersWindow: ch.myWindow, 185 | maxPacketSize: ch.maxIncomingPayload, 186 | peersID: ch.localId 187 | })); 188 | if (await ch.ready.shift()) { 189 | return ch; 190 | } 191 | throw "failed to open"; 192 | } 193 | 194 | newChannel(): Channel { 195 | var ch = new Channel(); 196 | ch.remoteWin = 0; 197 | ch.myWindow = channelWindowSize; 198 | ch.ready = new queue(); 199 | ch.readBuf = Buffer.alloc(0); 200 | ch.readers = []; 201 | ch.session = this; 202 | ch.localId = this.addCh(ch); 203 | return ch; 204 | } 205 | 206 | async loop() { 207 | try { 208 | while (true) { 209 | var packet = await this.readPacket(); 210 | if (packet === undefined) { 211 | this.close(); 212 | return; 213 | } 214 | if (packet[0] == msgChannelOpen) { 215 | await this.handleChannelOpen(packet); 216 | continue; 217 | } 218 | var data = new DataView(new Uint8Array(packet).buffer); 219 | var id = data.getUint32(1); 220 | var ch = this.getCh(id); 221 | if (ch === undefined) { 222 | throw "invalid channel ("+id+") on op "+packet[0]; 223 | } 224 | await ch.handlePacket(data); 225 | } 226 | } catch (e) { 227 | throw new Error("session readloop: "+e); 228 | } 229 | // catch { 230 | // this.channels.forEach(async (ch) => { 231 | // await ch.close(); 232 | // }) 233 | // this.channels = []; 234 | // await this.conn.close(); 235 | // } 236 | } 237 | 238 | getCh(id: number): Channel { 239 | var ch = this.channels[id]; 240 | if (ch.localId !== id) { 241 | console.log("bad ids:", id, ch.localId, ch.remoteId); 242 | } 243 | return ch; 244 | } 245 | 246 | addCh(ch: Channel): number { 247 | this.channels.forEach((v,i) => { 248 | if (v === undefined) { 249 | this.channels[i] = ch; 250 | return i; 251 | } 252 | }); 253 | this.channels.push(ch); 254 | return this.channels.length-1; 255 | } 256 | 257 | rmCh(id: number) { 258 | this.channels[id] = undefined; 259 | } 260 | 261 | accept(): Promise { 262 | return this.incoming.shift(); 263 | } 264 | 265 | async close(): Promise { 266 | for (const id of Object.keys(this.channels)) { 267 | if (this.channels[id] !== undefined) { 268 | this.channels[id].shutdown(); 269 | } 270 | } 271 | return this.conn.close(); 272 | } 273 | } 274 | 275 | // channel represents a virtual muxed connection 276 | export class Channel { 277 | localId: number; 278 | remoteId: number; 279 | maxIncomingPayload: number; 280 | maxRemotePayload: number; 281 | session: Session; 282 | ready: queue; 283 | sentEOF: boolean; 284 | gotEOF: boolean; 285 | sentClose: boolean; 286 | remoteWin: number; 287 | myWindow: number; 288 | readBuf: Buffer; 289 | readers: Array; 290 | 291 | ident(): number { 292 | return this.localId; 293 | } 294 | 295 | sendPacket(packet: Uint8Array): Promise { 296 | if (this.sentClose) { 297 | throw "EOF"; 298 | } 299 | this.sentClose = (packet[0] === msgChannelClose); 300 | return this.session.conn.write(Buffer.from(packet)); 301 | } 302 | 303 | sendMessage(type: number, msg: any): Promise { 304 | var data = new DataView(encode(type, msg).buffer); 305 | data.setUint32(1, this.remoteId); 306 | return this.sendPacket(new Uint8Array(data.buffer)); 307 | } 308 | 309 | handlePacket(packet: DataView) { 310 | if (packet.getUint8(0) === msgChannelData) { 311 | this.handleData(packet); 312 | return; 313 | } 314 | if (packet.getUint8(0) === msgChannelClose) { 315 | this.handleClose(); 316 | return; 317 | } 318 | if (packet.getUint8(0) === msgChannelEOF) { 319 | this.gotEOF = true; 320 | // if (this.readers.length > 0) { 321 | // this.readers.shift()(); 322 | // } 323 | return; 324 | } 325 | if (packet.getUint8(0) === msgChannelOpenFailure) { 326 | var fmsg:channelOpenFailureMsg = decode(Buffer.from(packet.buffer)); 327 | this.session.rmCh(fmsg.peersID); 328 | this.ready.push(false); 329 | return; 330 | } 331 | if (packet.getUint8(0) === msgChannelOpenConfirm) { 332 | var cmsg:channelOpenConfirmMsg = decode(Buffer.from(packet.buffer)); 333 | if (cmsg.maxPacketSize < minPacketLength || cmsg.maxPacketSize > 1<<30) { 334 | throw "invalid max packet size"; 335 | } 336 | this.remoteId = cmsg.myID; 337 | this.maxRemotePayload = cmsg.maxPacketSize; 338 | this.remoteWin += cmsg.myWindow; 339 | this.ready.push(true); 340 | return; 341 | } 342 | if (packet.getUint8(0) === msgChannelWindowAdjust) { 343 | var amsg:channelWindowAdjustMsg = decode(Buffer.from(packet.buffer)); 344 | this.remoteWin += amsg.additionalBytes; 345 | } 346 | } 347 | 348 | async handleData(packet: DataView) { 349 | var length = packet.getUint32(5); 350 | if (length == 0) { 351 | return; 352 | } 353 | if (length > this.maxIncomingPayload) { 354 | throw "incoming packet exceeds maximum payload size"; 355 | } 356 | var data = Buffer.from(packet.buffer).slice(9); 357 | // TODO: check packet length 358 | if (this.myWindow < length) { 359 | throw "remote side wrote too much"; 360 | } 361 | this.myWindow -= length; 362 | this.readBuf = Buffer.concat([this.readBuf, data], this.readBuf.length+data.length); 363 | if (this.readers.length > 0) { 364 | this.readers.shift()(); 365 | } 366 | } 367 | 368 | async adjustWindow(n: number) { 369 | // TODO 370 | } 371 | 372 | read(len): Promise { 373 | return new Promise(resolve => { 374 | var tryRead = () => { 375 | if (this.readBuf === undefined) { 376 | resolve(undefined); 377 | return; 378 | } 379 | if (this.readBuf.length >= len) { 380 | var data = this.readBuf.slice(0, len); 381 | this.readBuf = this.readBuf.slice(len); 382 | resolve(data); 383 | if (this.readBuf.length == 0 && this.gotEOF) { 384 | this.readBuf = undefined; 385 | } 386 | return; 387 | } 388 | this.readers.push(tryRead); 389 | } 390 | tryRead(); 391 | }); 392 | } 393 | 394 | write(buffer: Buffer): Promise { 395 | if (this.sentEOF) { 396 | return Promise.reject("EOF"); 397 | } 398 | // TODO: use window 399 | var header = new DataView(new ArrayBuffer(9)); 400 | header.setUint8(0, msgChannelData); 401 | header.setUint32(1, this.remoteId); 402 | header.setUint32(5, buffer.byteLength); 403 | var packet = new Uint8Array(9+buffer.byteLength); 404 | packet.set(new Uint8Array(header.buffer), 0); 405 | packet.set(new Uint8Array(buffer), 9); 406 | return this.sendPacket(packet); 407 | } 408 | 409 | handleClose(): Promise { 410 | return this.close(); 411 | } 412 | 413 | async close(): Promise { 414 | if (!this.sentClose) { 415 | await this.sendMessage(msgChannelClose, { 416 | peersID: this.remoteId 417 | }); 418 | this.sentClose = true; 419 | while (await this.ready.shift() !== undefined) {} 420 | return; 421 | } 422 | this.shutdown(); 423 | } 424 | 425 | shutdown() { 426 | this.readBuf = undefined; 427 | this.readers.forEach(reader => reader()); 428 | this.ready.close(); 429 | this.session.rmCh(this.localId); 430 | } 431 | 432 | async closeWrite() { 433 | this.sentEOF = true; 434 | await this.sendMessage(msgChannelEOF, { 435 | peersID: this.remoteId 436 | }); 437 | } 438 | } 439 | 440 | function encode(type: number, obj: any): Buffer { 441 | switch (type) { 442 | case msgChannelClose: 443 | var data = new DataView(new ArrayBuffer(5)); 444 | data.setUint8(0, type); 445 | data.setUint32(1, (obj).peersID); 446 | return Buffer.from(data.buffer); 447 | case msgChannelData: 448 | var datamsg = obj; 449 | var data = new DataView(new ArrayBuffer(9)); 450 | data.setUint8(0, type); 451 | data.setUint32(1, datamsg.peersID); 452 | data.setUint32(5, datamsg.length); 453 | var buf = new Uint8Array(9+datamsg.length); 454 | buf.set(new Uint8Array(data.buffer), 0); 455 | buf.set(datamsg.rest, 9); 456 | return Buffer.from(buf.buffer); 457 | case msgChannelEOF: 458 | var data = new DataView(new ArrayBuffer(5)); 459 | data.setUint8(0, type); 460 | data.setUint32(1, (obj).peersID); 461 | return Buffer.from(data.buffer); 462 | case msgChannelOpen: 463 | var data = new DataView(new ArrayBuffer(13)); 464 | var openmsg = obj; 465 | data.setUint8(0, type); 466 | data.setUint32(1, openmsg.peersID); 467 | data.setUint32(5, openmsg.peersWindow); 468 | data.setUint32(9, openmsg.maxPacketSize); 469 | return Buffer.from(data.buffer); 470 | case msgChannelOpenConfirm: 471 | var data = new DataView(new ArrayBuffer(17)); 472 | var confirmmsg = obj; 473 | data.setUint8(0, type); 474 | data.setUint32(1, confirmmsg.peersID); 475 | data.setUint32(5, confirmmsg.myID); 476 | data.setUint32(9, confirmmsg.myWindow); 477 | data.setUint32(13, confirmmsg.maxPacketSize); 478 | return Buffer.from(data.buffer); 479 | case msgChannelOpenFailure: 480 | var data = new DataView(new ArrayBuffer(5)); 481 | data.setUint8(0, type); 482 | data.setUint32(1, (obj).peersID); 483 | return Buffer.from(data.buffer); 484 | case msgChannelWindowAdjust: 485 | var data = new DataView(new ArrayBuffer(9)); 486 | var adjustmsg = obj; 487 | data.setUint8(0, type); 488 | data.setUint32(1, adjustmsg.peersID); 489 | data.setUint32(5, adjustmsg.additionalBytes); 490 | return Buffer.from(data.buffer); 491 | default: 492 | throw "unknown type"; 493 | } 494 | } 495 | 496 | function decode(packet: Buffer): any { 497 | var packetBuf = new Uint8Array(packet).buffer; 498 | switch (packet[0]) { 499 | case msgChannelClose: 500 | var data = new DataView(packetBuf); 501 | var closeMsg:channelCloseMsg = { 502 | peersID: data.getUint32(1) 503 | }; 504 | return closeMsg; 505 | case msgChannelData: 506 | var data = new DataView(packetBuf); 507 | var dataLength = data.getUint32(5); 508 | var dataMsg:channelDataMsg = { 509 | peersID: data.getUint32(1), 510 | length: dataLength, 511 | rest: new Uint8Array(dataLength), 512 | }; 513 | dataMsg.rest.set(new Uint8Array(data.buffer.slice(9))); 514 | return dataMsg; 515 | case msgChannelEOF: 516 | var data = new DataView(packetBuf); 517 | var eofMsg:channelEOFMsg = { 518 | peersID: data.getUint32(1), 519 | }; 520 | return eofMsg; 521 | case msgChannelOpen: 522 | var data = new DataView(packetBuf); 523 | var openMsg:channelOpenMsg = { 524 | peersID: data.getUint32(1), 525 | peersWindow: data.getUint32(5), 526 | maxPacketSize: data.getUint32(9), 527 | }; 528 | return openMsg; 529 | case msgChannelOpenConfirm: 530 | var data = new DataView(packetBuf); 531 | var confirmMsg:channelOpenConfirmMsg = { 532 | peersID: data.getUint32(1), 533 | myID: data.getUint32(5), 534 | myWindow: data.getUint32(9), 535 | maxPacketSize: data.getUint32(13), 536 | }; 537 | return confirmMsg; 538 | case msgChannelOpenFailure: 539 | var data = new DataView(packetBuf); 540 | var failureMsg:channelOpenFailureMsg = { 541 | peersID: data.getUint32(1), 542 | }; 543 | return failureMsg; 544 | case msgChannelWindowAdjust: 545 | var data = new DataView(packetBuf); 546 | var adjustMsg:channelWindowAdjustMsg = { 547 | peersID: data.getUint32(1), 548 | additionalBytes: data.getUint32(5), 549 | }; 550 | return adjustMsg; 551 | default: 552 | throw "unknown type"; 553 | } 554 | } 555 | 556 | // export {Session, Channel}; 557 | 558 | // declare var define: any; 559 | // declare var window: any; 560 | 561 | // (function () { 562 | 563 | // let exportables = [Session, Channel]; 564 | 565 | // // Node: Export function 566 | // if (typeof module !== "undefined" && module.exports) { 567 | // exportables.forEach(exp => module.exports[nameof(exp)] = exp); 568 | // } 569 | // // AMD/requirejs: Define the module 570 | // else if (typeof define === 'function' && define.amd) { 571 | // exportables.forEach(exp => define(() => exp)); 572 | // } 573 | // //expose it through Window 574 | // else if (window) { 575 | // exportables.forEach(exp => (window as any)[nameof(exp)] = exp); 576 | // } 577 | 578 | // function nameof(fn: any): string { 579 | // return typeof fn === 'undefined' ? '' : fn.name ? fn.name : (() => { 580 | // let result = /^function\s+([\w\$]+)\s*\(/.exec(fn.toString()); 581 | // return !result ? '' : result[1]; 582 | // })(); 583 | // } 584 | 585 | // } ()); 586 | -------------------------------------------------------------------------------- /exp/python/qmux.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, List 2 | from abc import ABC, abstractmethod 3 | from functools import reduce 4 | import asyncio, pdb 5 | import struct 6 | 7 | class DataView(): 8 | def __init__(self, buffer): 9 | self.buffer = [element.to_bytes(1, 'big') for element in buffer] 10 | 11 | def get_uint_8(self, index): 12 | return struct.unpack('>B', self.buffer[index])[0] 13 | 14 | def set_uint_8(self, index, number): 15 | if number > 255 or number < 0: 16 | number = number % 255 17 | self.buffer[index] = struct.pack(">B", number) 18 | 19 | def get_uint_16(self, index): 20 | byte_list = [] 21 | for byte in self.buffer[index:index+2]: 22 | byte_list.append(byte) 23 | return struct.unpack('>H', reduce(lambda a, b: a + b, byte_list))[0] 24 | 25 | def set_uint_16(self, index, number): 26 | struct_bytes = struct.pack('>H', number) 27 | for byte_index, byte in enumerate(struct_bytes): 28 | self.buffer[index+byte_index] = struct.pack(">B", byte) 29 | 30 | def get_uint_32(self, index): 31 | byte_list = [] 32 | for byte in self.buffer[index:index+4]: 33 | byte_list.append(byte) 34 | return struct.unpack('>L', reduce(lambda a, b: a + b, byte_list))[0] 35 | 36 | def set_uint_32(self, index, number): 37 | struct_bytes = struct.pack('>L', number) 38 | for byte_index, byte in enumerate(struct_bytes): 39 | self.buffer[index+byte_index] = struct.pack(">B", byte) 40 | 41 | def bytes(self): 42 | return reduce(lambda a, b: a + b, self.buffer) 43 | 44 | def empty_array(length): 45 | return [0] * length 46 | 47 | MSG_CHANNEL_OPEN = 100 48 | MSG_CHANNEL_OPEN_CONFIRM = 101 49 | MSG_CHANNEL_OPEN_FAILURE = 102 50 | MSG_CHANNEL_WINDOW_ADJUST = 103 51 | MSG_CHANNEL_DATA = 104 52 | MSG_CHANNEL_EOF = 105 53 | MSG_CHANNEL_CLOSE = 106 54 | 55 | MIN_PACKET_LENGTH = 9 56 | CHANNEL_MAX_PACKET = 1 << 15 57 | CHANNEL_WINDOW_SIZE = 64 * CHANNEL_MAX_PACKET 58 | 59 | class ChannelOpenMsg(): 60 | def __init__(self, peers_id: int, peers_window: int, max_packet_size: int): 61 | self.peers_id = peers_id 62 | self.peers_window = peers_window 63 | self.max_packet_size = max_packet_size 64 | 65 | class ChannelOpenConfirmMsg(): 66 | def __init__(self, peers_id: int, my_id: int, my_window: int, max_packet_size: int): 67 | self.peers_id = peers_id 68 | self.my_id = my_id 69 | self.my_window = my_window 70 | self.max_packet_size = max_packet_size 71 | 72 | class ChannelOpenFailureMsg(): 73 | def __init__(self, peers_id: int): 74 | self.peers_id = peers_id 75 | 76 | class ChannelWindowAdjustMsg(): 77 | def __init__(self, peers_id: int, additional_bytes: int): 78 | self.peers_id = peers_id 79 | self.additional_bytes = additional_bytes 80 | 81 | class ChannelDataMsg(): 82 | def __init__(self, peers_id: int, length: int, rest: list): 83 | self.peers_id = peers_id 84 | self.length = length 85 | self.rest = rest 86 | 87 | class ChannelEOFMsg(): 88 | def __init__(self, peers_id: int): 89 | self.peers_id = peers_id 90 | 91 | class ChannelCloseMsg(): 92 | def __init__(self, peers_id: int): 93 | self.peers_id = peers_id 94 | 95 | class Queue(): 96 | def __init__(self): 97 | self.queue = [] 98 | self.waiters = [] 99 | self.closed = False 100 | 101 | def push(self, obj): 102 | if self.closed: 103 | raise Exception("closed Queue") 104 | if self.waiters: 105 | self.waiters.pop(0).set_result(obj) 106 | return 107 | self.queue.append(obj) 108 | 109 | def shift(self) -> 'asyncio.Future': 110 | promise: 'asyncio.Future' = asyncio.Future() 111 | if self.closed: 112 | return promise 113 | if self.queue: 114 | promise.set_result(self.queue.pop(0)) 115 | return promise 116 | self.waiters.append(promise) 117 | return promise 118 | 119 | def close(self): 120 | if self.closed: 121 | return 122 | self.closed = True 123 | for waiter in self.waiters: 124 | waiter.cancel() 125 | 126 | class IConn(ABC): 127 | @abstractmethod 128 | async def read(self, length: int) -> 'asyncio.Future': 129 | pass 130 | 131 | @abstractmethod 132 | def write(self, buffer: bytes) -> 'asyncio.Future': 133 | pass 134 | 135 | @abstractmethod 136 | def close(self): 137 | pass 138 | 139 | class TCPConn(IConn): 140 | def __init__(self, reader, writer): 141 | self.reader = reader 142 | self.writer = writer 143 | 144 | async def read(self, length: int) -> 'asyncio.Future': 145 | try: 146 | return await self.reader.read(length) 147 | except ConnectionResetError: 148 | return [] 149 | 150 | def write(self, buffer: bytes) -> 'asyncio.Future': 151 | # TODO: think about draining 152 | self.writer.write(buffer) 153 | promise: 'asyncio.Future' = asyncio.Future() 154 | promise.set_result(len(buffer)) 155 | return promise 156 | 157 | def close(self): 158 | self.writer.close() 159 | 160 | class Session(): 161 | def __init__(self, conn: 'IConn', spawn): 162 | self.conn = conn 163 | self.channels: list = [] 164 | self.incoming = Queue() 165 | self.spawn = spawn 166 | spawn(self.loop()) 167 | 168 | @asyncio.coroutine 169 | def read_packet(self) -> 'asyncio.Future': 170 | sizes = { 171 | MSG_CHANNEL_OPEN: 12, 172 | MSG_CHANNEL_OPEN_CONFIRM: 16, 173 | MSG_CHANNEL_OPEN_FAILURE: 4, 174 | MSG_CHANNEL_WINDOW_ADJUST: 8, 175 | MSG_CHANNEL_DATA: 8, 176 | MSG_CHANNEL_EOF: 4, 177 | MSG_CHANNEL_CLOSE: 4, 178 | } 179 | msg = yield from self.conn.read(1) 180 | promise: 'asyncio.Future' = asyncio.Future() 181 | if not msg: 182 | promise.set_result(None) 183 | return promise 184 | if msg[0] < MSG_CHANNEL_OPEN or msg[0] > MSG_CHANNEL_CLOSE: 185 | raise Exception("bad packet: %s" % msg[0]) 186 | rest = yield from self.conn.read(sizes.get(msg[0])) 187 | if not rest: 188 | raise Exception("unexpected EOF") 189 | if msg[0] == MSG_CHANNEL_DATA: 190 | view = DataView(rest) 191 | length = view.get_uint_32(4) 192 | data = yield from self.conn.read(length) 193 | if not data: 194 | raise Exception("unexpected EOF") 195 | promise.set_result([*msg, *rest, *data]) 196 | return promise 197 | promise.set_result([*msg, *rest]) 198 | return promise 199 | 200 | async def handle_channel_open(self, packet: list): 201 | msg: 'ChannelOpenMsg' = decode(packet) 202 | if msg.max_packet_size < MIN_PACKET_LENGTH or msg.max_packet_size > 1<<30: 203 | await self.conn.write(encode(MSG_CHANNEL_OPEN_FAILURE, ChannelOpenFailureMsg(msg.peers_id))) 204 | return 205 | channel = self.new_channel() 206 | channel.remote_id = msg.peers_id 207 | channel.max_remote_pay_load = msg.max_packet_size 208 | channel.remote_win = msg.peers_window 209 | channel.max_incoming_pay_load = CHANNEL_MAX_PACKET 210 | self.incoming.push(channel) 211 | await self.conn.write(encode(MSG_CHANNEL_OPEN_CONFIRM, ChannelOpenConfirmMsg(channel.remote_id, channel.local_id, channel.my_window, channel.max_incoming_pay_load))) 212 | 213 | async def open(self) -> 'asyncio.Future': 214 | channel = self.new_channel() 215 | channel.max_incoming_pay_load = CHANNEL_MAX_PACKET 216 | await self.conn.write(encode(MSG_CHANNEL_OPEN, ChannelOpenMsg(channel.local_id, channel.my_window, channel.max_incoming_pay_load))) 217 | if await channel.ready.shift(): 218 | return channel 219 | raise Exception("failed to open") 220 | 221 | def new_channel(self) -> 'Channel': 222 | channel = Channel() 223 | channel.remote_win = 0 224 | channel.my_window = CHANNEL_WINDOW_SIZE 225 | channel.ready = Queue() 226 | channel.read_buf = bytearray() 227 | channel.readers = [] 228 | channel.session = self 229 | channel.local_id = self.add_ch(channel) 230 | return channel 231 | 232 | async def loop(self): 233 | try: 234 | while True: 235 | packet = await self.read_packet() 236 | if not packet.result(): 237 | self.close() 238 | return 239 | data = DataView(packet.result()) 240 | if packet.result()[0] == MSG_CHANNEL_OPEN: 241 | await self.handle_channel_open(data.buffer) 242 | continue 243 | data_id = data.get_uint_32(1) 244 | channel = self.get_ch(data_id) 245 | if not channel: 246 | raise Exception("invalid channel (%s) on op %s" % (data_id, packet[0])) 247 | await channel.handle_packet(data) 248 | except: 249 | raise Exception("session readloop") 250 | 251 | def get_ch(self, channel_id: int) -> 'Channel': 252 | channel = self.channels[channel_id] 253 | if channel.local_id != channel_id: 254 | print("bad ids: %s, %s, %s" % (channel_id, channel.local_id, channel.remote_id)) 255 | return channel 256 | 257 | def add_ch(self, channel: 'Channel') -> int: 258 | for i, var in enumerate(self.channels): 259 | if not var: 260 | self.channels[i] = channel 261 | return i 262 | self.channels.append(channel) 263 | return len(self.channels)-1 264 | 265 | def rm_ch(self, channel_id: int): 266 | self.channels[channel_id] = None 267 | 268 | async def accept(self) -> 'asyncio.Future': 269 | return await self.incoming.shift() 270 | 271 | def close(self): 272 | for channel_id in range(len(self.channels)-1): 273 | if not self.channels[channel_id]: 274 | self.channels[channel_id].shutdown() 275 | self.conn.close() 276 | 277 | class Channel(): 278 | local_id = 0 279 | remote_id = 0 280 | max_incoming_pay_load = 0 281 | max_remote_pay_load = 0 282 | session = None 283 | ready = None 284 | sent_EOF = None 285 | got_EOF = None 286 | sent_close = None 287 | remote_win = 0 288 | my_window = 0 289 | read_buf = bytearray() 290 | readers: List[Callable] 291 | 292 | def ident(self) -> int: 293 | return self.local_id 294 | 295 | def send_packet(self, packet: list) -> 'asyncio.Future': 296 | if self.sent_close: 297 | raise Exception("EOF") 298 | self.sent_close = packet[0] == MSG_CHANNEL_CLOSE 299 | promise: 'asyncio.Future' = asyncio.Future() 300 | promise.set_result(self.session.conn.write(bytes(packet))) 301 | return promise 302 | 303 | def send_message(self, number: int, msg) -> 'asyncio.Future': 304 | data = DataView(encode(number, msg)) 305 | data.set_uint_32(1, self.remote_id) 306 | promise: 'asyncio.Future' = asyncio.Future() 307 | promise.set_result(self.send_packet(data.bytes())) 308 | return promise 309 | 310 | async def handle_packet(self, packet: 'DataView'): 311 | if packet.get_uint_8(0) == MSG_CHANNEL_DATA: 312 | await self.handle_data(packet) 313 | return 314 | if packet.get_uint_8(0) == MSG_CHANNEL_CLOSE: 315 | self.handle_close() 316 | return 317 | if packet.get_uint_8(0) == MSG_CHANNEL_EOF: 318 | self.got_EOF = True 319 | return 320 | if packet.get_uint_8(0) == MSG_CHANNEL_OPEN_FAILURE: 321 | fmsg: 'ChannelOpenFailureMsg' = decode(packet.buffer) 322 | self.session.rm_ch(fmsg.peers_id) 323 | self.ready.push(False) 324 | return 325 | if packet.get_uint_8(0) == MSG_CHANNEL_OPEN_CONFIRM: 326 | cmsg: 'ChannelOpenConfirmMsg' = decode(packet.buffer) 327 | if cmsg.max_packet_size < MIN_PACKET_LENGTH or cmsg.max_packet_size > 1<<30: 328 | raise Exception("invalid max packet size") 329 | self.remote_id = cmsg.my_id 330 | self.max_remote_pay_load = cmsg.max_packet_size 331 | self.remote_win += cmsg.my_window 332 | self.ready.push(True) 333 | return 334 | if packet.get_uint_8(0) == MSG_CHANNEL_WINDOW_ADJUST: 335 | amsg: 'ChannelWindowAdjustMsg' = decode(packet.buffer) 336 | self.remote_win += amsg.additional_bytes 337 | 338 | async def handle_data(self, packet: 'DataView'): 339 | length = packet.get_uint_32(5) 340 | if not length: 341 | return 342 | if length > self.max_incoming_pay_load: 343 | raise Exception("incoming packet exceeds maximum payload size") 344 | data = packet.buffer[9:] 345 | if self.my_window < length: 346 | raise Exception("remote side wrote too much") 347 | self.my_window -= length 348 | for element in data: 349 | self.read_buf.extend(element) 350 | if self.readers: 351 | self.readers.pop(0)() 352 | 353 | async def adjust_window(self, num: int): 354 | # TODO 355 | return num 356 | 357 | def read(self, length) -> 'asyncio.Future': 358 | promise: 'asyncio.Future' = asyncio.Future() 359 | def try_read(): 360 | if self.read_buf is None: 361 | promise.set_result(None) 362 | return promise 363 | if len(self.read_buf) >= length: 364 | data = self.read_buf[0:length] 365 | self.read_buf = self.read_buf[length:] 366 | promise.set_result(data) 367 | if not self.read_buf and self.got_EOF: 368 | self.read_buf = None 369 | return promise 370 | self.readers.append(try_read) 371 | return promise 372 | return try_read() 373 | 374 | def write(self, buffer: list) -> 'asyncio.Future': 375 | if self.sent_EOF: 376 | raise Exception("EOF") 377 | header = DataView(empty_array(9)) 378 | header.set_uint_8(0, MSG_CHANNEL_DATA) 379 | header.set_uint_32(1, self.remote_id) 380 | header.set_uint_32(5, len(buffer)) 381 | promise: 'asyncio.Future' = asyncio.Future() 382 | promise.set_result(self.send_packet(header.bytes()+buffer)) 383 | return promise 384 | 385 | def handle_close(self): 386 | self.session.spawn(self.close()) 387 | 388 | async def close(self): 389 | if not self.sent_close: 390 | await self.send_message(MSG_CHANNEL_CLOSE, ChannelCloseMsg(self.remote_id)) 391 | self.sent_close = True 392 | while await self.ready.shift(): 393 | return 394 | self.shutdown() 395 | 396 | def shutdown(self): 397 | self.read_buf = None 398 | for reader in self.readers: 399 | reader() 400 | self.ready.close() 401 | self.session.rm_ch(self.local_id) 402 | 403 | async def close_write(self): 404 | self.sent_EOF = True 405 | await self.send_message(MSG_CHANNEL_EOF, ChannelEOFMsg(self.remote_id)) 406 | 407 | def encode(msg_type: int, obj) -> bytes: 408 | if msg_type == MSG_CHANNEL_CLOSE: 409 | data = DataView(empty_array(5)) 410 | data.set_uint_8(0, msg_type) 411 | data.set_uint_32(1, obj.peers_id) 412 | return data.bytes() 413 | if msg_type == MSG_CHANNEL_DATA: 414 | data = DataView(empty_array(9)) 415 | data.set_uint_8(0, msg_type) 416 | data.set_uint_32(1, obj.peers_id) 417 | data.set_uint_32(5, obj.length) 418 | buf = empty_array(9+obj.length) 419 | buf[0] = data.buffer 420 | buf[9] = obj.rest 421 | return bytes(buf) 422 | if msg_type == MSG_CHANNEL_EOF: 423 | data = DataView(empty_array(5)) 424 | data.set_uint_8(0, msg_type) 425 | data.set_uint_32(1, obj.peers_id) 426 | return data.bytes() 427 | if msg_type == MSG_CHANNEL_OPEN: 428 | data = DataView(empty_array(13)) 429 | data.set_uint_8(0, msg_type) 430 | data.set_uint_32(1, obj.peers_id) 431 | data.set_uint_32(5, obj.peers_window) 432 | data.set_uint_32(9, obj.max_packet_size) 433 | return data.bytes() 434 | if msg_type == MSG_CHANNEL_OPEN_CONFIRM: 435 | data = DataView(empty_array(17)) 436 | data.set_uint_8(0, msg_type) 437 | data.set_uint_32(1, obj.peers_id) 438 | data.set_uint_32(5, obj.my_id) 439 | data.set_uint_32(9, obj.my_window) 440 | data.set_uint_32(13, obj.max_packet_size) 441 | return data.bytes() 442 | if msg_type == MSG_CHANNEL_OPEN_FAILURE: 443 | data = DataView(empty_array(5)) 444 | data.set_uint_8(0, msg_type) 445 | data.set_uint_32(1, obj.peers_id) 446 | return data.bytes() 447 | if msg_type == MSG_CHANNEL_WINDOW_ADJUST: 448 | data = DataView(empty_array(9)) 449 | data.set_uint_8(0, msg_type) 450 | data.set_uint_32(1, obj.peers_id) 451 | data.set_uint_32(5, obj.additional_bytes) 452 | return data.bytes() 453 | raise Exception("unknown type") 454 | 455 | def decode(packet: list): 456 | element = int.from_bytes(packet[0], 'big') 457 | data = DataView([]) 458 | data.buffer = packet 459 | if element == MSG_CHANNEL_CLOSE: 460 | close_msg = ChannelCloseMsg(data.get_uint_32(1)) 461 | return close_msg 462 | if element == MSG_CHANNEL_DATA: 463 | data_length = data.get_uint_32(5) 464 | data_msg = ChannelDataMsg(data.get_uint_32(1), data_length, empty_array(data_length)) 465 | data_msg.rest = data.buffer[9:] 466 | return data_msg 467 | if element == MSG_CHANNEL_EOF: 468 | eof_msg = ChannelEOFMsg(data.get_uint_32(1)) 469 | return eof_msg 470 | if element == MSG_CHANNEL_OPEN: 471 | open_msg = ChannelOpenMsg(data.get_uint_32(1), data.get_uint_32(5), data.get_uint_32(9)) 472 | return open_msg 473 | if element == MSG_CHANNEL_OPEN_CONFIRM: 474 | confirm_msg = ChannelOpenConfirmMsg(data.get_uint_32(1), data.get_uint_32(5), data.get_uint_32(9), data.get_uint_32(13)) 475 | return confirm_msg 476 | if element == MSG_CHANNEL_OPEN_FAILURE: 477 | failure_msg = ChannelOpenFailureMsg(data.get_uint_32(1)) 478 | return failure_msg 479 | if element == MSG_CHANNEL_WINDOW_ADJUST: 480 | adjust_msg = ChannelWindowAdjustMsg(data.get_uint_32(1), data.get_uint_32(5)) 481 | return adjust_msg 482 | raise Exception("unknown type") 483 | --------------------------------------------------------------------------------