From 596b8c4e112c412bbeb621b818cdbeda75a81a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BA=BA=E7=94=9F?= Date: Wed, 8 Dec 2021 19:55:58 +0800 Subject: [PATCH] Add VNC as a video source (#372) Add VNC as a video source --- examples/vnc/README.md | 46 ++ examples/vnc/main.go | 127 +++++ pkg/driver/vncdriver/vnc/LICENSE | 21 + pkg/driver/vncdriver/vnc/README.md | 16 + pkg/driver/vncdriver/vnc/client.go | 494 ++++++++++++++++++++ pkg/driver/vncdriver/vnc/client_auth.go | 124 +++++ pkg/driver/vncdriver/vnc/color.go | 6 + pkg/driver/vncdriver/vnc/encoding.go | 186 ++++++++ pkg/driver/vncdriver/vnc/pixel_format.go | 151 ++++++ pkg/driver/vncdriver/vnc/pointer.go | 16 + pkg/driver/vncdriver/vnc/server_messages.go | 192 ++++++++ pkg/driver/vncdriver/vncdriver.go | 177 +++++++ 12 files changed, 1556 insertions(+) create mode 100644 examples/vnc/README.md create mode 100644 examples/vnc/main.go create mode 100644 pkg/driver/vncdriver/vnc/LICENSE create mode 100644 pkg/driver/vncdriver/vnc/README.md create mode 100644 pkg/driver/vncdriver/vnc/client.go create mode 100644 pkg/driver/vncdriver/vnc/client_auth.go create mode 100644 pkg/driver/vncdriver/vnc/color.go create mode 100644 pkg/driver/vncdriver/vnc/encoding.go create mode 100644 pkg/driver/vncdriver/vnc/pixel_format.go create mode 100644 pkg/driver/vncdriver/vnc/pointer.go create mode 100644 pkg/driver/vncdriver/vnc/server_messages.go create mode 100644 pkg/driver/vncdriver/vncdriver.go diff --git a/examples/vnc/README.md b/examples/vnc/README.md new file mode 100644 index 0000000..e5d9918 --- /dev/null +++ b/examples/vnc/README.md @@ -0,0 +1,46 @@ +## Instructions + +### Install required codecs + +In this example, we'll be using x264 and opus as our video and audio codecs. Therefore, we need to make sure that these codecs are installed within our system. + +Installation steps: + +* [x264](https://github.com/pion/mediadevices#x264) + +### Download vnc example + +``` +git clone https://github.com/pion/mediadevices.git +``` + +#### Compile vnc example + +``` +cd mediadevices/examples/vnc && go build +``` + +### Open example page + +[jsfiddle.net](https://jsfiddle.net/gh/get/library/pure/pion/mediadevices/tree/master/examples/internal/jsfiddle/audio-and-video) you should see two text-areas and a 'Start Session' button + +### Run the webrtc example with your browsers SessionDescription as stdin + +In the jsfiddle the top textarea is your browser, copy that, and store the session description in an environment variable, `export SDP=` + +Run `echo $SDP | ./vnc` + +In Windows + +```powershell +type sdp.txt| .\vnc.exe +``` +### Input webrtc's SessionDescription into your browser + +Copy the text that `./webrtc` just emitted and copy into second text area + +### Hit 'Start Session' in jsfiddle, enjoy your video! + +A video should start playing in your browser above the input boxes, and will continue playing until you close the application. + +Congrats, you have used pion-MediaDevices! Now start building something cool diff --git a/examples/vnc/main.go b/examples/vnc/main.go new file mode 100644 index 0000000..f881a67 --- /dev/null +++ b/examples/vnc/main.go @@ -0,0 +1,127 @@ +package main + +import ( + "fmt" + "github.com/pion/mediadevices/pkg/driver" + "github.com/pion/mediadevices/pkg/driver/vncdriver" + + "github.com/pion/mediadevices" + "github.com/pion/mediadevices/examples/internal/signal" + "github.com/pion/webrtc/v3" + + // If you don't like x264, you can also use vpx by importing as below + // "github.com/pion/mediadevices/pkg/codec/vpx" // This is required to use VP8/VP9 video encoder + // or you can also use openh264 for alternative h264 implementation + // "github.com/pion/mediadevices/pkg/codec/openh264" + // or if you use a raspberry pi like, you can use mmal for using its hardware encoder + // "github.com/pion/mediadevices/pkg/codec/mmal" + "github.com/pion/mediadevices/pkg/codec/x264" // This is required to use h264 video encoder + + // Note: If you don't have a camera or microphone or your adapters are not supported, + // you can always swap your adapters with our dummy adapters below. + // _ "github.com/pion/mediadevices/pkg/driver/videotest" + // _ "github.com/pion/mediadevices/pkg/driver/audiotest" +) + +func main() { + config := webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{ + { + URLs: []string{"stun:stun.l.google.com:19302"}, + }, + }, + } + driver.GetManager().Register( + vncdriver.NewVnc("127.0.0.1:5900"), + driver.Info{Label: "VNC", DeviceType: driver.Camera, Priority: driver.PriorityLow}, + ) + // Wait for the offer to be pasted + offer := webrtc.SessionDescription{} + signal.Decode(signal.MustReadStdin(), &offer) + + // Create a new RTCPeerConnection + x264Params, err := x264.NewParams() + if err != nil { + panic(err) + } + x264Params.BitRate = 500_000 // 500kbps + + if err != nil { + panic(err) + } + codecSelector := mediadevices.NewCodecSelector( + mediadevices.WithVideoEncoders(&x264Params), + ) + + mediaEngine := webrtc.MediaEngine{} + codecSelector.Populate(&mediaEngine) + api := webrtc.NewAPI(webrtc.WithMediaEngine(&mediaEngine)) + peerConnection, err := api.NewPeerConnection(config) + if err != nil { + panic(err) + } + + // Set the handler for ICE connection state + // This will notify you when the peer has connected/disconnected + peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { + fmt.Printf("Connection State has changed %s \n", connectionState.String()) + }) + + s, err := mediadevices.GetUserMedia(mediadevices.MediaStreamConstraints{ + Video: func(c *mediadevices.MediaTrackConstraints) { + + }, + Codec: codecSelector, + }) + if err != nil { + panic(err) + } + + for _, track := range s.GetTracks() { + track.OnEnded(func(err error) { + fmt.Printf("Track (ID: %s) ended with error: %v\n", + track.ID(), err) + }) + + _, err = peerConnection.AddTransceiverFromTrack(track, + webrtc.RtpTransceiverInit{ + Direction: webrtc.RTPTransceiverDirectionSendonly, + }, + ) + if err != nil { + panic(err) + } + } + + // Set the remote SessionDescription + err = peerConnection.SetRemoteDescription(offer) + if err != nil { + panic(err) + } + + // Create an answer + answer, err := peerConnection.CreateAnswer(nil) + if err != nil { + panic(err) + } + + // Create channel that is blocked until ICE Gathering is complete + gatherComplete := webrtc.GatheringCompletePromise(peerConnection) + + // Sets the LocalDescription, and starts our UDP listeners + err = peerConnection.SetLocalDescription(answer) + if err != nil { + panic(err) + } + + // Block until ICE Gathering is complete, disabling trickle ICE + // we do this because we only can exchange one signaling message + // in a production application you should exchange ICE Candidates via OnICECandidate + <-gatherComplete + + // Output the answer in base64 so we can paste it in browser + fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + + // Block forever + select {} +} diff --git a/pkg/driver/vncdriver/vnc/LICENSE b/pkg/driver/vncdriver/vnc/LICENSE new file mode 100644 index 0000000..f9c841a --- /dev/null +++ b/pkg/driver/vncdriver/vnc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pkg/driver/vncdriver/vnc/README.md b/pkg/driver/vncdriver/vnc/README.md new file mode 100644 index 0000000..cb0575b --- /dev/null +++ b/pkg/driver/vncdriver/vnc/README.md @@ -0,0 +1,16 @@ +# VNC Library for Go + +go-vnc is a VNC library for Go, initially supporting VNC clients but +with the goal of eventually implementing a VNC server. + +This library implements [RFC 6143](http://tools.ietf.org/html/rfc6143). + +## Usage & Installation + +The library is installable via standard `go get`. The package name is `vnc`. + +``` +$ go get github.com/mitchellh/go-vnc +``` + +Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-vnc diff --git a/pkg/driver/vncdriver/vnc/client.go b/pkg/driver/vncdriver/vnc/client.go new file mode 100644 index 0000000..ba94d17 --- /dev/null +++ b/pkg/driver/vncdriver/vnc/client.go @@ -0,0 +1,494 @@ +// Package vnc implements a VNC client. +// +// References: +// [PROTOCOL]: http://tools.ietf.org/html/rfc6143 +package vnc + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "unicode" +) + +type ClientConn struct { + c net.Conn + config *ClientConfig + + // If the pixel format uses a color map, then this is the color + // map that is used. This should not be modified directly, since + // the data comes from the server. + ColorMap [256]Color + + // Encodings supported by the client. This should not be modified + // directly. Instead, SetEncodings should be used. + Encs []Encoding + + // Width of the frame buffer in pixels, sent from the server. + FrameBufferWidth uint16 + + // Height of the frame buffer in pixels, sent from the server. + FrameBufferHeight uint16 + + // Name associated with the desktop, sent from the server. + DesktopName string + + // The pixel format associated with the connection. This shouldn't + // be modified. If you wish to set a new pixel format, use the + // SetPixelFormat method. + PixelFormat PixelFormat +} + +// A ClientConfig structure is used to configure a ClientConn. After +// one has been passed to initialize a connection, it must not be modified. +type ClientConfig struct { + // A slice of ClientAuth methods. Only the first instance that is + // suitable by the server will be used to authenticate. + Auth []ClientAuth + + // Exclusive determines whether the connection is shared with other + // clients. If true, then all other clients connected will be + // disconnected when a connection is established to the VNC server. + Exclusive bool + + // The channel that all messages received from the server will be + // sent on. If the channel blocks, then the goroutine reading data + // from the VNC server may block indefinitely. It is up to the user + // of the library to ensure that this channel is properly read. + // If this is not set, then all messages will be discarded. + ServerMessageCh chan<- ServerMessage + + // A slice of supported messages that can be read from the server. + // This only needs to contain NEW server messages, and doesn't + // need to explicitly contain the RFC-required messages. + ServerMessages []ServerMessage +} + +func Client(c net.Conn, cfg *ClientConfig) (*ClientConn, error) { + conn := &ClientConn{ + c: c, + config: cfg, + } + + if err := conn.handshake(); err != nil { + conn.Close() + return nil, err + } + + go conn.mainLoop() + + return conn, nil +} + +func (c *ClientConn) Close() error { + return c.c.Close() +} + +// CutText tells the server that the client has new text in its cut buffer. +// The text string MUST only contain Latin-1 characters. This encoding +// is compatible with Go's native string format, but can only use up to +// unicode.MaxLatin values. +// +// See RFC 6143 Section 7.5.6 +func (c *ClientConn) CutText(text string) error { + var buf bytes.Buffer + + // This is the fixed size data we'll send + fixedData := []interface{}{ + uint8(6), + uint8(0), + uint8(0), + uint8(0), + uint32(len(text)), + } + + for _, val := range fixedData { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + for _, char := range text { + if char > unicode.MaxLatin1 { + return fmt.Errorf("Character '%d' is not valid Latin-1", char) + } + + if err := binary.Write(&buf, binary.BigEndian, uint8(char)); err != nil { + return err + } + } + + dataLength := 8 + len(text) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + return nil +} + +// Requests a framebuffer update from the server. There may be an indefinite +// time between the request and the actual framebuffer update being +// received. +// +// See RFC 6143 Section 7.5.3 +func (c *ClientConn) FramebufferUpdateRequest(incremental bool, x, y, width, height uint16) error { + var buf bytes.Buffer + var incrementalByte uint8 = 0 + + if incremental { + incrementalByte = 1 + } + + data := []interface{}{ + uint8(3), + incrementalByte, + x, y, width, height, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:10]); err != nil { + return err + } + + return nil +} + +// KeyEvent indiciates a key press or release and sends it to the server. +// The key is indicated using the X Window System "keysym" value. Use +// Google to find a reference of these values. To simulate a key press, +// you must send a key with both a down event, and a non-down event. +// +// See 7.5.4. +func (c *ClientConn) KeyEvent(keysym uint32, down bool) error { + var downFlag uint8 = 0 + if down { + downFlag = 1 + } + + data := []interface{}{ + uint8(4), + downFlag, + uint8(0), + uint8(0), + keysym, + } + + for _, val := range data { + if err := binary.Write(c.c, binary.BigEndian, val); err != nil { + return err + } + } + + return nil +} + +// PointerEvent indicates that pointer movement or a pointer button +// press or release. +// +// The mask is a bitwise mask of various ButtonMask values. When a button +// is set, it is pressed, when it is unset, it is released. +// +// See RFC 6143 Section 7.5.5 +func (c *ClientConn) PointerEvent(mask ButtonMask, x, y uint16) error { + var buf bytes.Buffer + + data := []interface{}{ + uint8(5), + uint8(mask), + x, + y, + } + + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + if _, err := c.c.Write(buf.Bytes()[0:6]); err != nil { + return err + } + + return nil +} + +// SetEncodings sets the encoding types in which the pixel data can +// be sent from the server. After calling this method, the encs slice +// given should not be modified. +// +// See RFC 6143 Section 7.5.2 +func (c *ClientConn) SetEncodings(encs []Encoding) error { + data := make([]interface{}, 3+len(encs)) + data[0] = uint8(2) + data[1] = uint8(0) + data[2] = uint16(len(encs)) + + for i, enc := range encs { + data[3+i] = int32(enc.Type()) + } + + var buf bytes.Buffer + for _, val := range data { + if err := binary.Write(&buf, binary.BigEndian, val); err != nil { + return err + } + } + + dataLength := 4 + (4 * len(encs)) + if _, err := c.c.Write(buf.Bytes()[0:dataLength]); err != nil { + return err + } + + c.Encs = encs + + return nil +} + +// SetPixelFormat sets the format in which pixel values should be sent +// in FramebufferUpdate messages from the server. +// +// See RFC 6143 Section 7.5.1 +func (c *ClientConn) SetPixelFormat(format *PixelFormat) error { + var keyEvent [20]byte + keyEvent[0] = 0 + + pfBytes, err := writePixelFormat(format) + if err != nil { + return err + } + + // Copy the pixel format bytes into the proper slice location + copy(keyEvent[4:], pfBytes) + + // Send the data down the connection + if _, err := c.c.Write(keyEvent[:]); err != nil { + return err + } + + // Reset the color map as according to RFC. + var newColorMap [256]Color + c.ColorMap = newColorMap + + return nil +} + +const pvLen = 12 // ProtocolVersion message length. + +func parseProtocolVersion(pv []byte) (uint, uint, error) { + var major, minor uint + + if len(pv) < pvLen { + return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen) + } + + l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) + if l != 2 { + return 0, 0, fmt.Errorf("error parsing ProtocolVersion.") + } + if err != nil { + return 0, 0, err + } + + return major, minor, nil +} + +func (c *ClientConn) handshake() error { + var protocolVersion [pvLen]byte + + // 7.1.1, read the ProtocolVersion message sent by the server. + if _, err := io.ReadFull(c.c, protocolVersion[:]); err != nil { + return err + } + + maxMajor, maxMinor, err := parseProtocolVersion(protocolVersion[:]) + if err != nil { + return err + } + if maxMajor < 3 { + return fmt.Errorf("unsupported major version, less than 3: %d", maxMajor) + } + if maxMinor < 3 { + return fmt.Errorf("unsupported minor version, less than 3: %d", maxMinor) + } + + // Respond with the version we will support + if maxMinor<8 { + if _, err = c.c.Write([]byte("RFB 003.003\n")); err != nil { + return err + } + var numSecurityTypes uint32 + if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil { + return err + } + + if numSecurityTypes == 0 { + return fmt.Errorf("no security types: %s", c.readErrorReason()) + } + }else{ + if _, err = c.c.Write([]byte("RFB 003.008\n")); err != nil { + return err + } + // 7.1.2 Security Handshake from server + var numSecurityTypes uint8 + if err = binary.Read(c.c, binary.BigEndian, &numSecurityTypes); err != nil { + return err + } + + if numSecurityTypes == 0 { + return fmt.Errorf("no security types: %s", c.readErrorReason()) + } + + securityTypes := make([]uint8, numSecurityTypes) + if err = binary.Read(c.c, binary.BigEndian, &securityTypes); err != nil { + return err + } + + clientSecurityTypes := c.config.Auth + if clientSecurityTypes == nil { + clientSecurityTypes = []ClientAuth{new(ClientAuthNone)} + } + + var auth ClientAuth + FindAuth: + for _, curAuth := range clientSecurityTypes { + for _, securityType := range securityTypes { + if curAuth.SecurityType() == securityType { + // We use the first matching supported authentication + auth = curAuth + break FindAuth + } + } + } + + if auth == nil { + return fmt.Errorf("no suitable auth schemes found. server supported: %#v", securityTypes) + } + + // Respond back with the security type we'll use + if err = binary.Write(c.c, binary.BigEndian, auth.SecurityType()); err != nil { + return err + } + + if err = auth.Handshake(c.c); err != nil { + return err + } + + // 7.1.3 SecurityResult Handshake + var securityResult uint32 + if err = binary.Read(c.c, binary.BigEndian, &securityResult); err != nil { + return err + } + + if securityResult == 1 { + return fmt.Errorf("security handshake failed: %s", c.readErrorReason()) + } + } + // 7.3.1 ClientInit + var sharedFlag uint8 = 1 + if c.config.Exclusive { + sharedFlag = 0 + } + + if err = binary.Write(c.c, binary.BigEndian, sharedFlag); err != nil { + return err + } + + // 7.3.2 ServerInit + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferWidth); err != nil { + return err + } + + if err = binary.Read(c.c, binary.BigEndian, &c.FrameBufferHeight); err != nil { + return err + } + + // Read the pixel format + if err = readPixelFormat(c.c, &c.PixelFormat); err != nil { + return err + } + + var nameLength uint32 + if err = binary.Read(c.c, binary.BigEndian, &nameLength); err != nil { + return err + } + + nameBytes := make([]uint8, nameLength) + if err = binary.Read(c.c, binary.BigEndian, &nameBytes); err != nil { + return err + } + + c.DesktopName = string(nameBytes) + + return nil +} + +// mainLoop reads messages sent from the server and routes them to the +// proper channels for users of the client to read. +func (c *ClientConn) mainLoop() { + defer c.Close() + + // Build the map of available server messages + typeMap := make(map[uint8]ServerMessage) + + defaultMessages := []ServerMessage{ + new(FramebufferUpdateMessage), + new(SetColorMapEntriesMessage), + new(BellMessage), + new(ServerCutTextMessage), + } + + for _, msg := range defaultMessages { + typeMap[msg.Type()] = msg + } + + if c.config.ServerMessages != nil { + for _, msg := range c.config.ServerMessages { + typeMap[msg.Type()] = msg + } + } + + for { + var messageType uint8 + if err := binary.Read(c.c, binary.BigEndian, &messageType); err != nil { + break + } + + msg, ok := typeMap[messageType] + if !ok { + // Unsupported message type! Bad! + break + } + + parsedMsg, err := msg.Read(c, c.c) + if err != nil { + break + } + + if c.config.ServerMessageCh == nil { + continue + } + + c.config.ServerMessageCh <- parsedMsg + } +} + +func (c *ClientConn) readErrorReason() string { + var reasonLen uint32 + if err := binary.Read(c.c, binary.BigEndian, &reasonLen); err != nil { + return "" + } + + reason := make([]uint8, reasonLen) + if err := binary.Read(c.c, binary.BigEndian, &reason); err != nil { + return "" + } + + return string(reason) +} diff --git a/pkg/driver/vncdriver/vnc/client_auth.go b/pkg/driver/vncdriver/vnc/client_auth.go new file mode 100644 index 0000000..4ae7be9 --- /dev/null +++ b/pkg/driver/vncdriver/vnc/client_auth.go @@ -0,0 +1,124 @@ +package vnc + +import ( + "net" + + "crypto/des" + "encoding/binary" +) + +// A ClientAuth implements a method of authenticating with a remote server. +type ClientAuth interface { + // SecurityType returns the byte identifier sent by the server to + // identify this authentication scheme. + SecurityType() uint8 + + // Handshake is called when the authentication handshake should be + // performed, as part of the general RFB handshake. (see 7.2.1) + Handshake(net.Conn) error +} + +// ClientAuthNone is the "none" authentication. See 7.2.1 +type ClientAuthNone byte + +func (*ClientAuthNone) SecurityType() uint8 { + return 1 +} + +func (*ClientAuthNone) Handshake(net.Conn) error { + return nil +} + +// PasswordAuth is VNC authentication, 7.2.2 +type PasswordAuth struct { + Password string +} + +func (p *PasswordAuth) SecurityType() uint8 { + return 2 +} + +func (p *PasswordAuth) Handshake(c net.Conn) error { + randomValue := make([]uint8, 16) + if err := binary.Read(c, binary.BigEndian, &randomValue); err != nil { + return err + } + + crypted, err := p.encrypt(p.Password, randomValue) + + if (err != nil) { + return err + } + + if err := binary.Write(c, binary.BigEndian, &crypted); err != nil { + return err + } + + return nil +} + +func (p *PasswordAuth) reverseBits(b byte) byte { + var reverse = [256]int{ + 0, 128, 64, 192, 32, 160, 96, 224, + 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, + 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, + 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, + 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, + 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, + 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, + 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, + 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, + 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, + 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, + 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, + 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, + 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, + 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, + 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, + 31, 159, 95, 223, 63, 191, 127, 255, + } + + return byte(reverse[int(b)]) +} + +func (p *PasswordAuth) encrypt(key string, bytes []byte) ([]byte, error) { + keyBytes := []byte{0,0,0,0,0,0,0,0} + + if len(key) > 8 { + key = key[:8] + } + + for i := 0; i < len(key); i++ { + keyBytes[i] = p.reverseBits(key[i]) + } + + block, err := des.NewCipher(keyBytes) + + if err != nil { + return nil, err + } + + result1 := make([]byte, 8) + block.Encrypt(result1, bytes) + result2 := make([]byte, 8) + block.Encrypt(result2, bytes[8:]) + + crypted := append(result1, result2...) + + return crypted, nil +} diff --git a/pkg/driver/vncdriver/vnc/color.go b/pkg/driver/vncdriver/vnc/color.go new file mode 100644 index 0000000..30df467 --- /dev/null +++ b/pkg/driver/vncdriver/vnc/color.go @@ -0,0 +1,6 @@ +package vnc + +// Color represents a single color in a color map. +type Color struct { + R, G, B uint16 +} diff --git a/pkg/driver/vncdriver/vnc/encoding.go b/pkg/driver/vncdriver/vnc/encoding.go new file mode 100644 index 0000000..f99c4d0 --- /dev/null +++ b/pkg/driver/vncdriver/vnc/encoding.go @@ -0,0 +1,186 @@ +package vnc + +import ( + "bytes" + "compress/zlib" + "encoding/binary" + "io" +) + +// An Encoding implements a method for encoding pixel data that is +// sent by the server to the client. +type Encoding interface { + // The number that uniquely identifies this encoding type. + Type() int32 + + // Read reads the contents of the encoded pixel data from the reader. + // This should return a new Encoding implementation that contains + // the proper data. + Read(*ClientConn, *Rectangle, io.Reader) (Encoding, error) +} + +// RawEncoding is raw pixel data sent by the server. +// +// See RFC 6143 Section 7.7.1 +type RawEncoding struct { + Colors []Color + RawPixel []uint32 //RGBA +} + +func (*RawEncoding) Type() int32 { + return 0 +} + +func (*RawEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + bytesPerPixel := c.PixelFormat.BPP / 8 + pixelBytes := make([]uint8, bytesPerPixel) + + var byteOrder binary.ByteOrder = binary.LittleEndian + if c.PixelFormat.BigEndian { + byteOrder = binary.BigEndian + } + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + rawPixels:=make([]uint32,int(rect.Height)*int(rect.Width)) + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := io.ReadFull(r, pixelBytes); err != nil { + return nil, err + } + + var rawPixel uint32 + if c.PixelFormat.BPP == 8 { + rawPixel = uint32(pixelBytes[0]) + } else if c.PixelFormat.BPP == 16 { + rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + } else if c.PixelFormat.BPP == 32 { + rawPixel = byteOrder.Uint32(pixelBytes) + } + //rawPixels[int(y)*int(rect.Width)+int(x)]=rawPixel + color := &colors[int(y)*int(rect.Width)+int(x)] + if c.PixelFormat.TrueColor { + color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) + color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) + color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) + } else { + *color = c.ColorMap[rawPixel] + } + rawPixels[int(y)*int(rect.Width)+int(x)]=uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R) + //fmt.Printf("%x %x",rawPixel,rawPixels[int(y)*int(rect.Width)+int(x)]) + } + } + + return &RawEncoding{colors,rawPixels}, nil +} +// ZlibEncoding is raw pixel data sent by the server compressed by Zlib. +// +// A single Zlib stream is created. There is only a single header for a framebuffer request response. +type ZlibEncoding struct { + Colors []Color + RawPixel[] uint32 + ZStream *bytes.Buffer + ZReader io.ReadCloser +} + +func (*ZlibEncoding) Type() int32 { + return 6 +} + +func (ze *ZlibEncoding) Read(c *ClientConn, rect *Rectangle, r io.Reader) (Encoding, error) { + bytesPerPixel := c.PixelFormat.BPP / 8 + pixelBytes := make([]uint8, bytesPerPixel) + + var byteOrder binary.ByteOrder = binary.LittleEndian + if c.PixelFormat.BigEndian { + byteOrder = binary.BigEndian + } + + // Format + // 4 bytes | uint32 | length + // 'length' bytes | []byte | zlibData + + // Read zlib length + var zipLength uint32 + err := binary.Read(r, binary.BigEndian, &zipLength) + if err != nil { + return nil, err + } + + // Read all compressed data + zBytes := make([]byte, zipLength) + if _, err := io.ReadFull(r, zBytes); err != nil { + return nil, err + } + + // Create new zlib stream if needed + if ze.ZStream == nil { + // Create and save the buffer + ze.ZStream = new(bytes.Buffer) + ze.ZStream.Write(zBytes) + + // Create a reader for the buffer + ze.ZReader, err = zlib.NewReader(ze.ZStream) + if err != nil { + return nil, err + } + + // This is needed to avoid 'zlib missing header' + } else { + // Just append if already created + ze.ZStream.Write(zBytes) + } + + // Calculate zlib decompressed size + sizeToRead := int(rect.Height) * int(rect.Width) * int(bytesPerPixel) + + // Create buffer for bytes + colorBytes := make([]byte, sizeToRead) + + // Read all data from zlib stream + read, err := io.ReadFull(ze.ZReader, colorBytes) + if read != sizeToRead || err != nil { + return nil, err + } + + // Create buffer for raw encoding + colorReader := bytes.NewReader(colorBytes) + + colors := make([]Color, int(rect.Height)*int(rect.Width)) + rawPixels:=make([]uint32,int(rect.Height)*int(rect.Width)) + for y := uint16(0); y < rect.Height; y++ { + for x := uint16(0); x < rect.Width; x++ { + if _, err := io.ReadFull(colorReader, pixelBytes); err != nil { + return nil, err + } + + var rawPixel uint32 + if c.PixelFormat.BPP == 8 { + rawPixel = uint32(pixelBytes[0]) + } else if c.PixelFormat.BPP == 16 { + rawPixel = uint32(byteOrder.Uint16(pixelBytes)) + } else if c.PixelFormat.BPP == 32 { + rawPixel = byteOrder.Uint32(pixelBytes) + } + + color := &colors[int(y)*int(rect.Width)+int(x)] + if c.PixelFormat.TrueColor { + color.R = uint16((rawPixel >> c.PixelFormat.RedShift) & uint32(c.PixelFormat.RedMax)) + color.G = uint16((rawPixel >> c.PixelFormat.GreenShift) & uint32(c.PixelFormat.GreenMax)) + color.B = uint16((rawPixel >> c.PixelFormat.BlueShift) & uint32(c.PixelFormat.BlueMax)) + } else { + *color = c.ColorMap[rawPixel] + } + rawPixels[int(y)*int(rect.Width)+int(x)]=uint32(color.B)<<16 | uint32(color.G)<<8 | uint32(color.R) + } + } + + return &ZlibEncoding{Colors: colors,RawPixel: rawPixels}, nil +} + +func (ze *ZlibEncoding) Close() { + if ze.ZStream != nil { + ze.ZStream = nil + ze.ZReader.Close() + ze.ZReader = nil + } +} \ No newline at end of file diff --git a/pkg/driver/vncdriver/vnc/pixel_format.go b/pkg/driver/vncdriver/vnc/pixel_format.go new file mode 100644 index 0000000..ff8edbf --- /dev/null +++ b/pkg/driver/vncdriver/vnc/pixel_format.go @@ -0,0 +1,151 @@ +package vnc + +import ( + "bytes" + "encoding/binary" + "io" +) + +// PixelFormat describes the way a pixel is formatted for a VNC connection. +// +// See RFC 6143 Section 7.4 for information on each of the fields. +type PixelFormat struct { + BPP uint8 + Depth uint8 + BigEndian bool + TrueColor bool + RedMax uint16 + GreenMax uint16 + BlueMax uint16 + RedShift uint8 + GreenShift uint8 + BlueShift uint8 +} + +func readPixelFormat(r io.Reader, result *PixelFormat) error { + var rawPixelFormat [16]byte + if _, err := io.ReadFull(r, rawPixelFormat[:]); err != nil { + return err + } + + var pfBoolByte uint8 + brPF := bytes.NewReader(rawPixelFormat[:]) + if err := binary.Read(brPF, binary.BigEndian, &result.BPP); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.Depth); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // Big endian is true + result.BigEndian = true + } + + if err := binary.Read(brPF, binary.BigEndian, &pfBoolByte); err != nil { + return err + } + + if pfBoolByte != 0 { + // True Color is true. So we also have to read all the color max & shifts. + result.TrueColor = true + + if err := binary.Read(brPF, binary.BigEndian, &result.RedMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueMax); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.RedShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.GreenShift); err != nil { + return err + } + + if err := binary.Read(brPF, binary.BigEndian, &result.BlueShift); err != nil { + return err + } + } + + return nil +} + +func writePixelFormat(format *PixelFormat) ([]byte, error) { + var buf bytes.Buffer + + // Byte 1 + if err := binary.Write(&buf, binary.BigEndian, format.BPP); err != nil { + return nil, err + } + + // Byte 2 + if err := binary.Write(&buf, binary.BigEndian, format.Depth); err != nil { + return nil, err + } + + var boolByte byte + if format.BigEndian { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 3 (BigEndian) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + if format.TrueColor { + boolByte = 1 + } else { + boolByte = 0 + } + + // Byte 4 (TrueColor) + if err := binary.Write(&buf, binary.BigEndian, boolByte); err != nil { + return nil, err + } + + // If we have true color enabled then we have to fill in the rest of the + // structure with the color values. + if format.TrueColor { + if err := binary.Write(&buf, binary.BigEndian, format.RedMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueMax); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.RedShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.GreenShift); err != nil { + return nil, err + } + + if err := binary.Write(&buf, binary.BigEndian, format.BlueShift); err != nil { + return nil, err + } + } + + return buf.Bytes()[0:16], nil +} diff --git a/pkg/driver/vncdriver/vnc/pointer.go b/pkg/driver/vncdriver/vnc/pointer.go new file mode 100644 index 0000000..86d84e3 --- /dev/null +++ b/pkg/driver/vncdriver/vnc/pointer.go @@ -0,0 +1,16 @@ +package vnc + +// ButtonMask represents a mask of pointer presses/releases. +type ButtonMask uint8 + +// All available button mask components. +const ( + ButtonLeft ButtonMask = 1 << iota + ButtonMiddle + ButtonRight + Button4 + Button5 + Button6 + Button7 + Button8 +) diff --git a/pkg/driver/vncdriver/vnc/server_messages.go b/pkg/driver/vncdriver/vnc/server_messages.go new file mode 100644 index 0000000..6f2eeed --- /dev/null +++ b/pkg/driver/vncdriver/vnc/server_messages.go @@ -0,0 +1,192 @@ +package vnc + +import ( + "encoding/binary" + "fmt" + "io" +) + +// A ServerMessage implements a message sent from the server to the client. +type ServerMessage interface { + // The type of the message that is sent down on the wire. + Type() uint8 + + // Read reads the contents of the message from the reader. At the point + // this is called, the message type has already been read from the reader. + // This should return a new ServerMessage that is the appropriate type. + Read(*ClientConn, io.Reader) (ServerMessage, error) +} + +// FramebufferUpdateMessage consists of a sequence of rectangles of +// pixel data that the client should put into its framebuffer. +type FramebufferUpdateMessage struct { + Rectangles []Rectangle +} + +// Rectangle represents a rectangle of pixel data. +type Rectangle struct { + X uint16 + Y uint16 + Width uint16 + Height uint16 + Enc Encoding +} + +func (*FramebufferUpdateMessage) Type() uint8 { + return 0 +} + +func (*FramebufferUpdateMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var numRects uint16 + if err := binary.Read(r, binary.BigEndian, &numRects); err != nil { + return nil, err + } + + // Build the map of encodings supported + encMap := make(map[int32]Encoding) + for _, enc := range c.Encs { + encMap[enc.Type()] = enc + } + + // We must always support the raw encoding + rawEnc := new(RawEncoding) + encMap[rawEnc.Type()] = rawEnc + + rects := make([]Rectangle, numRects) + for i := uint16(0); i < numRects; i++ { + var encodingType int32 + + rect := &rects[i] + data := []interface{}{ + &rect.X, + &rect.Y, + &rect.Width, + &rect.Height, + &encodingType, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + enc, ok := encMap[encodingType] + if !ok { + return nil, fmt.Errorf("unsupported encoding type: %d", encodingType) + } + + var err error + rect.Enc, err = enc.Read(c, rect, r) + if err != nil { + return nil, err + } + } + + return &FramebufferUpdateMessage{rects}, nil +} + +// SetColorMapEntriesMessage is sent by the server to set values into +// the color map. This message will automatically update the color map +// for the associated connection, but contains the color change data +// if the consumer wants to read it. +// +// See RFC 6143 Section 7.6.2 +type SetColorMapEntriesMessage struct { + FirstColor uint16 + Colors []Color +} + +func (*SetColorMapEntriesMessage) Type() uint8 { + return 1 +} + +func (*SetColorMapEntriesMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [1]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var result SetColorMapEntriesMessage + if err := binary.Read(r, binary.BigEndian, &result.FirstColor); err != nil { + return nil, err + } + + var numColors uint16 + if err := binary.Read(r, binary.BigEndian, &numColors); err != nil { + return nil, err + } + + result.Colors = make([]Color, numColors) + for i := uint16(0); i < numColors; i++ { + + color := &result.Colors[i] + data := []interface{}{ + &color.R, + &color.G, + &color.B, + } + + for _, val := range data { + if err := binary.Read(r, binary.BigEndian, val); err != nil { + return nil, err + } + } + + // Update the connection's color map + c.ColorMap[result.FirstColor+i] = *color + } + + return &result, nil +} + +// Bell signals that an audible bell should be made on the client. +// +// See RFC 6143 Section 7.6.3 +type BellMessage byte + +func (*BellMessage) Type() uint8 { + return 2 +} + +func (*BellMessage) Read(*ClientConn, io.Reader) (ServerMessage, error) { + return new(BellMessage), nil +} + +// ServerCutTextMessage indicates the server has new text in the cut buffer. +// +// See RFC 6143 Section 7.6.4 +type ServerCutTextMessage struct { + Text string +} + +func (*ServerCutTextMessage) Type() uint8 { + return 3 +} + +func (*ServerCutTextMessage) Read(c *ClientConn, r io.Reader) (ServerMessage, error) { + // Read off the padding + var padding [3]byte + if _, err := io.ReadFull(r, padding[:]); err != nil { + return nil, err + } + + var textLength uint32 + if err := binary.Read(r, binary.BigEndian, &textLength); err != nil { + return nil, err + } + + textBytes := make([]uint8, textLength) + if err := binary.Read(r, binary.BigEndian, &textBytes); err != nil { + return nil, err + } + + return &ServerCutTextMessage{string(textBytes)}, nil +} diff --git a/pkg/driver/vncdriver/vncdriver.go b/pkg/driver/vncdriver/vncdriver.go new file mode 100644 index 0000000..0155243 --- /dev/null +++ b/pkg/driver/vncdriver/vncdriver.go @@ -0,0 +1,177 @@ +// Package videotest provides vncDevice video driver for testing. +package vncdriver + +import ( + "context" + "encoding/binary" + "fmt" + "github.com/pion/mediadevices/pkg/driver/vncdriver/vnc" + "image" + "io" + "net" + "sync" + "time" + + "github.com/pion/mediadevices/pkg/frame" + "github.com/pion/mediadevices/pkg/io/video" + "github.com/pion/mediadevices/pkg/prop" +) + +type vncDevice struct { + closed <-chan struct{} + cancel func() + tick *time.Ticker + h, w int + rawPixel []byte + mutex sync.Mutex + vClient *vnc.ClientConn + vncAddr string +} + +func NewVnc(vncAddr string) *vncDevice { + return &vncDevice{vncAddr: vncAddr} +} +func (d *vncDevice) PointerEvent(mask uint8, x, y uint16) { + if d.vClient!=nil{ + d.vClient.PointerEvent(vnc.ButtonMask(mask), x, y) + } +} +func (d *vncDevice) KeyEvent(keysym uint32, down bool) { + if d.vClient!=nil { + d.vClient.KeyEvent(keysym, down) + } +} +func (d *vncDevice) Open() error { + if d.vClient != nil { + return nil + } + ctx, cancel := context.WithCancel(context.Background()) + d.closed = ctx.Done() + d.cancel = cancel + msg := make(chan vnc.ServerMessage, 1) + conf := vnc.ClientConfig{ + ServerMessageCh: msg, + Exclusive: false, + } + d.mutex.Lock() + defer d.mutex.Unlock() + conn, err := net.Dial("tcp", d.vncAddr) + if err != nil { + return err + } + d.vClient, err = vnc.Client(conn, &conf) + if err != nil { + return err + } + d.vClient.SetEncodings([]vnc.Encoding{ + &vnc.ZlibEncoding{}, + &vnc.RawEncoding{}, + }) + d.w = int(d.vClient.FrameBufferWidth) + d.h = int(d.vClient.FrameBufferHeight) + + d.rawPixel = make([]byte, d.h*d.w*4) + + go func(ctx context.Context) { + c, cancel := context.WithCancel(ctx) + defer cancel() + if d.vClient == nil { + return + } + d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) + for { + select { + case <-c.Done(): + return + case msg := <-msg: + switch t := msg.(type) { + case *vnc.FramebufferUpdateMessage: + for _, rect := range t.Rectangles { + var pix []uint32 + switch t := rect.Enc.(type) { + case *vnc.RawEncoding: + pix = t.RawPixel + case *vnc.ZlibEncoding: + pix = t.RawPixel + } + for y := int(rect.Y); y < int(rect.Height+rect.Y); y++ { + for x := int(rect.X); x < int(rect.Width+rect.X); x++ { + binary.LittleEndian.PutUint32(d.rawPixel[(y*d.w+x)*4:], pix[(y-int(rect.Y))*int(rect.Width)+(x-int(rect.X))]) + //BigEndian + } + } + + } + //time.Sleep(33 * time.Millisecond) + d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) + break + default: + + } + case <-time.After(10 * time.Second): + //fmt.Println("Timeout FramebufferUpdate") + if d.vClient.FramebufferUpdateRequest(true, 0, 0, uint16(d.w), uint16(d.h)) != nil { + d.cancel() + return + } + + } + } + }(ctx) + return nil +} + +func (d *vncDevice) Close() error { + d.cancel() + if d.tick != nil { + d.tick.Stop() + } + d.mutex.Lock() + defer d.mutex.Unlock() + if d.vClient != nil { + d.vClient.Close() + d.vClient = nil + } + return nil +} + +func (d *vncDevice) VideoRecord(p prop.Media) (video.Reader, error) { + if p.FrameRate == 0 { + p.FrameRate = 15 + } + + tick := time.NewTicker(time.Duration(float32(time.Second) / p.FrameRate)) + d.tick = tick + closed := d.closed + pixs := make([]byte, d.h*d.w*4) + r := video.ReaderFunc(func() (image.Image, func(), error) { + select { + case <-closed: + fmt.Println("Stop Record Video By VideoRecord") + return nil, func() {}, io.EOF + default: + } + + <-tick.C + copy(pixs, d.rawPixel) + return &image.RGBA{ + Pix: pixs, + Stride: 4, + Rect: image.Rect(0, 0, d.w, d.h), + }, func() {}, nil + }) + + return r, nil +} + +func (d *vncDevice) Properties() []prop.Media { + return []prop.Media{ + { + Video: prop.Video{ + Width: d.w, + Height: d.h, + FrameFormat: frame.FormatRGBA, + }, + }, + } +}