sshpiper/cmd/sshpiperd/asciicast.go
Josh Bleecher Snyder 89ab72bee0
all: run gofumpt (#652)
* all: run gofumpt

https://github.com/mvdan/gofumpt

The main appeal for me here is that my (and many other peoples')
editors run this on save, so doing a single diff here
keeps other diffs minimal.

* .github/workflows: add gofumpt checker
2025-08-29 20:04:31 -07:00

158 lines
3.4 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"path"
"time"
)
const (
msgChannelRequest = 98
msgChannelOpenConfirm = 91
)
func jsonEscape(i string) string {
b, err := json.Marshal(i)
if err != nil {
panic(err)
}
s := string(b)
return s[1 : len(s)-1]
}
func readString(buf *bytes.Reader) string {
var l uint32
err := binary.Read(buf, binary.BigEndian, &l)
if err != nil {
return ""
}
s := make([]byte, l)
_, err = buf.Read(s)
if err != nil {
return ""
}
return string(s)
}
type asciicastLogger struct {
starttime time.Time
envs map[string]string
initWidth uint32
initHeight uint32
channels map[uint32]*os.File
channelIDMap map[uint32]uint32
recorddir string
prefix string // prefix for the output file
}
func newAsciicastLogger(recorddir string, prefix string) *asciicastLogger {
return &asciicastLogger{
envs: make(map[string]string),
recorddir: recorddir,
channels: make(map[uint32]*os.File),
channelIDMap: make(map[uint32]uint32),
prefix: prefix,
}
}
func (l *asciicastLogger) uphook(msg []byte) error {
switch msg[0] {
case msgChannelData:
clientChannelID := binary.BigEndian.Uint32(msg[1:5])
f, ok := l.channels[clientChannelID]
if ok {
buf := msg[9:]
t := time.Since(l.starttime).Seconds()
_, err := fmt.Fprintf(f, "[%v,\"o\",\"%s\"]\n", t, jsonEscape(string(buf)))
if err != nil {
return err
}
}
case msgChannelOpenConfirm:
clientChannelID := binary.BigEndian.Uint32(msg[1:5])
serverChannelID := binary.BigEndian.Uint32(msg[5:9])
l.channelIDMap[serverChannelID] = clientChannelID
}
return nil
}
func (l *asciicastLogger) downhook(msg []byte) error {
if msg[0] == msgChannelRequest {
t := time.Since(l.starttime).Seconds()
serverChannelID := binary.BigEndian.Uint32(msg[1:5])
clientChannelID := l.channelIDMap[serverChannelID]
buf := bytes.NewReader(msg[5:])
reqType := readString(buf)
switch reqType {
case "pty-req":
_, _ = buf.ReadByte()
term := readString(buf)
_ = binary.Read(buf, binary.BigEndian, &l.initWidth)
_ = binary.Read(buf, binary.BigEndian, &l.initHeight)
l.envs["TERM"] = term
case "env":
_, _ = buf.ReadByte()
varName := readString(buf)
varValue := readString(buf)
l.envs[varName] = varValue
case "window-change":
f, ok := l.channels[clientChannelID]
if !ok {
_, _ = buf.ReadByte()
var width, height uint32
_ = binary.Read(buf, binary.BigEndian, &width)
_ = binary.Read(buf, binary.BigEndian, &height)
_, err := fmt.Fprintf(f, "[%v,\"r\", \"%vx%v\"]\n", t, width, height)
if err != nil {
return err
}
}
case "shell", "exec":
jsonEnvs, err := json.Marshal(l.envs)
if err != nil {
return err
}
f, err := os.OpenFile(
path.Join(l.recorddir, fmt.Sprintf("%s%s-channel-%d.cast", l.prefix, reqType, clientChannelID)),
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0o600,
)
if err != nil {
return err
}
l.channels[clientChannelID] = f
l.starttime = time.Now()
_, err = fmt.Fprintf(
f,
"{\"version\": 2, \"width\": %d, \"height\": %d, \"timestamp\": %d, \"env\": %v}\n",
l.initWidth,
l.initHeight,
l.starttime.Unix(),
string(jsonEnvs),
)
if err != nil {
return err
}
}
}
return nil
}
func (l *asciicastLogger) Close() (err error) {
for _, f := range l.channels {
_ = f.Close()
}
return nil
}