cmd/transcode/main.go (284 lines of code) (raw):

// Command transcode converts an FBS file to an equivalent version that only uses raw encoding. package main import ( "bufio" "bytes" "encoding/binary" "flag" "fmt" "io" "log" "os" "github.com/cheggaaa/pb" logging "github.com/op/go-logging" "github.com/openai/go-vncdriver/vncclient" ) var ( in = flag.String("in", "", "path to server.fbs") out = flag.String("out", "", "path to output file") ) type cursorEncoding struct { b []byte } func (e *cursorEncoding) Type() int32 { return -239 } func (e *cursorEncoding) Size() int { return len(e.b) } func (e *cursorEncoding) Read(c *vncclient.ClientConn, rect *vncclient.Rectangle, r io.Reader) (vncclient.Encoding, error) { sz := rect.Area()*int(c.PixelFormat.BPP)/8 + (int(rect.Width+7)/8)*int(rect.Height) b := make([]byte, sz) _, err := io.ReadFull(r, b) return &cursorEncoding{b}, err } type fbsReader struct { buf bytes.Buffer r io.Reader timestamp [4]byte err error } func (r *fbsReader) read4() ([]byte, error) { b := make([]byte, 4) _, err := io.ReadFull(r.r, b) if err == io.EOF { err = io.ErrUnexpectedEOF } return b, err } func (r *fbsReader) Read(p []byte) (n int, err error) { // remember any previous error if r.err != nil { return 0, r.err } defer func() { if err != nil { r.err = err } }() if r.buf.Len() == 0 { // read length b, err := r.read4() if err != nil { return 0, err } l := int64(bytes2Uint32(b)) if l == 0 { return 0, io.EOF } // read data _, err = io.Copy(&r.buf, io.LimitReader(r.r, l)) if err == io.EOF { err = io.ErrUnexpectedEOF } if err != nil { return 0, err } // read timestamp b, err = r.read4() if err != nil { return 0, err } copy(r.timestamp[:], b) } return r.buf.Read(p) } func (r *fbsReader) Timestamp() []byte { return r.timestamp[:] } func main() { logging.SetLevel(logging.INFO, "") flag.Parse() if *in == "" || *out == "" { flag.Usage() fmt.Println("\n--in and --out are both required") os.Exit(1) } // Open input file and start streaming progress to stdout. var br *bufio.Reader { fi, err := os.Stat(*in) check(err) bar := pb.New64(fi.Size()) bar.SetMaxWidth(100) bar.SetUnits(pb.U_BYTES) bar.Start() defer bar.FinishPrint("") f, err := os.Open(*in) check(err) defer f.Close() br = bufio.NewReader(bar.NewProxyReader(f)) } // Open output file and copy header information to it. var w *bufio.Writer { f, err := os.Create(*out) check(err) defer f.Close() w = bufio.NewWriter(f) defer w.Flush() } { version := make([]byte, 12) check(io.ReadFull(br, version)) if s := string(version); s != "FBS 001.002\n" { log.Fatal("Unrecognized FBS version: " + s) } check(w.Write(version)) } { b, err := br.ReadBytes('\n') check(err) check(w.Write(b)) } r := &fbsReader{r: br} // Utilities. var ( // next returns the next n bytes of data from the server, and updates // timestamp to the FBS timestamp of the last of those bytes. // It exits non-zero if there are fewer than n bytes left in the file. next = func(n int) []byte { b := make([]byte, n) check(io.ReadFull(r, b)) return b } // nextSafe is like next, but exits zero if there are fewer than n bytes // left in the file. nextSafe = func(n int) []byte { b := make([]byte, n) _, err := io.ReadFull(r, b) if err == io.EOF || err == io.ErrUnexpectedEOF { os.Exit(0) } check(err) return b } // emit writes data in FBS format to w. emit = func(data []byte) { check(binary.Write(w, binary.BigEndian, uint32(len(data)))) check(w.Write(data)) check(w.Write(r.timestamp[:])) } ) // Read handshake and init { // ProtocolVersion message // 12 bytes version := next(12) major, minor, err := vncclient.ParseProtocolVersion(version) check(err) if major != 3 || minor != 3 { log.Fatalf("Unsupported RFB protocol version: %v.%v", major, minor) } emit(version) vncAuth := func() { // VNC Authentication challenge (we assume this is the security type chosen) // 16 bytes emit(next(16)) // SecurityResult message // 4 bytes b := next(4) if result := bytes2Uint32(b); result != 0 { log.Fatalf("Unexpected SecurityResult handshake: %v", result) } emit(b) } if minor == 3 { b := next(4) if sec := bytes2Uint32(b); sec != 2 { log.Fatalf("Unsupported security-type: %v", sec) } emit(b) vncAuth() } if minor == 8 { // security-types message // n+1 bytes, where n is the value of the first byte n := next(1) emit(append(n, next(int(n[0]))...)) vncAuth() } } // ServerInit message // 2+2+16+4+N bytes b := next(24) var pf vncclient.PixelFormat check(vncclient.ReadPixelFormat(bytes.NewReader(b[4:20]), &pf)) if !(pf.BPP == 32 && pf.Depth == 24 && !pf.BigEndian && pf.TrueColor && pf.RedMax == 255 && pf.GreenMax == 255 && pf.BlueMax == 255 && pf.RedShift == 16 && pf.GreenShift == 8 && pf.BlueShift == 0) { log.Fatalf("Unsupported pixel format: %#v\n", pf) } emit(append(b, next(int(bytes2Uint32(b[20:24])))...)) var fbu vncclient.FramebufferUpdateMessage conn := &vncclient.ClientConn{ Encs: []vncclient.Encoding{ new(vncclient.TightEncoding), new(vncclient.RawEncoding), new(cursorEncoding), }, PixelFormat: pf, } // // Most of the processing time is spent here, transcoding FramebufferUpdate messages // for { switch b := nextSafe(1); b[0] { // FramebufferUpdate case 0: msg, err := fbu.Read(conn, r) if err == io.EOF || err == io.ErrUnexpectedEOF { return } check(err) rects := msg.(*vncclient.FramebufferUpdateMessage).Rectangles // Start counting how many bytes we're going to write size := 0 size += 4 // account for header (see below) for _, r := range rects { size += 12 // every Rectangle starts out with four 2-byte fields (X, Y, W, H) // and a 4-byte encoding type, and then also contains pixels if ce, ok := r.Enc.(*cursorEncoding); ok { size += len(ce.b) // if it's a cursor encoding, it already got parsed } else { size += r.Area() * 4 // if it's pixels, then it's four bytes per pixel } } // Write FramebufferUpdate header check(binary.Write(w, binary.BigEndian, uint32(size))) check(w.Write(b)) // message type check(w.Write([]byte{0})) // padding check(binary.Write(w, binary.BigEndian, uint16(len(rects)))) // number of rectangles // Write FramebufferUpdate rectangles for _, r := range rects { var colors []vncclient.Color isCursor := false newEncType := int32(0) // regardless of input type, we're outputting raw (unless it's cursor) switch e := r.Enc.(type) { case *vncclient.TightEncoding: colors = e.Colors case *vncclient.RawEncoding: colors = e.Colors case *cursorEncoding: isCursor = true newEncType = r.Enc.Type() // keep it as cursor type } for _, x := range []interface{}{ r.X, r.Y, r.Width, r.Height, newEncType, } { check(binary.Write(w, binary.BigEndian, x)) } if isCursor { check(w.Write(r.Enc.(*cursorEncoding).b)) } for _, c := range colors { colorOrder := (uint32(c.R) << conn.PixelFormat.RedShift) + (uint32(c.G) << conn.PixelFormat.GreenShift) + (uint32(c.B) << conn.PixelFormat.BlueShift) // Write the RGB bytes out in little-endian order, // having verified "!pf.BigEndian" above check(w.Write([]byte{uint8(colorOrder), uint8(colorOrder >> 8), uint8(colorOrder >> 16), 0})) } } check(w.Write(r.timestamp[:])) // SetColorMapEntries case 1: log.Fatal("SetColorMapEntries not supported") // Bell case 2: emit(b) // ServerCutText case 3: b = append(b, nextSafe(7)...) b = append(b, nextSafe(int(bytes2Uint32(b[4:8])))...) emit(b) default: log.Fatalf("Unrecognized Server-to-Client message type: %v", b[0]) } } } func bytes2Uint32(b []byte) (u uint32) { if len(b) != 4 { panic(fmt.Sprintf("wrong size for []byte: %v", len(b))) } check(binary.Read(bytes.NewReader(b), binary.BigEndian, &u)) return } func check(e ...interface{}) { err := e[len(e)-1] if err != nil { log.Fatal(err) } }