Files
sausage/userland/esbuild/source/cmd/esbuild/stdio_protocol.go
T

206 lines
4.4 KiB
Go

// The JavaScript API communicates with the Go child process over stdin/stdout
// using this protocol. It's a very simple binary protocol that uses primitives
// and nested arrays and maps. It's basically JSON with UTF-8 encoding and an
// additional byte array primitive. You must send a response after receiving a
// request because the other end is blocking on the response coming back.
package main
import (
"encoding/binary"
"sort"
)
func readUint32(bytes []byte) (value uint32, leftOver []byte, ok bool) {
if len(bytes) >= 4 {
return binary.LittleEndian.Uint32(bytes), bytes[4:], true
}
return 0, bytes, false
}
func writeUint32(bytes []byte, value uint32) []byte {
bytes = append(bytes, 0, 0, 0, 0)
binary.LittleEndian.PutUint32(bytes[len(bytes)-4:], value)
return bytes
}
func readLengthPrefixedSlice(bytes []byte) (slice []byte, leftOver []byte, ok bool) {
if length, afterLength, ok := readUint32(bytes); ok && uint(len(afterLength)) >= uint(length) {
return afterLength[:length], afterLength[length:], true
}
return []byte{}, bytes, false
}
type packet struct {
value interface{}
id uint32
isRequest bool
}
func encodePacket(p packet) []byte {
var visit func(interface{})
var bytes []byte
visit = func(value interface{}) {
switch v := value.(type) {
case nil:
bytes = append(bytes, 0)
case bool:
n := uint8(0)
if v {
n = 1
}
bytes = append(bytes, 1, n)
case int:
bytes = append(bytes, 2)
bytes = writeUint32(bytes, uint32(v))
case string:
bytes = append(bytes, 3)
bytes = writeUint32(bytes, uint32(len(v)))
bytes = append(bytes, v...)
case []byte:
bytes = append(bytes, 4)
bytes = writeUint32(bytes, uint32(len(v)))
bytes = append(bytes, v...)
case []interface{}:
bytes = append(bytes, 5)
bytes = writeUint32(bytes, uint32(len(v)))
for _, item := range v {
visit(item)
}
case map[string]interface{}:
// Sort keys for determinism
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
bytes = append(bytes, 6)
bytes = writeUint32(bytes, uint32(len(keys)))
for _, k := range keys {
bytes = writeUint32(bytes, uint32(len(k)))
bytes = append(bytes, k...)
visit(v[k])
}
default:
panic("Invalid packet")
}
}
bytes = writeUint32(bytes, 0) // Reserve space for the length
if p.isRequest {
bytes = writeUint32(bytes, p.id<<1)
} else {
bytes = writeUint32(bytes, (p.id<<1)|1)
}
visit(p.value)
writeUint32(bytes[:0], uint32(len(bytes)-4)) // Patch the length in
return bytes
}
func decodePacket(bytes []byte) (packet, bool) {
var visit func() (interface{}, bool)
visit = func() (interface{}, bool) {
kind := bytes[0]
bytes = bytes[1:]
switch kind {
case 0: // nil
return nil, true
case 1: // bool
value := bytes[0]
bytes = bytes[1:]
return value != 0, true
case 2: // int
value, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
return int(value), true
case 3: // string
value, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
return string(value), true
case 4: // []byte
value, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
return value, true
case 5: // []interface{}
count, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
value := make([]interface{}, count)
for i := 0; i < int(count); i++ {
item, ok := visit()
if !ok {
return nil, false
}
value[i] = item
}
return value, true
case 6: // map[string]interface{}
count, next, ok := readUint32(bytes)
if !ok {
return nil, false
}
bytes = next
value := make(map[string]interface{}, count)
for i := 0; i < int(count); i++ {
key, next, ok := readLengthPrefixedSlice(bytes)
if !ok {
return nil, false
}
bytes = next
item, ok := visit()
if !ok {
return nil, false
}
value[string(key)] = item
}
return value, true
default:
panic("Invalid packet")
}
}
id, bytes, ok := readUint32(bytes)
if !ok {
return packet{}, false
}
isRequest := (id & 1) == 0
id >>= 1
value, ok := visit()
if !ok {
return packet{}, false
}
if len(bytes) != 0 {
return packet{}, false
}
return packet{id: id, isRequest: isRequest, value: value}, true
}